Skip to content

Commit 72fd4b7

Browse files
committed
fix: Fixed handling surrogare characters in insert, replace, delete mode in Vim
1 parent 357fcf5 commit 72fd4b7

File tree

2 files changed

+66
-6
lines changed

2 files changed

+66
-6
lines changed

Diff for: src/keyboard/vim.js

+30-6
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,20 @@ domLib.importCssString(`.normal-mode .ace_cursor{
923923
return range.head;
924924
}
925925

926+
function updateSelectionForSurrogateCharacters(cm, curStart, curEnd) {
927+
// start and character position when no selection
928+
// is the same in visual mode, and differs in 1 character in normal mode
929+
if (curStart.line === curEnd.line && curStart.ch >= curEnd.ch - 1) {
930+
var text = cm.getLine(curStart.line);
931+
var charCode = text.charCodeAt(curStart.ch);
932+
if (0xD800 <= charCode && charCode <= 0xD8FF) {
933+
curEnd.ch += 1;
934+
}
935+
}
936+
937+
return {start: curStart, end: curEnd};
938+
}
939+
926940
var defaultKeymap = [
927941
// Key to key mapping. This goes first to make it possible to override
928942
// existing mappings.
@@ -2540,9 +2554,10 @@ domLib.importCssString(`.normal-mode .ace_cursor{
25402554
mode = vim.visualBlock ? 'block' :
25412555
linewise ? 'line' :
25422556
'char';
2557+
var newPositions = updateSelectionForSurrogateCharacters(cm, curStart, curEnd);
25432558
cmSel = makeCmSelection(cm, {
2544-
anchor: curStart,
2545-
head: curEnd
2559+
anchor: newPositions.start,
2560+
head: newPositions.end
25462561
}, mode);
25472562
if (linewise) {
25482563
var ranges = cmSel.ranges;
@@ -2574,9 +2589,10 @@ domLib.importCssString(`.normal-mode .ace_cursor{
25742589
}
25752590
mode = 'char';
25762591
var exclusive = !motionArgs.inclusive || linewise;
2592+
var newPositions = updateSelectionForSurrogateCharacters(cm, curStart, curEnd);
25772593
cmSel = makeCmSelection(cm, {
2578-
anchor: curStart,
2579-
head: curEnd
2594+
anchor: newPositions.start,
2595+
head: newPositions.end
25802596
}, mode, exclusive);
25812597
}
25822598
cm.setSelections(cmSel.ranges, cmSel.primary);
@@ -3449,9 +3465,10 @@ domLib.importCssString(`.normal-mode .ace_cursor{
34493465
vim.visualBlock = !!actionArgs.blockwise;
34503466
head = clipCursorToContent(
34513467
cm, new Pos(anchor.line, anchor.ch + repeat - 1));
3468+
var newPosition = updateSelectionForSurrogateCharacters(cm, anchor, head)
34523469
vim.sel = {
3453-
anchor: anchor,
3454-
head: head
3470+
anchor: newPosition.start,
3471+
head: newPosition.end
34553472
};
34563473
CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""});
34573474
updateCmSelection(cm);
@@ -3749,18 +3766,25 @@ domLib.importCssString(`.normal-mode .ace_cursor{
37493766
}
37503767
curEnd = new Pos(curStart.line, replaceTo);
37513768
}
3769+
3770+
var newPositions = updateSelectionForSurrogateCharacters(cm, curStart, curEnd);
3771+
curStart = newPositions.start;
3772+
curEnd = newPositions.end;
37523773
if (replaceWith=='\n') {
37533774
if (!vim.visualMode) cm.replaceRange('', curStart, curEnd);
37543775
// special case, where vim help says to replace by just one line-break
37553776
(CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm);
37563777
} else {
37573778
var replaceWithStr = cm.getRange(curStart, curEnd);
3779+
// replace all surrogate characters with selected character
3780+
replaceWithStr = replaceWithStr.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, replaceWith);
37583781
//replace all characters in range by selected, but keep linebreaks
37593782
replaceWithStr = replaceWithStr.replace(/[^\n]/g, replaceWith);
37603783
if (vim.visualBlock) {
37613784
// Tabs are split in visua block before replacing
37623785
var spaces = new Array(cm.getOption("tabSize")+1).join(' ');
37633786
replaceWithStr = cm.getSelection();
3787+
replaceWithStr = replaceWithStr.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, replaceWith);
37643788
replaceWithStr = replaceWithStr.replace(/\t/g, spaces).replace(/[^\n]/g, replaceWith).split('\n');
37653789
cm.replaceSelections(replaceWithStr);
37663790
} else {

Diff for: src/keyboard/vim_test.js

+36
Original file line numberDiff line numberDiff line change
@@ -1863,6 +1863,13 @@ testVim('i', function(cm, vim, helpers) {
18631863
helpers.assertCursorAt(0, 1);
18641864
eq('vim-insert', cm.getOption('keyMap'));
18651865
});
1866+
testVim('i with surrogate characters', function(cm, vim, helpers) {
1867+
cm.setCursor(0, 0);
1868+
helpers.doKeys('i');
1869+
helpers.doKeys('test');
1870+
helpers.doKeys('<Esc>');
1871+
eq('test😀', cm.getValue());
1872+
}, { value: '😀' });
18661873
testVim('i_repeat', function(cm, vim, helpers) {
18671874
helpers.doKeys('3', 'i');
18681875
helpers.doKeys('test')
@@ -2111,6 +2118,11 @@ testVim('r', function(cm, vim, helpers) {
21112118
helpers.doKeys('r', '<CR>');
21122119
eq('\nx', cm.getValue());
21132120
}, { value: 'wordet\nanother' });
2121+
testVim('r with surrogate characters', function(cm, vim, helpers) {
2122+
cm.setCursor(0, 0);
2123+
helpers.doKeys('r', 'u');
2124+
eq('u', cm.getValue());
2125+
}, { value: '😀' });
21142126
testVim('r_visual_block', function(cm, vim, helpers) {
21152127
cm.ace.setOptions({tabSize: 4, useSoftTabs: false}); // ace_patch TODO
21162128
cm.setCursor(2, 3);
@@ -2125,6 +2137,16 @@ testVim('r_visual_block', function(cm, vim, helpers) {
21252137
helpers.doKeys('<C-v>', 'h', 'h', 'r', 'r');
21262138
eq('1 l\n5 l\nalllefg\nrrrrrrrr', cm.getValue());
21272139
}, {value: '1234\n5678\nabcdefg', indentWithTabs: true});
2140+
testVim('r_visual with surrogate characters', function(cm, vim, helpers) {
2141+
cm.setCursor(0, 0);
2142+
helpers.doKeys('v', 'r', 'u');
2143+
eq('u', cm.getValue());
2144+
}, { value: '😀' });
2145+
testVim('r_visual_block with surrogate characters', function(cm, vim, helpers) {
2146+
cm.setCursor(0, 0);
2147+
helpers.doKeys('<C-v>', 'r', 'u');
2148+
eq('u', cm.getValue());
2149+
}, { value: '😀' });
21282150
testVim('R', function(cm, vim, helpers) {
21292151
cm.setCursor(0, 1);
21302152
helpers.doKeys('R');
@@ -2673,13 +2695,27 @@ testVim('s_normal', function(cm, vim, helpers) {
26732695
helpers.doKeys('<Esc>');
26742696
eq('ac', cm.getValue());
26752697
}, { value: 'abc'});
2698+
testVim('s_normal surrogate character', function(cm, vim, helpers) {
2699+
cm.setCursor(0, 0);
2700+
helpers.doKeys('s');
2701+
helpers.doKeys('test');
2702+
helpers.doKeys('<Esc>');
2703+
eq('test', cm.getValue());
2704+
}, { value: '😀' });
26762705
testVim('s_visual', function(cm, vim, helpers) {
26772706
cm.setCursor(0, 1);
26782707
helpers.doKeys('v', 's');
26792708
helpers.doKeys('<Esc>');
26802709
helpers.assertCursorAt(0, 0);
26812710
eq('ac', cm.getValue());
26822711
}, { value: 'abc'});
2712+
testVim('d with surrogate character', function(cm, vim, helpers) {
2713+
cm.setCursor(0, 0);
2714+
helpers.doKeys('v');
2715+
helpers.doKeys('d');
2716+
helpers.doKeys('<Esc>');
2717+
eq('', cm.getValue());
2718+
}, { value: '😀' });
26832719
testVim('o_visual', function(cm, vim, helpers) {
26842720
cm.setCursor(0,0);
26852721
helpers.doKeys('v','l','l','l','o');

0 commit comments

Comments
 (0)