Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[vim] blockwise paste #2751

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
109 changes: 80 additions & 29 deletions keymap/vim.js
Expand Up @@ -787,17 +787,19 @@
* pasted, should it insert itself into a new line, or should the text be
* inserted at the cursor position.)
*/
function Register(text, linewise) {
function Register(text, linewise, blockwise) {
this.clear();
this.keyBuffer = [text || ''];
this.insertModeChanges = [];
this.searchQueries = [];
this.linewise = !!linewise;
this.blockwise = !!blockwise;
}
Register.prototype = {
setText: function(text, linewise) {
setText: function(text, linewise, blockwise) {
this.keyBuffer = [text || ''];
this.linewise = !!linewise;
this.blockwise = !!blockwise;
},
pushText: function(text, linewise) {
// if this register has ever been set to linewise, use linewise.
Expand Down Expand Up @@ -842,7 +844,7 @@
registers['/'] = new Register();
}
RegisterController.prototype = {
pushText: function(registerName, operator, text, linewise) {
pushText: function(registerName, operator, text, linewise, blockwise) {
if (linewise && text.charAt(0) == '\n') {
text = text.slice(1) + '\n';
}
Expand All @@ -859,7 +861,7 @@
switch (operator) {
case 'yank':
// The 0 register contains the text from the most recent yank.
this.registers['0'] = new Register(text, linewise);
this.registers['0'] = new Register(text, linewise, blockwise);
break;
case 'delete':
case 'change':
Expand All @@ -875,7 +877,7 @@
break;
}
// Make sure the unnamed register is set to what just happened
this.unnamedRegister.setText(text, linewise);
this.unnamedRegister.setText(text, linewise, blockwise);
return;
}

Expand All @@ -884,7 +886,7 @@
if (append) {
register.pushText(text, linewise);
} else {
register.setText(text, linewise);
register.setText(text, linewise, blockwise);
}
// The unnamed register always has the same value as the last used
// register.
Expand Down Expand Up @@ -1924,17 +1926,19 @@
var curEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor;
// Save the '>' mark before cm.replaceRange clears it.
var selectionEnd, selectionStart;
var blockwise = vim.visualBlock;
if (vim.visualMode) {
selectionEnd = vim.marks['>'].find();
selectionStart = vim.marks['<'].find();
} else if (vim.lastSelection) {
selectionEnd = vim.lastSelection.curStartMark.find();
selectionStart = vim.lastSelection.curEndMark.find();
blockwise = vim.lastSelection.visualBlock;
}
var text = cm.getSelection();
vimGlobalState.registerController.pushText(
operatorArgs.registerName, 'delete', text,
operatorArgs.linewise);
operatorArgs.linewise, blockwise);
var replacement = new Array(selections.length).join('1').split('1');
// If the ending line is past the last line, inclusive, instead of
// including the trailing \n, include the \n before the starting line
Expand Down Expand Up @@ -2007,11 +2011,11 @@
cm.setCursor(cursorIsBefore(curStart, curEnd) ? curStart : curEnd);
}
},
yank: function(cm, operatorArgs, _vim, _curStart, _curEnd, curOriginal) {
yank: function(cm, operatorArgs, vim, _curStart, _curEnd, curOriginal) {
var text = cm.getSelection();
vimGlobalState.registerController.pushText(
operatorArgs.registerName, 'yank',
text, operatorArgs.linewise);
text, operatorArgs.linewise, vim.visualBlock);
cm.setCursor(curOriginal);
}
};
Expand Down Expand Up @@ -2374,6 +2378,7 @@
var text = Array(actionArgs.repeat + 1).join(text);
}
var linewise = register.linewise;
var blockwise = register.blockwise;
if (linewise) {
if(vim.visualMode) {
text = vim.visualLine ? text.slice(0, -1) : '\n' + text.slice(0, text.length - 1) + '\n';
Expand All @@ -2386,6 +2391,7 @@
cur.ch = 0;
}
} else {
text = blockwise ? text.split('\n') : text;
cur.ch += actionArgs.after ? 1 : 0;
}
var curPosFinal;
Expand All @@ -2397,32 +2403,71 @@
var selectedArea = getSelectedAreaRange(cm, vim);
var selectionStart = selectedArea[0];
var selectionEnd = selectedArea[1];
var selectedText = cm.getSelection();
var selections = cm.listSelections();
var emptyStrings = new Array(selections.length).join('1').split('1');
// save the curEnd marker before it get cleared due to cm.replaceRange.
if (vim.lastSelection) lastSelectionCurEnd = vim.lastSelection.curEndMark.find();
if (vim.lastSelection) {
lastSelectionCurEnd = vim.lastSelection.curEndMark.find();
}
// push the previously selected text to unnamed register
vimGlobalState.registerController.unnamedRegister.setText(cm.getRange(selectionStart, selectionEnd));
cm.replaceRange(text, selectionStart, selectionEnd);
vimGlobalState.registerController.unnamedRegister.setText(selectedText);
if (blockwise) {
// first delete the selected text
cm.replaceSelections(emptyStrings);
// Set new selections as per the block length of the yanked text
selectionEnd = Pos(selectionStart.line + text.length-1, selectionStart.ch);
cm.setCursor(selectionStart);
selectBlock(cm, selectionEnd);
cm.replaceSelections(text);
curPosFinal = selectionStart;
} else if (vim.visualBlock) {
cm.replaceSelections(emptyStrings);
cm.setCursor(selectionStart);
cm.replaceRange(text, selectionStart, selectionStart);
curPosFinal = selectionStart;
} else {
cm.replaceRange(text, selectionStart, selectionEnd);
curPosFinal = cm.posFromIndex(cm.indexFromPos(selectionStart) + text.length - 1);
}
// restore the the curEnd marker
if(lastSelectionCurEnd) vim.lastSelection.curEndMark = cm.setBookmark(lastSelectionCurEnd);
curPosFinal = cm.posFromIndex(cm.indexFromPos(selectionStart) + text.length - 1);
if(linewise)curPosFinal.ch=0;
if(lastSelectionCurEnd) {
vim.lastSelection.curEndMark = cm.setBookmark(lastSelectionCurEnd);
}
if (linewise) {
curPosFinal.ch=0;
}
} else {
cm.replaceRange(text, cur);
// Now fine tune the cursor to where we want it.
if (linewise && actionArgs.after) {
curPosFinal = Pos(
if (blockwise) {
cm.setCursor(cur);
for (var i = 0; i < text.length; i++) {
var lastCh = lineLength(cm, cur.line+i);
if (lastCh < cur.ch) {
extendLineToColumn(cm, cur.line+i, cur.ch);
}
}
cm.setCursor(cur);
selectBlock(cm, Pos(cur.line + text.length-1, cur.ch));
cm.replaceSelections(text);
curPosFinal = cur;
} else {
cm.replaceRange(text, cur);
// Now fine tune the cursor to where we want it.
if (linewise && actionArgs.after) {
curPosFinal = Pos(
cur.line + 1,
findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1)));
} else if (linewise && !actionArgs.after) {
curPosFinal = Pos(
cur.line,
findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line)));
} else if (!linewise && actionArgs.after) {
idx = cm.indexFromPos(cur);
curPosFinal = cm.posFromIndex(idx + text.length - 1);
} else {
idx = cm.indexFromPos(cur);
curPosFinal = cm.posFromIndex(idx + text.length);
} else if (linewise && !actionArgs.after) {
curPosFinal = Pos(
cur.line,
findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line)));
} else if (!linewise && actionArgs.after) {
idx = cm.indexFromPos(cur);
curPosFinal = cm.posFromIndex(idx + text.length - 1);
} else {
idx = cm.indexFromPos(cur);
curPosFinal = cm.posFromIndex(idx + text.length);
}
}
}
cm.setCursor(curPosFinal);
Expand Down Expand Up @@ -2632,6 +2677,12 @@
function escapeRegex(s) {
return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1');
}
function extendLineToColumn(cm, lineNum, column) {
var endCh = lineLength(cm, lineNum);
var spaces = new Array(column-endCh+1).join(' ');
cm.setCursor(Pos(lineNum, endCh));
cm.replaceRange(spaces, cm.getCursor());
}
// This functions selects a rectangular block
// of text with selectionEnd as any of its corner
// Height of block:
Expand Down
36 changes: 36 additions & 0 deletions test/vim_test.js
Expand Up @@ -1865,6 +1865,42 @@ testVim('S_normal', function(cm, vim, helpers) {
helpers.assertCursorAt(1, 0);
eq('aa\n\ncc', cm.getValue());
}, { value: 'aa\nbb\ncc'});
testVim('blockwise_paste', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', '3', 'j', 'l', 'y');
cm.setCursor(0, 2);
// paste one char after the current cursor position
helpers.doKeys('p');
eq('helhelo\nworwold\nfoofo\nbarba', cm.getValue());
cm.setCursor(0, 0);
helpers.doKeys('v', '4', 'l', 'y');
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', '3', 'j', 'p');
eq('helheelhelo\norwold\noofo\narba', cm.getValue());
}, { value: 'hello\nworld\nfoo\nbar'});
testVim('blockwise_paste_long/short_line', function(cm, vim, helpers) {
// extend short lines in case of different line lengths.
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', 'j', 'j', 'y');
cm.setCursor(0, 3);
helpers.doKeys('p');
eq('hellho\nfoo f\nbar b', cm.getValue());
}, { value: 'hello\nfoo\nbar'});
testVim('blockwise_paste_cut_paste', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', '2', 'j', 'x');
cm.setCursor(0, 0);
helpers.doKeys('P');
eq('cut\nand\npaste\nme', cm.getValue());
}, { value: 'cut\nand\npaste\nme'});
testVim('blockwise_paste_from_register', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', '2', 'j', '"', 'a', 'y');
cm.setCursor(0, 3);
helpers.doKeys('"', 'a', 'p');
eq('foobfar\nhellho\nworlwd', cm.getValue());
}, { value: 'foobar\nhello\nworld'});

testVim('S_visual', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('v', 'j', 'S');
Expand Down