vim.js
785 lines
| 26.2 KiB
| application/javascript
|
JavascriptLexer
Matthias BUSSONNIER
|
r8053 | // Supported keybindings: | ||
// | ||||
// Cursor movement: | ||||
// h, j, k, l | ||||
// e, E, w, W, b, B | ||||
// Ctrl-f, Ctrl-b | ||||
// Ctrl-n, Ctrl-p | ||||
// $, ^, 0 | ||||
// G | ||||
// ge, gE | ||||
// gg | ||||
// f<char>, F<char>, t<char>, T<char> | ||||
// Ctrl-o, Ctrl-i TODO (FIXME - Ctrl-O wont work in Chrome) | ||||
// /, ?, n, N TODO (does not work) | ||||
// #, * TODO | ||||
// | ||||
// Entering insert mode: | ||||
// i, I, a, A, o, O | ||||
// s | ||||
// ce, cb (without support for number of actions like c3e - TODO) | ||||
// cc | ||||
// S, C TODO | ||||
// cf<char>, cF<char>, ct<char>, cT<char> | ||||
// | ||||
// Deleting text: | ||||
// x, X | ||||
// J | ||||
// dd, D | ||||
// de, db (without support for number of actions like d3e - TODO) | ||||
// df<char>, dF<char>, dt<char>, dT<char> | ||||
// | ||||
// Yanking and pasting: | ||||
// yy, Y | ||||
// p, P | ||||
// p'<char> TODO - test | ||||
// y'<char> TODO - test | ||||
// m<char> TODO - test | ||||
// | ||||
// Changing text in place: | ||||
// ~ | ||||
// r<char> | ||||
// | ||||
// Visual mode: | ||||
// v, V TODO | ||||
// | ||||
// Misc: | ||||
// . TODO | ||||
// | ||||
(function() { | ||||
var count = ""; | ||||
var sdir = "f"; | ||||
var buf = ""; | ||||
var yank = 0; | ||||
var mark = []; | ||||
var reptTimes = 0; | ||||
function emptyBuffer() { buf = ""; } | ||||
function pushInBuffer(str) { buf += str; } | ||||
function pushCountDigit(digit) { return function(cm) {count += digit;}; } | ||||
function popCount() { var i = parseInt(count, 10); count = ""; return i || 1; } | ||||
function iterTimes(func) { | ||||
for (var i = 0, c = popCount(); i < c; ++i) func(i, i == c - 1); | ||||
} | ||||
function countTimes(func) { | ||||
if (typeof func == "string") func = CodeMirror.commands[func]; | ||||
return function(cm) { iterTimes(function () { func(cm); }); }; | ||||
} | ||||
function iterObj(o, f) { | ||||
for (var prop in o) if (o.hasOwnProperty(prop)) f(prop, o[prop]); | ||||
} | ||||
function iterList(l, f) { | ||||
for (var i in l) f(l[i]); | ||||
} | ||||
function toLetter(ch) { | ||||
// T -> t, Shift-T -> T, '*' -> *, "Space" -> " " | ||||
if (ch.slice(0, 6) == "Shift-") { | ||||
return ch.slice(0, 1); | ||||
} else { | ||||
if (ch == "Space") return " "; | ||||
if (ch.length == 3 && ch[0] == "'" && ch[2] == "'") return ch[1]; | ||||
return ch.toLowerCase(); | ||||
} | ||||
} | ||||
var SPECIAL_SYMBOLS = "~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;\"\'1234567890"; | ||||
function toCombo(ch) { | ||||
// t -> T, T -> Shift-T, * -> '*', " " -> "Space" | ||||
if (ch == " ") return "Space"; | ||||
var specialIdx = SPECIAL_SYMBOLS.indexOf(ch); | ||||
if (specialIdx != -1) return "'" + ch + "'"; | ||||
if (ch.toLowerCase() == ch) return ch.toUpperCase(); | ||||
return "Shift-" + ch.toUpperCase(); | ||||
} | ||||
var word = [/\w/, /[^\w\s]/], bigWord = [/\S/]; | ||||
function findWord(line, pos, dir, regexps) { | ||||
var stop = 0, next = -1; | ||||
if (dir > 0) { stop = line.length; next = 0; } | ||||
var start = stop, end = stop; | ||||
// Find bounds of next one. | ||||
outer: for (; pos != stop; pos += dir) { | ||||
for (var i = 0; i < regexps.length; ++i) { | ||||
if (regexps[i].test(line.charAt(pos + next))) { | ||||
start = pos; | ||||
for (; pos != stop; pos += dir) { | ||||
if (!regexps[i].test(line.charAt(pos + next))) break; | ||||
} | ||||
end = pos; | ||||
break outer; | ||||
} | ||||
} | ||||
} | ||||
return {from: Math.min(start, end), to: Math.max(start, end)}; | ||||
} | ||||
function moveToWord(cm, regexps, dir, times, where) { | ||||
var cur = cm.getCursor(); | ||||
for (var i = 0; i < times; i++) { | ||||
var line = cm.getLine(cur.line), startCh = cur.ch, word; | ||||
while (true) { | ||||
// If we're at start/end of line, start on prev/next respectivly | ||||
if (cur.ch == line.length && dir > 0) { | ||||
cur.line++; | ||||
cur.ch = 0; | ||||
line = cm.getLine(cur.line); | ||||
} else if (cur.ch == 0 && dir < 0) { | ||||
cur.line--; | ||||
cur.ch = line.length; | ||||
line = cm.getLine(cur.line); | ||||
} | ||||
if (!line) break; | ||||
// On to the actual searching | ||||
word = findWord(line, cur.ch, dir, regexps); | ||||
cur.ch = word[where == "end" ? "to" : "from"]; | ||||
if (startCh == cur.ch && word.from != word.to) cur.ch = word[dir < 0 ? "from" : "to"]; | ||||
else break; | ||||
} | ||||
} | ||||
return cur; | ||||
} | ||||
function joinLineNext(cm) { | ||||
var cur = cm.getCursor(), ch = cur.ch, line = cm.getLine(cur.line); | ||||
CodeMirror.commands.goLineEnd(cm); | ||||
if (cur.line != cm.lineCount()) { | ||||
CodeMirror.commands.goLineEnd(cm); | ||||
cm.replaceSelection(" ", "end"); | ||||
CodeMirror.commands.delCharRight(cm); | ||||
} | ||||
} | ||||
function delTillMark(cm, cHar) { | ||||
var i = mark[cHar]; | ||||
if (i === undefined) { | ||||
// console.log("Mark not set"); // TODO - show in status bar | ||||
return; | ||||
} | ||||
var l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l; | ||||
cm.setCursor(start); | ||||
for (var c = start; c <= end; c++) { | ||||
pushInBuffer("\n"+cm.getLine(start)); | ||||
cm.removeLine(start); | ||||
} | ||||
} | ||||
function yankTillMark(cm, cHar) { | ||||
var i = mark[cHar]; | ||||
if (i === undefined) { | ||||
// console.log("Mark not set"); // TODO - show in status bar | ||||
return; | ||||
} | ||||
var l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l; | ||||
for (var c = start; c <= end; c++) { | ||||
pushInBuffer("\n"+cm.getLine(c)); | ||||
} | ||||
cm.setCursor(start); | ||||
} | ||||
function goLineStartText(cm) { | ||||
// Go to the start of the line where the text begins, or the end for whitespace-only lines | ||||
var cur = cm.getCursor(), firstNonWS = cm.getLine(cur.line).search(/\S/); | ||||
cm.setCursor(cur.line, firstNonWS == -1 ? line.length : firstNonWS, true); | ||||
} | ||||
function charIdxInLine(cm, cHar, motion_options) { | ||||
// Search for cHar in line. | ||||
// motion_options: {forward, inclusive} | ||||
// If inclusive = true, include it too. | ||||
// If forward = true, search forward, else search backwards. | ||||
// If char is not found on this line, do nothing | ||||
var cur = cm.getCursor(), line = cm.getLine(cur.line), idx; | ||||
var ch = toLetter(cHar), mo = motion_options; | ||||
if (mo.forward) { | ||||
idx = line.indexOf(ch, cur.ch + 1); | ||||
if (idx != -1 && mo.inclusive) idx += 1; | ||||
} else { | ||||
idx = line.lastIndexOf(ch, cur.ch); | ||||
if (idx != -1 && !mo.inclusive) idx += 1; | ||||
} | ||||
return idx; | ||||
} | ||||
function moveTillChar(cm, cHar, motion_options) { | ||||
// Move to cHar in line, as found by charIdxInLine. | ||||
var idx = charIdxInLine(cm, cHar, motion_options), cur = cm.getCursor(); | ||||
if (idx != -1) cm.setCursor({line: cur.line, ch: idx}); | ||||
} | ||||
function delTillChar(cm, cHar, motion_options) { | ||||
// delete text in this line, untill cHar is met, | ||||
// as found by charIdxInLine. | ||||
// If char is not found on this line, do nothing | ||||
var idx = charIdxInLine(cm, cHar, motion_options); | ||||
var cur = cm.getCursor(); | ||||
if (idx !== -1) { | ||||
if (motion_options.forward) { | ||||
cm.replaceRange("", {line: cur.line, ch: cur.ch}, {line: cur.line, ch: idx}); | ||||
} else { | ||||
cm.replaceRange("", {line: cur.line, ch: idx}, {line: cur.line, ch: cur.ch}); | ||||
} | ||||
} | ||||
} | ||||
function enterInsertMode(cm) { | ||||
// enter insert mode: switch mode and cursor | ||||
popCount(); | ||||
cm.setOption("keyMap", "vim-insert"); | ||||
} | ||||
function dialog(cm, text, shortText, f) { | ||||
if (cm.openDialog) cm.openDialog(text, f); | ||||
else f(prompt(shortText, "")); | ||||
} | ||||
function showAlert(cm, text) { | ||||
if (cm.openDialog) cm.openDialog(CodeMirror.htmlEscape(text) + " <button type=button>OK</button>"); | ||||
else alert(text); | ||||
} | ||||
// main keymap | ||||
var map = CodeMirror.keyMap.vim = { | ||||
// Pipe (|); TODO: should be *screen* chars, so need a util function to turn tabs into spaces? | ||||
"'|'": function(cm) { | ||||
cm.setCursor(cm.getCursor().line, popCount() - 1, true); | ||||
}, | ||||
"A": function(cm) { | ||||
cm.setCursor(cm.getCursor().line, cm.getCursor().ch+1, true); | ||||
enterInsertMode(cm); | ||||
}, | ||||
"Shift-A": function(cm) { CodeMirror.commands.goLineEnd(cm); enterInsertMode(cm);}, | ||||
"I": function(cm) { enterInsertMode(cm);}, | ||||
"Shift-I": function(cm) { goLineStartText(cm); enterInsertMode(cm);}, | ||||
"O": function(cm) { | ||||
CodeMirror.commands.goLineEnd(cm); | ||||
CodeMirror.commands.newlineAndIndent(cm); | ||||
enterInsertMode(cm); | ||||
}, | ||||
"Shift-O": function(cm) { | ||||
CodeMirror.commands.goLineStart(cm); | ||||
cm.replaceSelection("\n", "start"); | ||||
cm.indentLine(cm.getCursor().line); | ||||
enterInsertMode(cm); | ||||
}, | ||||
"G": function(cm) { cm.setOption("keyMap", "vim-prefix-g");}, | ||||
"Shift-D": function(cm) { | ||||
// commented out verions works, but I left original, cause maybe | ||||
// I don't know vim enouth to see what it does | ||||
/* var cur = cm.getCursor(); | ||||
var f = {line: cur.line, ch: cur.ch}, t = {line: cur.line}; | ||||
pushInBuffer(cm.getRange(f, t)); | ||||
*/ | ||||
emptyBuffer(); | ||||
mark["Shift-D"] = cm.getCursor(false).line; | ||||
cm.setCursor(cm.getCursor(true).line); | ||||
delTillMark(cm,"Shift-D"); mark = []; | ||||
}, | ||||
"S": function (cm) { | ||||
countTimes(function (_cm) { | ||||
CodeMirror.commands.delCharRight(_cm); | ||||
})(cm); | ||||
enterInsertMode(cm); | ||||
}, | ||||
"M": function(cm) {cm.setOption("keyMap", "vim-prefix-m"); mark = [];}, | ||||
"Y": function(cm) {cm.setOption("keyMap", "vim-prefix-y"); emptyBuffer(); yank = 0;}, | ||||
"Shift-Y": function(cm) { | ||||
emptyBuffer(); | ||||
mark["Shift-D"] = cm.getCursor(false).line; | ||||
cm.setCursor(cm.getCursor(true).line); | ||||
yankTillMark(cm,"Shift-D"); mark = []; | ||||
}, | ||||
"/": function(cm) {var f = CodeMirror.commands.find; f && f(cm); sdir = "f";}, | ||||
"'?'": function(cm) { | ||||
var f = CodeMirror.commands.find; | ||||
if (f) { f(cm); CodeMirror.commands.findPrev(cm); sdir = "r"; } | ||||
}, | ||||
"N": function(cm) { | ||||
var fn = CodeMirror.commands.findNext; | ||||
if (fn) sdir != "r" ? fn(cm) : CodeMirror.commands.findPrev(cm); | ||||
}, | ||||
"Shift-N": function(cm) { | ||||
var fn = CodeMirror.commands.findNext; | ||||
if (fn) sdir != "r" ? CodeMirror.commands.findPrev(cm) : fn.findNext(cm); | ||||
}, | ||||
"Shift-G": function(cm) { | ||||
count == "" ? cm.setCursor(cm.lineCount()) : cm.setCursor(parseInt(count, 10)-1); | ||||
popCount(); | ||||
CodeMirror.commands.goLineStart(cm); | ||||
}, | ||||
"':'": function(cm) { | ||||
var exModeDialog = ': <input type="text" style="width: 90%"/>'; | ||||
dialog(cm, exModeDialog, ':', function(command) { | ||||
if (command.match(/^\d+$/)) { | ||||
cm.setCursor(command - 1, cm.getCursor().ch); | ||||
} else { | ||||
showAlert(cm, "Bad command: " + command); | ||||
} | ||||
}); | ||||
}, | ||||
nofallthrough: true, style: "fat-cursor" | ||||
}; | ||||
// standard mode switching | ||||
iterList(["d", "t", "T", "f", "F", "c", "r"], | ||||
function (ch) { | ||||
CodeMirror.keyMap.vim[toCombo(ch)] = function (cm) { | ||||
cm.setOption("keyMap", "vim-prefix-" + ch); | ||||
emptyBuffer(); | ||||
}; | ||||
}); | ||||
function addCountBindings(keyMap) { | ||||
// Add bindings for number keys | ||||
keyMap["0"] = function(cm) { | ||||
count.length > 0 ? pushCountDigit("0")(cm) : CodeMirror.commands.goLineStart(cm); | ||||
}; | ||||
for (var i = 1; i < 10; ++i) keyMap[i] = pushCountDigit(i); | ||||
} | ||||
addCountBindings(CodeMirror.keyMap.vim); | ||||
// main num keymap | ||||
// Add bindings that are influenced by number keys | ||||
iterObj({ | ||||
"Left": "goColumnLeft", "Right": "goColumnRight", | ||||
"Down": "goLineDown", "Up": "goLineUp", "Backspace": "goCharLeft", | ||||
"Space": "goCharRight", | ||||
"X": function(cm) {CodeMirror.commands.delCharRight(cm);}, | ||||
"P": function(cm) { | ||||
var cur = cm.getCursor().line; | ||||
if (buf!= "") { | ||||
if (buf[0] == "\n") CodeMirror.commands.goLineEnd(cm); | ||||
cm.replaceRange(buf, cm.getCursor()); | ||||
} | ||||
}, | ||||
"Shift-X": function(cm) {CodeMirror.commands.delCharLeft(cm);}, | ||||
"Shift-J": function(cm) {joinLineNext(cm);}, | ||||
"Shift-P": function(cm) { | ||||
var cur = cm.getCursor().line; | ||||
if (buf!= "") { | ||||
CodeMirror.commands.goLineUp(cm); | ||||
CodeMirror.commands.goLineEnd(cm); | ||||
cm.replaceSelection(buf, "end"); | ||||
} | ||||
cm.setCursor(cur+1); | ||||
}, | ||||
"'~'": function(cm) { | ||||
var cur = cm.getCursor(), cHar = cm.getRange({line: cur.line, ch: cur.ch}, {line: cur.line, ch: cur.ch+1}); | ||||
cHar = cHar != cHar.toLowerCase() ? cHar.toLowerCase() : cHar.toUpperCase(); | ||||
cm.replaceRange(cHar, {line: cur.line, ch: cur.ch}, {line: cur.line, ch: cur.ch+1}); | ||||
cm.setCursor(cur.line, cur.ch+1); | ||||
}, | ||||
"Ctrl-B": function(cm) {CodeMirror.commands.goPageUp(cm);}, | ||||
"Ctrl-F": function(cm) {CodeMirror.commands.goPageDown(cm);}, | ||||
"Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", | ||||
"U": "undo", "Ctrl-R": "redo" | ||||
}, function(key, cmd) { map[key] = countTimes(cmd); }); | ||||
// empty key maps | ||||
iterList([ | ||||
"vim-prefix-d'", | ||||
"vim-prefix-y'", | ||||
"vim-prefix-df", | ||||
"vim-prefix-dF", | ||||
"vim-prefix-dt", | ||||
"vim-prefix-dT", | ||||
"vim-prefix-c", | ||||
"vim-prefix-cf", | ||||
"vim-prefix-cF", | ||||
"vim-prefix-ct", | ||||
"vim-prefix-cT", | ||||
"vim-prefix-", | ||||
"vim-prefix-f", | ||||
"vim-prefix-F", | ||||
"vim-prefix-t", | ||||
"vim-prefix-T", | ||||
"vim-prefix-r", | ||||
"vim-prefix-m" | ||||
], | ||||
function (prefix) { | ||||
CodeMirror.keyMap[prefix] = { | ||||
auto: "vim", | ||||
nofallthrough: true, | ||||
style: "fat-cursor" | ||||
}; | ||||
}); | ||||
CodeMirror.keyMap["vim-prefix-g"] = { | ||||
"E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, word, -1, 1, "start"));}), | ||||
"Shift-E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, bigWord, -1, 1, "start"));}), | ||||
"G": function (cm) { cm.setCursor({line: 0, ch: cm.getCursor().ch});}, | ||||
auto: "vim", nofallthrough: true, style: "fat-cursor" | ||||
}; | ||||
CodeMirror.keyMap["vim-prefix-d"] = { | ||||
"D": countTimes(function(cm) { | ||||
pushInBuffer("\n"+cm.getLine(cm.getCursor().line)); | ||||
cm.removeLine(cm.getCursor().line); | ||||
cm.setOption("keyMap", "vim"); | ||||
}), | ||||
"'": function(cm) { | ||||
cm.setOption("keyMap", "vim-prefix-d'"); | ||||
emptyBuffer(); | ||||
}, | ||||
"B": function(cm) { | ||||
var cur = cm.getCursor(); | ||||
var line = cm.getLine(cur.line); | ||||
var index = line.lastIndexOf(" ", cur.ch); | ||||
pushInBuffer(line.substring(index, cur.ch)); | ||||
cm.replaceRange("", {line: cur.line, ch: index}, cur); | ||||
cm.setOption("keyMap", "vim"); | ||||
}, | ||||
nofallthrough: true, style: "fat-cursor" | ||||
}; | ||||
// FIXME - does not work for bindings like "d3e" | ||||
addCountBindings(CodeMirror.keyMap["vim-prefix-d"]); | ||||
CodeMirror.keyMap["vim-prefix-c"] = { | ||||
"B": function (cm) { | ||||
countTimes("delWordLeft")(cm); | ||||
enterInsertMode(cm); | ||||
}, | ||||
"C": function (cm) { | ||||
iterTimes(function (i, last) { | ||||
CodeMirror.commands.deleteLine(cm); | ||||
if (i) { | ||||
CodeMirror.commands.delCharRight(cm); | ||||
if (last) CodeMirror.commands.deleteLine(cm); | ||||
} | ||||
}); | ||||
enterInsertMode(cm); | ||||
}, | ||||
nofallthrough: true, style: "fat-cursor" | ||||
}; | ||||
iterList(["vim-prefix-d", "vim-prefix-c", "vim-prefix-"], function (prefix) { | ||||
iterList(["f", "F", "T", "t"], | ||||
function (ch) { | ||||
CodeMirror.keyMap[prefix][toCombo(ch)] = function (cm) { | ||||
cm.setOption("keyMap", prefix + ch); | ||||
emptyBuffer(); | ||||
}; | ||||
}); | ||||
}); | ||||
var MOTION_OPTIONS = { | ||||
"t": {inclusive: false, forward: true}, | ||||
"f": {inclusive: true, forward: true}, | ||||
"T": {inclusive: false, forward: false}, | ||||
"F": {inclusive: true, forward: false} | ||||
}; | ||||
function setupPrefixBindingForKey(m) { | ||||
CodeMirror.keyMap["vim-prefix-m"][m] = function(cm) { | ||||
mark[m] = cm.getCursor().line; | ||||
}; | ||||
CodeMirror.keyMap["vim-prefix-d'"][m] = function(cm) { | ||||
delTillMark(cm,m); | ||||
}; | ||||
CodeMirror.keyMap["vim-prefix-y'"][m] = function(cm) { | ||||
yankTillMark(cm,m); | ||||
}; | ||||
CodeMirror.keyMap["vim-prefix-r"][m] = function (cm) { | ||||
var cur = cm.getCursor(); | ||||
cm.replaceRange(toLetter(m), | ||||
{line: cur.line, ch: cur.ch}, | ||||
{line: cur.line, ch: cur.ch + 1}); | ||||
CodeMirror.commands.goColumnLeft(cm); | ||||
}; | ||||
// all commands, related to motions till char in line | ||||
iterObj(MOTION_OPTIONS, function (ch, options) { | ||||
CodeMirror.keyMap["vim-prefix-" + ch][m] = function(cm) { | ||||
moveTillChar(cm, m, options); | ||||
}; | ||||
CodeMirror.keyMap["vim-prefix-d" + ch][m] = function(cm) { | ||||
delTillChar(cm, m, options); | ||||
}; | ||||
CodeMirror.keyMap["vim-prefix-c" + ch][m] = function(cm) { | ||||
delTillChar(cm, m, options); | ||||
enterInsertMode(cm); | ||||
}; | ||||
}); | ||||
} | ||||
for (var i = 65; i < 65 + 26; i++) { // uppercase alphabet char codes | ||||
var ch = String.fromCharCode(i); | ||||
setupPrefixBindingForKey(toCombo(ch)); | ||||
setupPrefixBindingForKey(toCombo(ch.toLowerCase())); | ||||
} | ||||
iterList(SPECIAL_SYMBOLS, function (ch) { | ||||
setupPrefixBindingForKey(toCombo(ch)); | ||||
}); | ||||
setupPrefixBindingForKey("Space"); | ||||
CodeMirror.keyMap["vim-prefix-y"] = { | ||||
"Y": countTimes(function(cm) { pushInBuffer("\n"+cm.getLine(cm.getCursor().line+yank)); yank++; }), | ||||
"'": function(cm) {cm.setOption("keyMap", "vim-prefix-y'"); emptyBuffer();}, | ||||
nofallthrough: true, style: "fat-cursor" | ||||
}; | ||||
CodeMirror.keyMap["vim-insert"] = { | ||||
// TODO: override navigation keys so that Esc will cancel automatic indentation from o, O, i_<CR> | ||||
"Esc": function(cm) { | ||||
cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1, true); | ||||
cm.setOption("keyMap", "vim"); | ||||
}, | ||||
"Ctrl-N": "autocomplete", | ||||
"Ctrl-P": "autocomplete", | ||||
fallthrough: ["default"] | ||||
}; | ||||
function findMatchedSymbol(cm, cur, symb) { | ||||
var line = cur.line; | ||||
var symb = symb ? symb : cm.getLine(line)[cur.ch]; | ||||
// Are we at the opening or closing char | ||||
var forwards = ['(', '[', '{'].indexOf(symb) != -1; | ||||
var reverseSymb = (function(sym) { | ||||
switch (sym) { | ||||
case '(' : return ')'; | ||||
case '[' : return ']'; | ||||
case '{' : return '}'; | ||||
case ')' : return '('; | ||||
case ']' : return '['; | ||||
case '}' : return '{'; | ||||
default : return null; | ||||
} | ||||
})(symb); | ||||
// Couldn't find a matching symbol, abort | ||||
if (reverseSymb == null) return cur; | ||||
// Tracking our imbalance in open/closing symbols. An opening symbol wii be | ||||
// the first thing we pick up if moving forward, this isn't true moving backwards | ||||
var disBal = forwards ? 0 : 1; | ||||
while (true) { | ||||
if (line == cur.line) { | ||||
// First pass, do some special stuff | ||||
var currLine = forwards ? cm.getLine(line).substr(cur.ch).split('') : cm.getLine(line).substr(0,cur.ch).split('').reverse(); | ||||
} else { | ||||
var currLine = forwards ? cm.getLine(line).split('') : cm.getLine(line).split('').reverse(); | ||||
} | ||||
for (var index = 0; index < currLine.length; index++) { | ||||
if (currLine[index] == symb) disBal++; | ||||
else if (currLine[index] == reverseSymb) disBal--; | ||||
if (disBal == 0) { | ||||
if (forwards && cur.line == line) return {line: line, ch: index + cur.ch}; | ||||
else if (forwards) return {line: line, ch: index}; | ||||
else return {line: line, ch: currLine.length - index - 1 }; | ||||
} | ||||
} | ||||
if (forwards) line++; | ||||
else line--; | ||||
} | ||||
} | ||||
function selectCompanionObject(cm, revSymb, inclusive) { | ||||
var cur = cm.getCursor(); | ||||
var end = findMatchedSymbol(cm, cur, revSymb); | ||||
var start = findMatchedSymbol(cm, end); | ||||
start.ch += inclusive ? 1 : 0; | ||||
end.ch += inclusive ? 0 : 1; | ||||
return {start: start, end: end}; | ||||
} | ||||
// These are our motion commands to be used for navigation and selection with | ||||
// certian other commands. All should return a cursor object. | ||||
var motionList = ['B', 'E', 'J', 'K', 'H', 'L', 'W', 'Shift-W', "'^'", "'$'", "'%'", 'Esc']; | ||||
motions = { | ||||
'B': function(cm, times) { return moveToWord(cm, word, -1, times); }, | ||||
'Shift-B': function(cm, times) { return moveToWord(cm, bigWord, -1, times); }, | ||||
'E': function(cm, times) { return moveToWord(cm, word, 1, times, 'end'); }, | ||||
'Shift-E': function(cm, times) { return moveToWord(cm, bigWord, 1, times, 'end'); }, | ||||
'J': function(cm, times) { | ||||
var cur = cm.getCursor(); | ||||
return {line: cur.line+times, ch : cur.ch}; | ||||
}, | ||||
'K': function(cm, times) { | ||||
var cur = cm.getCursor(); | ||||
return {line: cur.line-times, ch: cur.ch}; | ||||
}, | ||||
'H': function(cm, times) { | ||||
var cur = cm.getCursor(); | ||||
return {line: cur.line, ch: cur.ch-times}; | ||||
}, | ||||
'L': function(cm, times) { | ||||
var cur = cm.getCursor(); | ||||
return {line: cur.line, ch: cur.ch+times}; | ||||
}, | ||||
'W': function(cm, times) { return moveToWord(cm, word, 1, times); }, | ||||
'Shift-W': function(cm, times) { return moveToWord(cm, bigWord, 1, times); }, | ||||
"'^'": function(cm, times) { | ||||
var cur = cm.getCursor(); | ||||
var line = cm.getLine(cur.line).split(''); | ||||
// Empty line :o | ||||
if (line.length == 0) return cur; | ||||
for (var index = 0; index < line.length; index++) { | ||||
if (line[index].match(/[^\s]/)) return {line: cur.line, ch: index}; | ||||
} | ||||
}, | ||||
"'$'": function(cm) { | ||||
var cur = cm.getCursor(); | ||||
var line = cm.getLine(cur.line); | ||||
return {line: cur.line, ch: line.length}; | ||||
}, | ||||
"'%'": function(cm) { return findMatchedSymbol(cm, cm.getCursor()); }, | ||||
"Esc" : function(cm) { | ||||
cm.setOption('vim'); | ||||
reptTimes = 0; | ||||
return cm.getCursor(); | ||||
} | ||||
}; | ||||
// Map our movement actions each operator and non-operational movement | ||||
motionList.forEach(function(key, index, array) { | ||||
CodeMirror.keyMap['vim-prefix-d'][key] = function(cm) { | ||||
// Get our selected range | ||||
var start = cm.getCursor(); | ||||
var end = motions[key](cm, reptTimes ? reptTimes : 1); | ||||
// Set swap var if range is of negative length | ||||
if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true; | ||||
// Take action, switching start and end if swap var is set | ||||
pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end)); | ||||
cm.replaceRange("", swap ? end : start, swap ? start : end); | ||||
// And clean up | ||||
reptTimes = 0; | ||||
cm.setOption("keyMap", "vim"); | ||||
}; | ||||
CodeMirror.keyMap['vim-prefix-c'][key] = function(cm) { | ||||
var start = cm.getCursor(); | ||||
var end = motions[key](cm, reptTimes ? reptTimes : 1); | ||||
if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true; | ||||
pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end)); | ||||
cm.replaceRange("", swap ? end : start, swap ? start : end); | ||||
reptTimes = 0; | ||||
cm.setOption('keyMap', 'vim-insert'); | ||||
}; | ||||
CodeMirror.keyMap['vim-prefix-y'][key] = function(cm) { | ||||
var start = cm.getCursor(); | ||||
var end = motions[key](cm, reptTimes ? reptTimes : 1); | ||||
if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true; | ||||
pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end)); | ||||
reptTimes = 0; | ||||
cm.setOption("keyMap", "vim"); | ||||
}; | ||||
CodeMirror.keyMap['vim'][key] = function(cm) { | ||||
var cur = motions[key](cm, reptTimes ? reptTimes : 1); | ||||
cm.setCursor(cur.line, cur.ch); | ||||
reptTimes = 0; | ||||
}; | ||||
}); | ||||
var nums = [1,2,3,4,5,6,7,8,9]; | ||||
nums.forEach(function(key, index, array) { | ||||
CodeMirror.keyMap['vim'][key] = function (cm) { | ||||
reptTimes = (reptTimes * 10) + key; | ||||
}; | ||||
CodeMirror.keyMap['vim-prefix-d'][key] = function (cm) { | ||||
reptTimes = (reptTimes * 10) + key; | ||||
}; | ||||
CodeMirror.keyMap['vim-prefix-y'][key] = function (cm) { | ||||
reptTimes = (reptTimes * 10) + key; | ||||
}; | ||||
CodeMirror.keyMap['vim-prefix-c'][key] = function (cm) { | ||||
reptTimes = (reptTimes * 10) + key; | ||||
}; | ||||
}); | ||||
// Create our keymaps for each operator and make xa and xi where x is an operator | ||||
// change to the corrosponding keymap | ||||
var operators = ['d', 'y', 'c']; | ||||
operators.forEach(function(key, index, array) { | ||||
CodeMirror.keyMap['vim-prefix-'+key+'a'] = { | ||||
auto: 'vim', nofallthrough: true, style: "fat-cursor" | ||||
}; | ||||
CodeMirror.keyMap['vim-prefix-'+key+'i'] = { | ||||
auto: 'vim', nofallthrough: true, style: "fat-cursor" | ||||
}; | ||||
CodeMirror.keyMap['vim-prefix-'+key]['A'] = function(cm) { | ||||
reptTimes = 0; | ||||
cm.setOption('keyMap', 'vim-prefix-' + key + 'a'); | ||||
}; | ||||
CodeMirror.keyMap['vim-prefix-'+key]['I'] = function(cm) { | ||||
reptTimes = 0; | ||||
cm.setOption('keyMap', 'vim-prefix-' + key + 'i'); | ||||
}; | ||||
}); | ||||
function regexLastIndexOf(string, pattern, startIndex) { | ||||
for (var i = startIndex == null ? string.length : startIndex; i >= 0; --i) | ||||
if (pattern.test(string.charAt(i))) return i; | ||||
return -1; | ||||
} | ||||
// Create our text object functions. They work similar to motions but they | ||||
// return a start cursor as well | ||||
var textObjectList = ['W', 'Shift-[', 'Shift-9', '[']; | ||||
var textObjects = { | ||||
'W': function(cm, inclusive) { | ||||
var cur = cm.getCursor(); | ||||
var line = cm.getLine(cur.line); | ||||
var line_to_char = new String(line.substring(0, cur.ch)); | ||||
var start = regexLastIndexOf(line_to_char, /[^a-zA-Z0-9]/) + 1; | ||||
var end = motions["E"](cm, 1) ; | ||||
end.ch += inclusive ? 1 : 0 ; | ||||
return {start: {line: cur.line, ch: start}, end: end }; | ||||
}, | ||||
'Shift-[': function(cm, inclusive) { return selectCompanionObject(cm, '}', inclusive); }, | ||||
'Shift-9': function(cm, inclusive) { return selectCompanionObject(cm, ')', inclusive); }, | ||||
'[': function(cm, inclusive) { return selectCompanionObject(cm, ']', inclusive); } | ||||
}; | ||||
// One function to handle all operation upon text objects. Kinda funky but it works | ||||
// better than rewriting this code six times | ||||
function textObjectManipulation(cm, object, remove, insert, inclusive) { | ||||
// Object is the text object, delete object if remove is true, enter insert | ||||
// mode if insert is true, inclusive is the difference between a and i | ||||
var tmp = textObjects[object](cm, inclusive); | ||||
var start = tmp.start; | ||||
var end = tmp.end; | ||||
if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true ; | ||||
pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end)); | ||||
if (remove) cm.replaceRange("", swap ? end : start, swap ? start : end); | ||||
if (insert) cm.setOption('keyMap', 'vim-insert'); | ||||
} | ||||
// And finally build the keymaps up from the text objects | ||||
for (var i = 0; i < textObjectList.length; ++i) { | ||||
var object = textObjectList[i]; | ||||
(function(object) { | ||||
CodeMirror.keyMap['vim-prefix-di'][object] = function(cm) { textObjectManipulation(cm, object, true, false, false); }; | ||||
CodeMirror.keyMap['vim-prefix-da'][object] = function(cm) { textObjectManipulation(cm, object, true, false, true); }; | ||||
CodeMirror.keyMap['vim-prefix-yi'][object] = function(cm) { textObjectManipulation(cm, object, false, false, false); }; | ||||
CodeMirror.keyMap['vim-prefix-ya'][object] = function(cm) { textObjectManipulation(cm, object, false, false, true); }; | ||||
CodeMirror.keyMap['vim-prefix-ci'][object] = function(cm) { textObjectManipulation(cm, object, true, true, false); }; | ||||
CodeMirror.keyMap['vim-prefix-ca'][object] = function(cm) { textObjectManipulation(cm, object, true, true, true); }; | ||||
})(object) | ||||
} | ||||
})(); | ||||