Related: #38 reports entering an invalid end-of-line cursor position by clicking with the mouse. This report reproduces a similar symptom entirely through Vim keys and identifies a separate path in exitVisualMode.
Versions and environment
@replit/codemirror-vim: 6.4.0
@replit/codemirror-vim-core: 0.1.0
@codemirror/state: 6.7.5
@codemirror/view: 6.43.12
- Reproduced in native macOS WebKit, both with a bare CodeMirror editor and in an application integration.
Reproduction
- Start with a single line containing
alpha, with the cursor on a in Normal mode.
- Press
v, then press l five times to reach the position after the final character.
- Press Escape.
- Press
l once more.
After step 3, the Normal-mode cursor remains at zero-based column 5, after the final a. Step 4 moves it backward to column 4 despite being a rightward motion.
Expected: Escape should leave the Normal-mode cursor at column 4 immediately, and the following l should not move it. Unconfigured Vim behaves this way.
Minimal setup and programmatic reproduction:
import { EditorView, drawSelection } from '@codemirror/view';
import { vim, Vim, getCM } from '@replit/codemirror-vim';
const view = new EditorView({
doc: 'alpha',
extensions: [vim(), drawSelection()],
parent: document.body,
});
const cm = getCM(view);
for (const key of ['v', 'l', 'l', 'l', 'l', 'l', '<Esc>']) {
Vim.handleKey(cm, key, 'user');
}
console.log(cm.getCursor()); // Actual: { line: 0, ch: 5 }; expected ch: 4
Vim.handleKey(cm, 'l', 'user');
console.log(cm.getCursor()); // { line: 0, ch: 4 }
Apparent cause and candidate fix
In packages/codemirror-vim-core/vim.js, exitVisualMode calls clipCursorToContent before clearing vim.visualMode. That helper permits the line-break position while Visual mode is active, so it uses Visual-mode bounds for the resulting Normal-mode cursor.
Moving the cursor-clipping block after clearing the Visual-mode flags corrects this reproduction. updateLastSelection must still run before clearing the flags so gv retains the previous selection:
function exitVisualMode(cm, moveHead) {
var vim = cm.state.vim;
updateLastSelection(cm, vim);
vim.visualMode = false;
vim.visualLine = false;
vim.visualBlock = false;
if (moveHead !== false) {
cm.setCursor(clipCursorToContent(cm, vim.sel.head));
}
if (!vim.insertMode) CM.signal(cm, "vim-mode-change", {mode: "normal"});
}
Local checks with that change covered nonempty and empty lines, a supplementary-plane emoji, gv, visual yank, linewise put, and undo.
Related: #38 reports entering an invalid end-of-line cursor position by clicking with the mouse. This report reproduces a similar symptom entirely through Vim keys and identifies a separate path in
exitVisualMode.Versions and environment
@replit/codemirror-vim: 6.4.0@replit/codemirror-vim-core: 0.1.0@codemirror/state: 6.7.5@codemirror/view: 6.43.12Reproduction
alpha, with the cursor onain Normal mode.v, then presslfive times to reach the position after the final character.lonce more.After step 3, the Normal-mode cursor remains at zero-based column 5, after the final
a. Step 4 moves it backward to column 4 despite being a rightward motion.Expected: Escape should leave the Normal-mode cursor at column 4 immediately, and the following
lshould not move it. Unconfigured Vim behaves this way.Minimal setup and programmatic reproduction:
Apparent cause and candidate fix
In
packages/codemirror-vim-core/vim.js,exitVisualModecallsclipCursorToContentbefore clearingvim.visualMode. That helper permits the line-break position while Visual mode is active, so it uses Visual-mode bounds for the resulting Normal-mode cursor.Moving the cursor-clipping block after clearing the Visual-mode flags corrects this reproduction.
updateLastSelectionmust still run before clearing the flags sogvretains the previous selection:Local checks with that change covered nonempty and empty lines, a supplementary-plane emoji,
gv, visual yank, linewise put, and undo.