Skip to content

Commit

Permalink
Merge pull request #4934 from andrewnester/vim-emoji
Browse files Browse the repository at this point in the history
fix: Fixed handling surrogare characters in insert, replace, delete mode in Vim
  • Loading branch information
nightwing committed Sep 24, 2022
2 parents 357fcf5 + 38f893a commit e281200
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 8 deletions.
42 changes: 34 additions & 8 deletions src/keyboard/vim.js
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 @@ -3374,9 +3390,11 @@ domLib.importCssString(`.normal-mode .ace_cursor{
} else if (insertAt == 'bol') {
head = new Pos(head.line, 0);
} else if (insertAt == 'charAfter') {
head = offsetCursor(head, 0, 1);
var newPosition = updateSelectionForSurrogateCharacters(cm, head, offsetCursor(head, 0, 1));
head = newPosition.end;
} else if (insertAt == 'firstNonBlank') {
head = motions.moveToFirstNonWhiteSpaceCharacter(cm, head);
var newPosition = updateSelectionForSurrogateCharacters(cm, head, motions.moveToFirstNonWhiteSpaceCharacter(cm, head));
head = newPosition.end;
} else if (insertAt == 'startOfSelectedArea') {
if (!vim.visualMode)
return;
Expand Down Expand Up @@ -3449,9 +3467,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 +3768,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
43 changes: 43 additions & 0 deletions src/keyboard/vim_test.js
Expand Up @@ -1850,6 +1850,13 @@ testVim('a_eol', function(cm, vim, helpers) {
helpers.assertCursorAt(0, lines[0].length);
eq('vim-insert', cm.getOption('keyMap'));
});
testVim('a with surrogate characters', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('a');
helpers.doKeys('test');
helpers.doKeys('<Esc>');
eq('πŸ˜€test', cm.getValue());
}, {value: 'πŸ˜€'});
testVim('A_endOfSelectedArea', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('v', 'j', 'l');
Expand All @@ -1863,6 +1870,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 +2125,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 +2144,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 +2702,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 e281200

Please sign in to comment.