diff --git a/packages/codemirror/src/extensions/custom_tab.ts b/packages/codemirror/src/extensions/custom_tab.ts index 9ba602010..2b93d7678 100644 --- a/packages/codemirror/src/extensions/custom_tab.ts +++ b/packages/codemirror/src/extensions/custom_tab.ts @@ -2,20 +2,41 @@ import { indentLess, indentMore } from "@codemirror/commands"; import { EditorSelection, type ChangeSpec } from "@codemirror/state"; import type { KeyBinding } from "@codemirror/view"; +/** + * Custom key binding for indentation: + * + * - Tab while at the beginning of a line will indent the line. + * - Tab while not at the beginning of a line will insert a tab character. + * - Tab while not at the beginning of a line while text is selected will replace the txt with a tab character. + * - Shift+Tab will always unindent. + */ const smartIndentWithTab: KeyBinding[] = [ { key: "Tab", run({ state, dispatch }) { const { selection } = state; - - // Handle selection indenting normally - if (selection.ranges.some(range => !range.empty)) { - return indentMore({ state, dispatch }); - } - const changes = []; const newSelections = []; + // Step 1: Handle non-empty selections → replace with tab + if (selection.ranges.some(range => !range.empty)) { + for (let range of selection.ranges) { + changes.push({ from: range.from, to: range.to, insert: "\t" }); + newSelections.push(EditorSelection.cursor(range.from + 1)); + } + + dispatch( + state.update({ + changes, + selection: EditorSelection.create(newSelections), + scrollIntoView: true, + userEvent: "input" + }) + ); + return true; + } + + // Step 2: Handle empty selections for (let range of selection.ranges) { const line = state.doc.lineAt(range.head); const beforeCursor = state.doc.sliceString(line.from, range.head); @@ -24,7 +45,7 @@ const smartIndentWithTab: KeyBinding[] = [ // Only whitespace before cursor → indent line return indentMore({ state, dispatch }); } else { - // Insert a tab character at cursor + // Insert tab character at cursor changes.push({ from: range.head, to: range.head, insert: "\t" }); newSelections.push(EditorSelection.cursor(range.head + 1)); }