uiwjs / react-codemirror

CodeMirror 6 component for React. @codemirror https://uiwjs.github.io/react-codemirror/
https://uiwjs.github.io/react-codemirror/
MIT License
1.68k stars 132 forks source link

tab causes entire line to shift #432

Open sam-rez opened 1 year ago

sam-rez commented 1 year ago

When I use tab it causes the entire line to shift.

Is there a way to only have it tab from where the cursor is?

jaywcjlove commented 1 year ago

@sam-rez

You need to find the answer to this question here: https://discuss.codemirror.net/

andrienko commented 1 year ago

@jaywcjlove, looking for answer there doesn't help. In there it explicitly says that tab key is not bound by default. And the documentation seems to be rather.. terse. @uiwjs/react-codemirror, on the other hand, adds it by default, and the wrong one - it is exactly the expected behavior of that function, I believe.

Btw, I found it weird - I'd expect a prop called indentWithTab to set default indentation to tab, not add a keybinding to tab button to indent with spaces.

@sam-rez, react-codemirror uses indentMore and indentLess for tab by default, which are exactly that - indenting entire line left/right. You have to change that behavior to make it work:

insertTab and indentLess are in @codemirror/commands package, keymap is in @codemirror/view package.

Also, if you want shift+tab to shift line(s) left only when you have something selected, you'll have to create your own command, similar to insertTab command:

import type { StateCommand } from '@codemirror/state';
import { indentLess } from '@codemirror/commands';

export const indentSelectionLess: StateCommand = ({state, dispatch}) => {
  if (state.readOnly) return false;
  if (state.selection.ranges.some(r => !r.empty)) return indentLess({state, dispatch})
  return false;
}

...and if you want tab to insert the default indentation unit (in my case, with tabSize: 2, it is 2 spaces) - you could use something like this:

import type { StateCommand } from '@codemirror/state';
import { indentMore } from '@codemirror/commands';

import { indentUnit } from '@codemirror/language';

export const indentOrInsertTab: StateCommand = ({ state, dispatch }) => {
  if (state.readOnly) return false;
  if (state.selection.ranges.some((r) => !r.empty)) return indentMore({ state, dispatch });
  dispatch(state.update(state.replaceSelection(state.facet(indentUnit)), { scrollIntoView: true, userEvent: 'input' }));
  return true;
};

So, final keybinding would look something like this:

{
    key: 'Tab',
    run: indentOrInsertTab,
    shift: indentSelectionLess,
  }

that will insert default indentation (spaces or tab if tab is set as indentUnit) when there's nothing selected, indent lines if something is selected, and pressing shift+tab would unindent only when there's something selected.