Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
nightwing
ef53363bae update vim mode 2015-03-05 23:49:44 +04:00
2 changed files with 140 additions and 73 deletions

View file

@ -1135,9 +1135,11 @@ dom.importCssString(".normal-mode .ace_cursor{\
}
var numberRegex = /[\d]/;
var wordRegexp = [{test: CodeMirror.isWordChar}, {test: function(ch) {
return !CodeMirror.isWordChar(ch) && !/\s/.test(ch);
}}], bigWordRegexp = [(/\S/)];
var wordCharTest = [CodeMirror.isWordChar, function(ch) {
return ch && !CodeMirror.isWordChar(ch) && !/\s/.test(ch);
}], bigWordCharTest = [function(ch) {
return /\S/.test(ch);
}];
function makeKeyRange(start, size) {
var keys = [];
for (var i = start; i < start + size; i++) {
@ -1417,6 +1419,8 @@ dom.importCssString(".normal-mode .ace_cursor{\
// Testing hook.
maybeInitVimState_: maybeInitVimState,
suppressErrorLogging: false,
InsertModeKey: InsertModeKey,
map: function(lhs, rhs, ctx) {
// Add user defined key bindings.
@ -1567,7 +1571,9 @@ dom.importCssString(".normal-mode .ace_cursor{\
// clear VIM state in case it's in a bad state.
cm.state.vim = undefined;
maybeInitVimState(cm);
console['log'](e);
if (!CodeMirror.Vim.suppressErrorLogging) {
console['log'](e);
}
throw e;
}
return true;
@ -1577,7 +1583,16 @@ dom.importCssString(".normal-mode .ace_cursor{\
},
handleEx: function(cm, input) {
exCommandDispatcher.processCommand(cm, input);
}
},
defineMotion: defineMotion,
defineAction: defineAction,
defineOperator: defineOperator,
mapCommand: mapCommand,
_mapCommand: _mapCommand,
exitVisualMode: exitVisualMode,
exitInsertMode: exitInsertMode
};
// Represents the current input state.
@ -1827,12 +1842,10 @@ dom.importCssString(".normal-mode .ace_cursor{\
break;
case 'search':
this.processSearch(cm, vim, command);
clearInputState(cm);
break;
case 'ex':
case 'keyToEx':
this.processEx(cm, vim, command);
clearInputState(cm);
break;
default:
break;
@ -1925,6 +1938,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
updateSearchQuery(cm, query, ignoreCase, smartCase);
} catch (e) {
showConfirm(cm, 'Invalid regex: ' + query);
clearInputState(cm);
return;
}
commandDispatcher.processMotion(cm, vim, {
@ -1974,6 +1988,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
clearSearchHighlight(cm);
cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
CodeMirror.e_stop(e);
clearInputState(cm);
close();
cm.focus();
}
@ -2040,6 +2055,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
vimGlobalState.exCommandHistoryController.pushInput(input);
vimGlobalState.exCommandHistoryController.reset();
CodeMirror.e_stop(e);
clearInputState(cm);
close();
cm.focus();
}
@ -2250,7 +2266,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
var operatorMoveTo = operators[operator](
cm, operatorArgs, cmSel.ranges, oldAnchor, newHead);
if (vim.visualMode) {
exitVisualMode(cm);
exitVisualMode(cm, operatorMoveTo != null);
}
if (operatorMoveTo) {
cm.setCursor(operatorMoveTo);
@ -2627,6 +2643,10 @@ dom.importCssString(".normal-mode .ace_cursor{\
}
};
function defineMotion(name, fn) {
motions[name] = fn;
}
function fillArray(val, times) {
var arr = [];
for (var i = 0; i < times; i++) {
@ -2709,7 +2729,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
vimGlobalState.registerController.pushText(
args.registerName, 'delete', text,
args.linewise, vim.visualBlock);
return finalHead;
return clipCursorToContent(cm, finalHead);
},
indent: function(cm, args, ranges) {
var vim = cm.state.vim;
@ -2777,6 +2797,10 @@ dom.importCssString(".normal-mode .ace_cursor{\
}
};
function defineOperator(name, fn) {
operators[name] = fn;
}
var actions = {
jumpListWalk: function(cm, actionArgs, vim) {
if (vim.visualMode) {
@ -2993,6 +3017,11 @@ dom.importCssString(".normal-mode .ace_cursor{\
if (vim.visualMode) {
curStart = cm.getCursor('anchor');
curEnd = cm.getCursor('head');
if (cursorIsBefore(curEnd, curStart)) {
var tmp = curEnd;
curEnd = curStart;
curStart = tmp;
}
curEnd.ch = lineLength(cm, curEnd.line) - 1;
} else {
// Repeat is the number of lines to join. Minimum 2 lines.
@ -3011,10 +3040,10 @@ dom.importCssString(".normal-mode .ace_cursor{\
cm.replaceRange(text, curStart, tmp);
}
var curFinalPos = Pos(curStart.line, finalCh);
cm.setCursor(curFinalPos);
if (vim.visualMode) {
exitVisualMode(cm);
exitVisualMode(cm, false);
}
cm.setCursor(curFinalPos);
},
newLineAndEnterInsertMode: function(cm, actionArgs, vim) {
vim.insertMode = true;
@ -3177,7 +3206,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
}
}
if (vim.visualMode) {
exitVisualMode(cm);
exitVisualMode(cm, false);
}
cm.setCursor(curPosFinal);
},
@ -3235,7 +3264,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
curStart = cursorIsBefore(selections[0].anchor, selections[0].head) ?
selections[0].anchor : selections[0].head;
cm.setCursor(curStart);
exitVisualMode(cm);
exitVisualMode(cm, false);
} else {
cm.setCursor(offsetCursor(curEnd, 0, -1));
}
@ -3283,6 +3312,10 @@ dom.importCssString(".normal-mode .ace_cursor{\
exitInsertMode: exitInsertMode
};
function defineAction(name, fn) {
actions[name] = fn;
}
/*
* Below are miscellaneous utility functions used by vim.js
*/
@ -3412,9 +3445,6 @@ dom.importCssString(".normal-mode .ace_cursor{\
function lineLength(cm, lineNum) {
return cm.getLine(lineNum).length;
}
function reverse(s){
return s.split('').reverse().join('');
}
function trim(s) {
if (s.trim) {
return s.trim();
@ -3728,59 +3758,38 @@ dom.importCssString(".normal-mode .ace_cursor{\
// Seek to first word or non-whitespace character, depending on if
// noSymbol is true.
var textAfterIdx = line.substring(idx);
var firstMatchedChar;
if (noSymbol) {
firstMatchedChar = textAfterIdx.search(/\w/);
} else {
firstMatchedChar = textAfterIdx.search(/\S/);
var test = noSymbol ? wordCharTest[0] : bigWordCharTest [0];
while (!test(line.charAt(idx))) {
idx++;
if (idx >= line.length) { return null; }
}
if (firstMatchedChar == -1) {
return null;
}
idx += firstMatchedChar;
textAfterIdx = line.substring(idx);
var textBeforeIdx = line.substring(0, idx);
var matchRegex;
// Greedy matchers for the "word" we are trying to expand.
if (bigWord) {
matchRegex = /^\S+/;
test = bigWordCharTest[0];
} else {
if ((/\w/).test(line.charAt(idx))) {
matchRegex = /^\w+/;
} else {
matchRegex = /^[^\w\s]+/;
test = wordCharTest[0];
if (!test(line.charAt(idx))) {
test = wordCharTest[1];
}
}
var wordAfterRegex = matchRegex.exec(textAfterIdx);
var wordStart = idx;
var wordEnd = idx + wordAfterRegex[0].length;
// TODO: Find a better way to do this. It will be slow on very long lines.
var revTextBeforeIdx = reverse(textBeforeIdx);
var wordBeforeRegex = matchRegex.exec(revTextBeforeIdx);
if (wordBeforeRegex) {
wordStart -= wordBeforeRegex[0].length;
}
var end = idx, start = idx;
while (test(line.charAt(end)) && end < line.length) { end++; }
while (test(line.charAt(start)) && start >= 0) { start--; }
start++;
if (inclusive) {
// If present, trim all whitespace after word.
// Otherwise, trim all whitespace before word.
var textAfterWordEnd = line.substring(wordEnd);
var whitespacesAfterWord = textAfterWordEnd.match(/^\s*/)[0].length;
if (whitespacesAfterWord > 0) {
wordEnd += whitespacesAfterWord;
} else {
var revTrim = revTextBeforeIdx.length - wordStart;
var textBeforeWordStart = revTextBeforeIdx.substring(revTrim);
var whitespacesBeforeWord = textBeforeWordStart.match(/^\s*/)[0].length;
wordStart -= whitespacesBeforeWord;
// If present, include all whitespace after word.
// Otherwise, include all whitespace before word, except indentation.
var wordEnd = end;
while (/\s/.test(line.charAt(end)) && end < line.length) { end++; }
if (wordEnd == end) {
var wordStart = start;
while (/\s/.test(line.charAt(start - 1)) && start > 0) { start--; }
if (!start) { start = wordStart; }
}
}
return { start: Pos(cur.line, wordStart),
end: Pos(cur.line, wordEnd) };
return { start: Pos(cur.line, start), end: Pos(cur.line, end) };
}
function recordJumpPosition(cm, oldCur, newCur) {
@ -3938,7 +3947,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
var pos = cur.ch;
var line = cm.getLine(lineNum);
var dir = forward ? 1 : -1;
var regexps = bigWord ? bigWordRegexp : wordRegexp;
var charTests = bigWord ? bigWordCharTest: wordCharTest;
if (emptyLineIsWord && line == '') {
lineNum += dir;
@ -3958,11 +3967,11 @@ dom.importCssString(".normal-mode .ace_cursor{\
// Find bounds of next word.
while (pos != stop) {
var foundWord = false;
for (var i = 0; i < regexps.length && !foundWord; ++i) {
if (regexps[i].test(line.charAt(pos))) {
for (var i = 0; i < charTests.length && !foundWord; ++i) {
if (charTests[i](line.charAt(pos))) {
wordStart = pos;
// Advance to end of word.
while (pos != stop && regexps[i].test(line.charAt(pos))) {
while (pos != stop && charTests[i](line.charAt(pos))) {
pos += dir;
}
wordEnd = pos;
@ -4279,6 +4288,12 @@ dom.importCssString(".normal-mode .ace_cursor{\
},
setReversed: function(reversed) {
vimGlobalState.isReversed = reversed;
},
getScrollbarAnnotate: function() {
return this.annotate;
},
setScrollbarAnnotate: function(annotate) {
this.annotate = annotate;
}
};
function getSearchState(cm) {
@ -4557,14 +4572,21 @@ dom.importCssString(".normal-mode .ace_cursor{\
};
}
function highlightSearchMatches(cm, query) {
var overlay = getSearchState(cm).getOverlay();
var searchState = getSearchState(cm);
var overlay = searchState.getOverlay();
if (!overlay || query != overlay.query) {
if (overlay) {
cm.removeOverlay(overlay);
}
overlay = searchOverlay(query);
cm.addOverlay(overlay);
getSearchState(cm).setOverlay(overlay);
if (cm.showMatchesOnScrollbar) {
if (searchState.getScrollbarAnnotate()) {
searchState.getScrollbarAnnotate().clear();
}
searchState.setScrollbarAnnotate(cm.showMatchesOnScrollbar(query));
}
searchState.setOverlay(overlay);
}
}
function findNext(cm, prev, query, repeat) {
@ -4589,8 +4611,13 @@ dom.importCssString(".normal-mode .ace_cursor{\
});
}
function clearSearchHighlight(cm) {
var state = getSearchState(cm);
cm.removeOverlay(getSearchState(cm).getOverlay());
getSearchState(cm).setOverlay(null);
state.setOverlay(null);
if (state.getScrollbarAnnotate()) {
state.getScrollbarAnnotate().clear();
state.setScrollbarAnnotate(null);
}
}
/**
* Check if pos is in the specified range, INCLUSIVE.
@ -5417,6 +5444,19 @@ dom.importCssString(".normal-mode .ace_cursor{\
}
}
function _mapCommand(command) {
defaultKeymap.push(command);
}
function mapCommand(keys, type, name, args, extra) {
var command = {keys: keys, type: type};
command[type] = name;
command[type + "Args"] = args;
for (var key in extra)
command[key] = extra[key];
_mapCommand(command);
}
// The timeout in milliseconds for the two-character ESC keymap should be
// adjusted according to your typing speed to prevent false positives.
defineOption('insertModeEscKeysTimeout', 200, 'number');
@ -5597,6 +5637,7 @@ dom.importCssString(".normal-mode .ace_cursor{\
var macroModeState = vimGlobalState.macroModeState;
var lastChange = macroModeState.lastInsertModeChanges;
var keyName = CodeMirror.keyName(e);
if (!keyName) { return; }
function onKeyFound() {
lastChange.changes.push(new InsertModeKey(keyName));
return true;

View file

@ -73,6 +73,9 @@ function test(name, fn) {
}
vim.CodeMirror.Vim.unmap("Y");
vim.CodeMirror.Vim.defineEx('write', 'w', function(cm) {
CodeMirror.commands.save(cm);
});
@ -685,7 +688,7 @@ testVim('dl_eol', function(cm, vim, helpers) {
var register = helpers.getRegisterController().getRegister();
eq(' ', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 6);
helpers.assertCursorAt(0, 5);
}, { value: ' word1 ' });
testVim('dl_repeat', function(cm, vim, helpers) {
var curStart = makeCursor(0, 0);
@ -767,6 +770,16 @@ testVim('dw_word', function(cm, vim, helpers) {
is(!register.linewise);
eqPos(curStart, cm.getCursor());
}, { value: ' word1 word2' });
testVim('dw_unicode_word', function(cm, vim, helpers) {
helpers.doKeys('d', 'w');
eq(cm.getValue().length, 10);
helpers.doKeys('d', 'w');
eq(cm.getValue().length, 6);
helpers.doKeys('d', 'w');
eq(cm.getValue().length, 5);
helpers.doKeys('d', 'e');
eq(cm.getValue().length, 2);
}, { value: ' \u0562\u0561\u0580\u0587\xbbe\xb5g ' });
testVim('dw_only_word', function(cm, vim, helpers) {
// Test that if there is only 1 word left, dw deletes till the end of the
// line.
@ -776,7 +789,7 @@ testVim('dw_only_word', function(cm, vim, helpers) {
var register = helpers.getRegisterController().getRegister();
eq('word1 ', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 1);
helpers.assertCursorAt(0, 0);
}, { value: ' word1 ' });
testVim('dw_eol', function(cm, vim, helpers) {
// Assert that dw does not delete the newline if last word to delete is at end
@ -787,7 +800,7 @@ testVim('dw_eol', function(cm, vim, helpers) {
var register = helpers.getRegisterController().getRegister();
eq('word1', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 1);
helpers.assertCursorAt(0, 0);
}, { value: ' word1\nword2' });
testVim('dw_eol_with_multiple_newlines', function(cm, vim, helpers) {
// Assert that dw does not delete the newline if last word to delete is at end
@ -798,7 +811,7 @@ testVim('dw_eol_with_multiple_newlines', function(cm, vim, helpers) {
var register = helpers.getRegisterController().getRegister();
eq('word1', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 1);
helpers.assertCursorAt(0, 0);
}, { value: ' word1\n\nword2' });
testVim('dw_empty_line_followed_by_whitespace', function(cm, vim, helpers) {
cm.setCursor(0, 0);
@ -844,7 +857,7 @@ testVim('dw_repeat', function(cm, vim, helpers) {
var register = helpers.getRegisterController().getRegister();
eq('word1\nword2', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 1);
helpers.assertCursorAt(0, 0);
}, { value: ' word1\nword2' });
testVim('de_word_start_and_empty_lines', function(cm, vim, helpers) {
cm.setCursor(0, 0);
@ -1371,7 +1384,7 @@ testVim('D', function(cm, vim, helpers) {
var register = helpers.getRegisterController().getRegister();
eq('rd1', register.toString());
is(!register.linewise);
helpers.assertCursorAt(0, 3);
helpers.assertCursorAt(0, 2);
}, { value: ' word1\nword2\n word3' });
testVim('C', function(cm, vim, helpers) {
var curStart = makeCursor(0, 3);
@ -1962,7 +1975,6 @@ testVim('visual_block_move_to_eol', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', 'G', '$');
var selections = cm.getSelections().join();
console.log(selections);
eq("123,45,6", selections);
}, {value: '123\n45\n6'});
testVim('visual_block_different_line_lengths', function(cm, vim, helpers) {
@ -2053,6 +2065,11 @@ testVim('visual_join', function(cm, vim, helpers) {
eq(' 1 2 3\n 4\n 5', cm.getValue());
is(!vim.visualMode);
}, { value: ' 1\n 2\n 3\n 4\n 5' });
testVim('visual_join_2', function(cm, vim, helpers) {
helpers.doKeys('G', 'V', 'g', 'g', 'J');
eq('1 2 3 4 5 6 ', cm.getValue());
is(!vim.visualMode);
}, { value: '1\n2\n3\n4\n5\n6\n'});
testVim('visual_blank', function(cm, vim, helpers) {
helpers.doKeys('v', 'k');
eq(vim.visualMode, true);
@ -2260,6 +2277,15 @@ testVim('S_visual', function(cm, vim, helpers) {
eq('\ncc', cm.getValue());
}, { value: 'aa\nbb\ncc'});
testVim('d_/', function(cm, vim, helpers) {
cm.openDialog = helpers.fakeOpenDialog('match');
helpers.doKeys('2', 'd', '/');
helpers.assertCursorAt(0, 0);
eq('match \n next', cm.getValue());
cm.openDialog = helpers.fakeOpenDialog('2');
helpers.doKeys('d', ':');
// TODO eq(' next', cm.getValue());
}, { value: 'text match match \n next' });
testVim('/ and n/N', function(cm, vim, helpers) {
cm.openDialog = helpers.fakeOpenDialog('match');
helpers.doKeys('/');
@ -3204,7 +3230,7 @@ testVim('scrollMotion', function(cm, vim, helpers){
cm.refresh(); //ace!
prevScrollInfo = cm.getScrollInfo();
helpers.doKeys('<C-y>');
eq(prevCursor.line - 1, cm.getCursor().line);
eq(prevCursor.line - 1, cm.getCursor().line, "Y");
is(prevScrollInfo.top > cm.getScrollInfo().top);
}, { value: scrollMotionSandbox});