@@ -92,26 +92,46 @@ function isReadOnly(state: EditorState): boolean {
9292 return state . facet ( EditorState . readOnly ) ;
9393}
9494
95- // Breaks keyboard navigation out of the editor, but we want that
96- const breakFocusOutBinding : KeyBinding = {
97- // https://codemirror.net/examples/tab/
98- ...indentWithTab ,
99- // `indentWithTab` will also do indent when tab is pressed without Shift (like
100- // in browser devtools for example). We want to just input tab symbol in that
101- // case
102- run ( { state, dispatch } ) {
103- if ( isReadOnly ( state ) ) {
95+ // Breaks keyboard navigation out of the editor, but we want that.
96+ // A user has to hit `Escape` then `Tab` to enter keyboard navigation.
97+ // Note that the ordering of these matters as no more key handlers are called
98+ // after the first corresponding `run` function returns true.
99+ // https://codemirror.net/examples/tab/
100+ const tabKeymap : KeyBinding [ ] = [
101+ {
102+ key : 'Tab' ,
103+ run ( context ) {
104+ if ( isReadOnly ( context . state ) ) {
105+ return false ;
106+ }
107+ return acceptCompletion ( context ) ;
108+ } ,
109+ } ,
110+ {
111+ key : 'Tab' ,
112+ run ( { state, dispatch } ) {
113+ if ( isReadOnly ( state ) ) {
114+ return false ;
115+ }
116+
117+ // `indentWithTab` will indent when `Tab` is pressed without any selection (like
118+ // in browser devtools for example). Instead we want to input the tab symbol in
119+ // that case, to have behavior similar to VSCode's editors.
120+ if ( ! state . selection . ranges . some ( ( range ) => ! range . empty ) ) {
121+ dispatch (
122+ state . update ( state . replaceSelection ( '\t' ) , {
123+ scrollIntoView : true ,
124+ userEvent : 'input' ,
125+ } )
126+ ) ;
127+ return true ;
128+ }
129+
104130 return false ;
105- }
106- dispatch (
107- state . update ( state . replaceSelection ( '\t' ) , {
108- scrollIntoView : true ,
109- userEvent : 'input' ,
110- } )
111- ) ;
112- return true ;
131+ } ,
113132 } ,
114- } ;
133+ indentWithTab ,
134+ ] ;
115135
116136type CodemirrorThemeType = 'light' | 'dark' ;
117137
@@ -845,16 +865,10 @@ const BaseEditor = React.forwardRef<EditorRef, EditorProps>(function BaseEditor(
845865 placeholderExtension ,
846866 // User provided commands should take precedence over default keybindings.
847867 commandsExtension ,
868+ // The order of this keymap matters, when the `run` function of the corresponding key
869+ // returns false it goes to the next corresponding key, if it returns true then
870+ // it completes and does not try further handlers.
848871 keymap . of ( [
849- {
850- key : 'Tab' ,
851- run ( context ) {
852- if ( isReadOnly ( context . state ) ) {
853- return false ;
854- }
855- return acceptCompletion ( context ) ;
856- } ,
857- } ,
858872 {
859873 key : 'Ctrl-Shift-b' ,
860874 run : prettify ,
@@ -868,7 +882,7 @@ const BaseEditor = React.forwardRef<EditorRef, EditorProps>(function BaseEditor(
868882 ...historyKeymap ,
869883 ...foldKeymap ,
870884 ...completionKeymap ,
871- breakFocusOutBinding ,
885+ ... tabKeymap ,
872886 ] ) ,
873887 readOnlyExtension ,
874888 EditorView . updateListener . of ( ( update ) => {
0 commit comments