From 4798e767c41ed7daf5c2eb92db958d8abe717192 Mon Sep 17 00:00:00 2001 From: Yi Li Date: Thu, 22 Oct 2026 22:28:58 +0800 Subject: [PATCH] fix gv after leaving visual-line or visual-block mode with Esc A typed exits visual mode from handleEsc in findKey, outside a vim operation. The cursor move in exitVisualMode then reports cursor activity, handleExternalSelection sees visual mode over an empty selection and exits visual mode itself, recording the last selection with its linewise or blockwise flags. Back in the outer exitVisualMode, updateLastSelection ran again after those flags were cleared, so `V j gv` and ` j l gv` reselected charwise. An exit that the cursor move already made now returns before recording again. The existing reselect tests leave visual mode by toggling V or , which never takes this path; two tests leave it with . Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01W2aBHqJtz3wmW7kAdNmsYS --- packages/codemirror-vim-core/test/vim_test.js | 12 ++++++++++++ packages/codemirror-vim-core/vim.js | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/packages/codemirror-vim-core/test/vim_test.js b/packages/codemirror-vim-core/test/vim_test.js index 789fc06..892fe1c 100644 --- a/packages/codemirror-vim-core/test/vim_test.js +++ b/packages/codemirror-vim-core/test/vim_test.js @@ -2686,6 +2686,18 @@ testVim('reselect_visual_line', function(cm, vim, helpers) { helpers.doKeys('V', 'p' , 'g', 'v', 'd'); eq('foo\nand', cm.getValue()); }, { value: 'hello\nthis\nis\nfoo\nand\nbar' }); +testVim('reselect_visual_line_after_esc', function(cm, vim, helpers) { + helpers.doKeys('l', 'V', 'j', '', 'g', 'v'); + eq(vim.visualLine, true); + helpers.doKeys('d'); + eq('is\nfoo', cm.getValue()); +}, { value: 'hello\nthis\nis\nfoo' }); +testVim('reselect_visual_block_after_esc', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('', 'j', 'l', '', 'g', 'v'); + eq(vim.visualBlock, true); + eq(cm.getSelections().join(''), '23oo'); +}, { value: '123456\nfoo\nbar' }); testVim('reselect_visual_block', function(cm, vim, helpers) { cm.setCursor(1, 2); helpers.doKeys('', 'k', 'h', ''); diff --git a/packages/codemirror-vim-core/vim.js b/packages/codemirror-vim-core/vim.js index 3cafa6b..9345b0f 100644 --- a/packages/codemirror-vim-core/vim.js +++ b/packages/codemirror-vim-core/vim.js @@ -3958,6 +3958,12 @@ export function initVim(CM) { if (moveHead !== false) { cm.setCursor(clipCursorToContent(cm, vim.sel.head)); } + // Outside a vim operation (a typed exits from handleEsc in findKey), + // the cursor move above reports cursor activity, and + // handleExternalSelection exits visual mode itself, recording the last + // selection with its linewise/blockwise flags. Recording it again here, + // after those flags were cleared, would make `gv` reselect charwise. + if (!vim.visualMode) return; updateLastSelection(cm, vim); vim.visualMode = false; vim.visualLine = false;