Skip to content

Commit

Permalink
Added vim keybinds for '(' and ')' in order to allow movement by
Browse files Browse the repository at this point in the history
sentence. Also added unit tests for the new movement operators.
  • Loading branch information
ebogard authored and marijnh committed Apr 20, 2018
1 parent ffc174b commit 61623ec
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 0 deletions.
182 changes: 182 additions & 0 deletions keymap/vim.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@
{ keys: 'gE', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, bigWord: true, inclusive: true }},
{ keys: '{', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: false, toJumplist: true }},
{ keys: '}', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: true, toJumplist: true }},
{ keys: '(', type: 'motion', motion: 'moveBySentence', motionArgs: { forward: false }},
{ keys: ')', type: 'motion', motion: 'moveBySentence', motionArgs: { forward: true }},
{ keys: '<C-f>', type: 'motion', motion: 'moveByPage', motionArgs: { forward: true }},
{ keys: '<C-b>', type: 'motion', motion: 'moveByPage', motionArgs: { forward: false }},
{ keys: '<C-d>', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: true, explicitRepeat: true }},
Expand Down Expand Up @@ -423,6 +425,9 @@
function isWhiteSpaceString(k) {
return (/^\s*$/).test(k);
}
function isEndOfSentenceSymbol(k) {
return '.?!'.indexOf(k) != -1;
}
function inArray(val, arr) {
for (var i = 0; i < arr.length; i++) {
if (arr[i] == val) {
Expand Down Expand Up @@ -1811,6 +1816,10 @@
var dir = motionArgs.forward ? 1 : -1;
return findParagraph(cm, head, motionArgs.repeat, dir);
},
moveBySentence: function(cm, head, motionArgs) {
var dir = motionArgs.forward ? 1 : -1;
return findSentence(cm, head, motionArgs.repeat, dir);
},
moveByScroll: function(cm, head, motionArgs, vim) {
var scrollbox = cm.getScrollInfo();
var curEnd = null;
Expand Down Expand Up @@ -3534,6 +3543,179 @@
return { start: start, end: end };
}

function findSentence(cm, cur, repeat, dir) {

/*
Takes an index object
{
line: the line string,
ln: line number,
pos: index in line,
dir: direction of traversal (-1 or 1)
}
and modifies the line, ln, and pos members to represent the
next valid position or sets them to null if there are
no more valid positions.
*/
function nextChar(cm, idx) {
if (idx.pos + idx.dir < 0 || idx.pos + idx.dir >= idx.line.length) {
idx.ln += idx.dir;
if (!isLine(cm, idx.ln)) {
idx.line = null;
idx.ln = null;
idx.pos = null;
return;
}
idx.line = cm.getLine(idx.ln);
idx.pos = (idx.dir > 0) ? 0 : idx.line.length - 1;
}
else {
idx.pos += idx.dir;
}
}

/*
Performs one iteration of traversal in forward direction
Returns an index object of the new location
*/
function forward(cm, ln, pos, dir) {
var line = cm.getLine(ln);
var stop = (line === "");

var curr = {
line: line,
ln: ln,
pos: pos,
dir: dir,
}

var last_valid = {
ln: curr.ln,
pos: curr.pos,
}

var skip_empty_lines = (curr.line === "");

// Move one step to skip character we start on
nextChar(cm, curr);

while (curr.line !== null) {
last_valid.ln = curr.ln;
last_valid.pos = curr.pos;

if (curr.line === "" && !skip_empty_lines) {
return { ln: curr.ln, pos: curr.pos, };
}
else if (stop && curr.line !== "" && !isWhiteSpaceString(curr.line[curr.pos])) {
return { ln: curr.ln, pos: curr.pos, };
}
else if (isEndOfSentenceSymbol(curr.line[curr.pos])
&& !stop
&& (curr.pos === curr.line.length - 1
|| isWhiteSpaceString(curr.line[curr.pos + 1]))) {
stop = true;
}

nextChar(cm, curr);
}

/*
Set the position to the last non whitespace character on the last
valid line in the case that we reach the end of the document.
*/
var line = cm.getLine(last_valid.ln);
last_valid.pos = 0;
for(var i = line.length - 1; i >= 0; --i) {
if (!isWhiteSpaceString(line[i])) {
last_valid.pos = i;
break;
}
}

return last_valid;

}

/*
Performs one iteration of traversal in reverse direction
Returns an index object of the new location
*/
function reverse(cm, ln, pos, dir) {
var line = cm.getLine(ln);

var curr = {
line: line,
ln: ln,
pos: pos,
dir: dir,
}

var last_valid = {
ln: curr.ln,
pos: null,
};

var skip_empty_lines = (curr.line === "");

// Move one step to skip character we start on
nextChar(cm, curr);

while (curr.line !== null) {

if (curr.line === "" && !skip_empty_lines) {
if (last_valid.pos !== null) {
return last_valid;
}
else {
return { ln: curr.ln, pos: curr.pos };
}
}
else if (isEndOfSentenceSymbol(curr.line[curr.pos])
&& last_valid.pos !== null
&& !(curr.ln === last_valid.ln && curr.pos + 1 === last_valid.pos)) {
return last_valid;
}
else if (curr.line !== "" && !isWhiteSpaceString(curr.line[curr.pos])) {
skip_empty_lines = false;
last_valid = { ln: curr.ln, pos: curr.pos }
}

nextChar(cm, curr);
}

/*
Set the position to the first non whitespace character on the last
valid line in the case that we reach the beginning of the document.
*/
var line = cm.getLine(last_valid.ln);
last_valid.pos = 0;
for(var i = 0; i < line.length; ++i) {
if (!isWhiteSpaceString(line[i])) {
last_valid.pos = i;
break;
}
}
return last_valid;
}

var curr_index = {
ln: cur.line,
pos: cur.ch,
};

while (repeat > 0) {
if (dir < 0) {
curr_index = reverse(cm, curr_index.ln, curr_index.pos, dir);
}
else {
curr_index = forward(cm, curr_index.ln, curr_index.pos, dir);
}
repeat--;
}

return Pos(curr_index.ln, curr_index.pos);
}

// TODO: perhaps this finagling of start and end positions belonds
// in codemirror/replaceRange?
function selectCompanionObject(cm, head, symb, inclusive) {
Expand Down
34 changes: 34 additions & 0 deletions test/vim_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,40 @@ testVim('{', function(cm, vim, helpers) {
helpers.doKeys('6', '{');
helpers.assertCursorAt(0, 0);
}, { value: 'a\n\nb\nc\n\nd' });
testVim('(', function(cm, vim, helpers) {
cm.setCursor(6, 23);
helpers.doKeys('(');
helpers.assertCursorAt(6, 14);
helpers.doKeys('2', '(');
helpers.assertCursorAt(5, 0);
helpers.doKeys('(');
helpers.assertCursorAt(4, 0);
helpers.doKeys('(');
helpers.assertCursorAt(3, 0);
helpers.doKeys('(');
helpers.assertCursorAt(2, 0);
helpers.doKeys('(');
helpers.assertCursorAt(0, 0);
helpers.doKeys('(');
helpers.assertCursorAt(0, 0);
}, { value: 'sentence1.\n\n\nsentence2\n\nsentence3. sentence4\n sentence5? sentence6!' });
testVim(')', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('2', ')');
helpers.assertCursorAt(3, 0);
helpers.doKeys(')');
helpers.assertCursorAt(4, 0);
helpers.doKeys(')');
helpers.assertCursorAt(5, 0);
helpers.doKeys(')');
helpers.assertCursorAt(5, 11);
helpers.doKeys(')');
helpers.assertCursorAt(6, 14);
helpers.doKeys(')');
helpers.assertCursorAt(6, 23);
helpers.doKeys(')');
helpers.assertCursorAt(6, 23);
}, { value: 'sentence1.\n\n\nsentence2\n\nsentence3. sentence4\n sentence5? sentence6!' });
testVim('paragraph_motions', function(cm, vim, helpers) {
cm.setCursor(10, 0);
helpers.doKeys('{');
Expand Down

0 comments on commit 61623ec

Please sign in to comment.