Skip to content

Commit

Permalink
fix: Fixed handling surrogare characters in insert, replace, delete m…
Browse files Browse the repository at this point in the history
…ode in Vim
  • Loading branch information
andrewnester committed Sep 23, 2022
1 parent 357fcf5 commit 72fd4b7
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 6 deletions.
36 changes: 30 additions & 6 deletions src/keyboard/vim.js
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,20 @@ domLib.importCssString(`.normal-mode .ace_cursor{
return range.head;
}

function updateSelectionForSurrogateCharacters(cm, curStart, curEnd) {
// start and character position when no selection
// is the same in visual mode, and differs in 1 character in normal mode
if (curStart.line === curEnd.line && curStart.ch >= curEnd.ch - 1) {
var text = cm.getLine(curStart.line);
var charCode = text.charCodeAt(curStart.ch);
if (0xD800 <= charCode && charCode <= 0xD8FF) {
curEnd.ch += 1;
}
}

return {start: curStart, end: curEnd};
}

var defaultKeymap = [
// Key to key mapping. This goes first to make it possible to override
// existing mappings.
Expand Down Expand Up @@ -2540,9 +2554,10 @@ domLib.importCssString(`.normal-mode .ace_cursor{
mode = vim.visualBlock ? 'block' :
linewise ? 'line' :
'char';
var newPositions = updateSelectionForSurrogateCharacters(cm, curStart, curEnd);
cmSel = makeCmSelection(cm, {
anchor: curStart,
head: curEnd
anchor: newPositions.start,
head: newPositions.end
}, mode);
if (linewise) {
var ranges = cmSel.ranges;
Expand Down Expand Up @@ -2574,9 +2589,10 @@ domLib.importCssString(`.normal-mode .ace_cursor{
}
mode = 'char';
var exclusive = !motionArgs.inclusive || linewise;
var newPositions = updateSelectionForSurrogateCharacters(cm, curStart, curEnd);
cmSel = makeCmSelection(cm, {
anchor: curStart,
head: curEnd
anchor: newPositions.start,
head: newPositions.end
}, mode, exclusive);
}
cm.setSelections(cmSel.ranges, cmSel.primary);
Expand Down Expand Up @@ -3449,9 +3465,10 @@ domLib.importCssString(`.normal-mode .ace_cursor{
vim.visualBlock = !!actionArgs.blockwise;
head = clipCursorToContent(
cm, new Pos(anchor.line, anchor.ch + repeat - 1));
var newPosition = updateSelectionForSurrogateCharacters(cm, anchor, head)
vim.sel = {
anchor: anchor,
head: head
anchor: newPosition.start,
head: newPosition.end
};
CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""});
updateCmSelection(cm);
Expand Down Expand Up @@ -3749,18 +3766,25 @@ domLib.importCssString(`.normal-mode .ace_cursor{
}
curEnd = new Pos(curStart.line, replaceTo);
}

var newPositions = updateSelectionForSurrogateCharacters(cm, curStart, curEnd);
curStart = newPositions.start;
curEnd = newPositions.end;
if (replaceWith=='\n') {
if (!vim.visualMode) cm.replaceRange('', curStart, curEnd);
// special case, where vim help says to replace by just one line-break
(CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm);
} else {
var replaceWithStr = cm.getRange(curStart, curEnd);
// replace all surrogate characters with selected character
replaceWithStr = replaceWithStr.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, replaceWith);
//replace all characters in range by selected, but keep linebreaks
replaceWithStr = replaceWithStr.replace(/[^\n]/g, replaceWith);
if (vim.visualBlock) {
// Tabs are split in visua block before replacing
var spaces = new Array(cm.getOption("tabSize")+1).join(' ');
replaceWithStr = cm.getSelection();
replaceWithStr = replaceWithStr.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, replaceWith);
replaceWithStr = replaceWithStr.replace(/\t/g, spaces).replace(/[^\n]/g, replaceWith).split('\n');
cm.replaceSelections(replaceWithStr);
} else {
Expand Down
36 changes: 36 additions & 0 deletions src/keyboard/vim_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1863,6 +1863,13 @@ testVim('i', function(cm, vim, helpers) {
helpers.assertCursorAt(0, 1);
eq('vim-insert', cm.getOption('keyMap'));
});
testVim('i with surrogate characters', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('i');
helpers.doKeys('test');
helpers.doKeys('<Esc>');
eq('test😀', cm.getValue());
}, { value: '😀' });
testVim('i_repeat', function(cm, vim, helpers) {
helpers.doKeys('3', 'i');
helpers.doKeys('test')
Expand Down Expand Up @@ -2111,6 +2118,11 @@ testVim('r', function(cm, vim, helpers) {
helpers.doKeys('r', '<CR>');
eq('\nx', cm.getValue());
}, { value: 'wordet\nanother' });
testVim('r with surrogate characters', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('r', 'u');
eq('u', cm.getValue());
}, { value: '😀' });
testVim('r_visual_block', function(cm, vim, helpers) {
cm.ace.setOptions({tabSize: 4, useSoftTabs: false}); // ace_patch TODO
cm.setCursor(2, 3);
Expand All @@ -2125,6 +2137,16 @@ testVim('r_visual_block', function(cm, vim, helpers) {
helpers.doKeys('<C-v>', 'h', 'h', 'r', 'r');
eq('1 l\n5 l\nalllefg\nrrrrrrrr', cm.getValue());
}, {value: '1234\n5678\nabcdefg', indentWithTabs: true});
testVim('r_visual with surrogate characters', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('v', 'r', 'u');
eq('u', cm.getValue());
}, { value: '😀' });
testVim('r_visual_block with surrogate characters', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', 'r', 'u');
eq('u', cm.getValue());
}, { value: '😀' });
testVim('R', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('R');
Expand Down Expand Up @@ -2673,13 +2695,27 @@ testVim('s_normal', function(cm, vim, helpers) {
helpers.doKeys('<Esc>');
eq('ac', cm.getValue());
}, { value: 'abc'});
testVim('s_normal surrogate character', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('s');
helpers.doKeys('test');
helpers.doKeys('<Esc>');
eq('test', cm.getValue());
}, { value: '😀' });
testVim('s_visual', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('v', 's');
helpers.doKeys('<Esc>');
helpers.assertCursorAt(0, 0);
eq('ac', cm.getValue());
}, { value: 'abc'});
testVim('d with surrogate character', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('v');
helpers.doKeys('d');
helpers.doKeys('<Esc>');
eq('', cm.getValue());
}, { value: '😀' });
testVim('o_visual', function(cm, vim, helpers) {
cm.setCursor(0,0);
helpers.doKeys('v','l','l','l','o');
Expand Down

0 comments on commit 72fd4b7

Please sign in to comment.