From 0e04fff0ea80fa5cbe96b8354db38bd334aea83a Mon Sep 17 00:00:00 2001 From: Ananya Sen Date: Mon, 16 Jul 2012 16:04:05 -0700 Subject: upgrade to codemirror 2.3 Signed-off-by: Ananya Sen Conflicts: js/code-editor/codemirror-ninja/theme/lesser-dark-ninja.css Signed-off-by: Ananya Sen --- imports/codemirror/LICENSE | 2 +- imports/codemirror/keymap/emacs.js | 4 +- imports/codemirror/keymap/vim.js | 927 +++++++++++++++------ imports/codemirror/lib/codemirror.css | 64 +- imports/codemirror/lib/codemirror.js | 619 +++++++++----- imports/codemirror/lib/util/closetag.js | 28 - imports/codemirror/lib/util/foldcode.js | 36 +- imports/codemirror/lib/util/formatting.js | 9 +- imports/codemirror/lib/util/loadmode.js | 51 ++ imports/codemirror/lib/util/match-highlighter.js | 2 +- imports/codemirror/lib/util/multiplex.js | 72 ++ imports/codemirror/lib/util/overlay.js | 5 +- imports/codemirror/lib/util/pig-hint.js | 123 +++ imports/codemirror/lib/util/search.js | 32 +- imports/codemirror/lib/util/searchcursor.js | 2 +- imports/codemirror/mode/clike/clike.js | 39 +- imports/codemirror/mode/clike/scala.html | 765 +++++++++++++++++ .../codemirror/mode/coffeescript/coffeescript.js | 6 + imports/codemirror/mode/coffeescript/index.html | 6 + imports/codemirror/mode/diff/diff.css | 3 - imports/codemirror/mode/diff/diff.js | 29 +- imports/codemirror/mode/diff/index.html | 13 +- imports/codemirror/mode/ecl/ecl.js | 2 +- imports/codemirror/mode/erlang/erlang.js | 251 ++++++ imports/codemirror/mode/erlang/index.html | 61 ++ imports/codemirror/mode/gfm/gfm.js | 42 +- .../codemirror/mode/htmlembedded/htmlembedded.js | 2 +- imports/codemirror/mode/htmlmixed/htmlmixed.js | 4 +- imports/codemirror/mode/javascript/javascript.js | 9 +- imports/codemirror/mode/less/index.html | 25 +- imports/codemirror/mode/less/less.js | 42 +- imports/codemirror/mode/markdown/markdown.js | 2 +- imports/codemirror/mode/php/php.js | 2 +- imports/codemirror/mode/pig/index.html | 42 + imports/codemirror/mode/pig/pig.js | 172 ++++ imports/codemirror/mode/python/python.js | 30 +- imports/codemirror/mode/rst/rst.js | 2 +- imports/codemirror/mode/ruby/ruby.js | 11 +- imports/codemirror/mode/scheme/scheme.js | 120 ++- imports/codemirror/mode/shell/index.html | 50 ++ imports/codemirror/mode/shell/shell.js | 103 +++ imports/codemirror/mode/stex/stex.js | 10 +- imports/codemirror/mode/stex/test.html | 12 + imports/codemirror/mode/tiddlywiki/tiddlywiki.css | 35 +- imports/codemirror/mode/tiddlywiki/tiddlywiki.js | 28 +- imports/codemirror/mode/tiki/index.html | 82 ++ imports/codemirror/mode/tiki/tiki.css | 26 + imports/codemirror/mode/tiki/tiki.js | 316 +++++++ imports/codemirror/mode/xml/xml.js | 69 +- imports/codemirror/mode/xmlpure/index.html | 58 -- imports/codemirror/mode/xmlpure/xmlpure.js | 490 ----------- imports/codemirror/theme/ambiance.css | 81 ++ imports/codemirror/theme/blackboard.css | 25 + imports/codemirror/theme/erlang-dark.css | 21 + imports/codemirror/theme/lesser-dark.css | 7 +- imports/codemirror/theme/night.css | 2 +- imports/codemirror/theme/vibrant-ink.css | 27 + imports/codemirror/version.txt | 2 +- js/code-editor/code-editor-wrapper.js | 2 + 59 files changed, 3860 insertions(+), 1242 deletions(-) create mode 100644 imports/codemirror/lib/util/loadmode.js create mode 100644 imports/codemirror/lib/util/multiplex.js create mode 100644 imports/codemirror/lib/util/pig-hint.js create mode 100644 imports/codemirror/mode/clike/scala.html delete mode 100644 imports/codemirror/mode/diff/diff.css create mode 100644 imports/codemirror/mode/erlang/erlang.js create mode 100644 imports/codemirror/mode/erlang/index.html create mode 100644 imports/codemirror/mode/pig/index.html create mode 100644 imports/codemirror/mode/pig/pig.js create mode 100644 imports/codemirror/mode/shell/index.html create mode 100644 imports/codemirror/mode/shell/shell.js create mode 100644 imports/codemirror/mode/tiki/index.html create mode 100644 imports/codemirror/mode/tiki/tiki.css create mode 100644 imports/codemirror/mode/tiki/tiki.js delete mode 100644 imports/codemirror/mode/xmlpure/index.html delete mode 100644 imports/codemirror/mode/xmlpure/xmlpure.js create mode 100644 imports/codemirror/theme/ambiance.css create mode 100644 imports/codemirror/theme/blackboard.css create mode 100644 imports/codemirror/theme/erlang-dark.css create mode 100644 imports/codemirror/theme/vibrant-ink.css diff --git a/imports/codemirror/LICENSE b/imports/codemirror/LICENSE index f62410e6..3916e96b 100644 --- a/imports/codemirror/LICENSE +++ b/imports/codemirror/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2011 by Marijn Haverbeke +Copyright (C) 2012 by Marijn Haverbeke Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/imports/codemirror/keymap/emacs.js b/imports/codemirror/keymap/emacs.js index 8fd3564e..2a57e2ff 100644 --- a/imports/codemirror/keymap/emacs.js +++ b/imports/codemirror/keymap/emacs.js @@ -18,12 +18,12 @@ "Alt-Y": function(cm) {cm.replaceSelection(popFromRing());}, "Ctrl-/": "undo", "Shift-Ctrl--": "undo", "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd", "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": "clearSearch", "Shift-Alt-5": "replace", - "Ctrl-Z": "undo", "Cmd-Z": "undo", + "Ctrl-Z": "undo", "Cmd-Z": "undo", "Alt-/": "autocomplete", fallthrough: ["basic", "emacsy"] }; CodeMirror.keyMap["emacs-Ctrl-X"] = { "Ctrl-S": "save", "Ctrl-W": "save", "S": "saveAll", "F": "open", "U": "undo", "K": "close", - auto: "emacs", catchall: function(cm) {/*ignore*/} + auto: "emacs", nofallthrough: true }; })(); diff --git a/imports/codemirror/keymap/vim.js b/imports/codemirror/keymap/vim.js index f8fa5e07..2aa6e0f7 100644 --- a/imports/codemirror/keymap/vim.js +++ b/imports/codemirror/keymap/vim.js @@ -1,21 +1,96 @@ +// 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, F, t, T +// 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, cF, ct, cT +// +// Deleting text: +// x, X +// J +// dd, D +// de, db (without support for number of actions like d3e - TODO) +// df, dF, dt, dT +// +// Yanking and pasting: +// yy, Y +// p, P +// p' TODO - test +// y' TODO - test +// m TODO - test +// +// Changing text in place: +// ~ +// r +// +// 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); count = ""; return i || 1; } + 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) { for (var i = 0, c = popCount(); i < c; ++i) func(cm); } + 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) { @@ -37,62 +112,172 @@ } return {from: Math.min(start, end), to: Math.max(start, end)}; } - function moveToWord(cm, regexps, dir, where) { - var cur = cm.getCursor(), ch = cur.ch, line = cm.getLine(cur.line), word; - while (true) { - word = findWord(line, ch, dir, regexps); - ch = word[where == "end" ? "to" : "from"]; - if (ch == cur.ch && word.from != word.to) ch = word[dir < 0 ? "from" : "to"]; - else break; + 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; + } } - cm.setCursor(cur.line, word[where == "end" ? "to" : "from"], true); + return cur; } function joinLineNext(cm) { var cur = cm.getCursor(), ch = cur.ch, line = cm.getLine(cur.line); - CodeMirror.commands.goLineEnd(cm); + CodeMirror.commands.goLineEnd(cm); if (cur.line != cm.lineCount()) { CodeMirror.commands.goLineEnd(cm); cm.replaceSelection(" ", "end"); CodeMirror.commands.delCharRight(cm); - } - } - function editCursor(mode) { - if (mode == "vim-insert") { - // put in your cursor css changing code - } else if (mode == "vim") { - // put in your cursor css changing code } } - function delTillMark(cm, cHar) { - var i = mark[cHar], l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l; + 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)); + pushInBuffer("\n"+cm.getLine(start)); cm.removeLine(start); } } - function yankTillMark(cm, cHar) { - var i = mark[cHar], l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l; + 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"); + } + + // main keymap var map = CodeMirror.keyMap.vim = { - "0": function(cm) {count.length > 0 ? pushCountDigit("0")(cm) : CodeMirror.commands.goLineStart(cm);}, - "A": function(cm) {popCount(); cm.setCursor(cm.getCursor().line, cm.getCursor().ch+1, true); cm.setOption("keyMap", "vim-insert"); editCursor("vim-insert");}, - "Shift-A": function(cm) {popCount(); CodeMirror.commands.goLineEnd(cm); cm.setOption("keyMap", "vim-insert"); editCursor("vim-insert");}, - "I": function(cm) {popCount(); cm.setOption("keyMap", "vim-insert"); editCursor("vim-insert");}, - "Shift-I": function(cm) {popCount(); CodeMirror.commands.goLineStartSmart(cm); cm.setOption("keyMap", "vim-insert"); editCursor("vim-insert");}, - "O": function(cm) {popCount(); CodeMirror.commands.goLineEnd(cm); cm.replaceSelection("\n", "end"); cm.setOption("keyMap", "vim-insert"); editCursor("vim-insert");}, - "Shift-O": function(cm) {popCount(); CodeMirror.commands.goLineStart(cm); cm.replaceSelection("\n", "start"); cm.setOption("keyMap", "vim-insert"); editCursor("vim-insert");}, - "G": function(cm) {cm.setOption("keyMap", "vim-prefix-g");}, - "D": function(cm) {cm.setOption("keyMap", "vim-prefix-d"); emptyBuffer();}, + // 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;}, - "/": function(cm) {var f = CodeMirror.commands.find; f && f(cm); sdir = "f"}, - "Shift-/": function(cm) { + "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"; } }, @@ -104,244 +289,478 @@ 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)-1); popCount(); CodeMirror.commands.goLineStart(cm);}, - catchall: function(cm) {/*ignore*/} + "Shift-G": function(cm) { + count == "" ? cm.setCursor(cm.lineCount()) : cm.setCursor(parseInt(count, 10)-1); + popCount(); + CodeMirror.commands.goLineStart(cm); + }, + nofallthrough: true, style: "fat-cursor" }; - // Add bindings for number keys - for (var i = 1; i < 10; ++i) map[i] = pushCountDigit(i); + + // 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({"H": "goColumnLeft", "L": "goColumnRight", "J": "goLineDown", "K": "goLineUp", - "Left": "goColumnLeft", "Right": "goColumnRight", "Down": "goLineDown", "Up": "goLineUp", - "Backspace": "goCharLeft", "Space": "goCharRight", - "B": function(cm) {moveToWord(cm, word, -1, "end");}, - "E": function(cm) {moveToWord(cm, word, 1, "end");}, - "W": function(cm) {moveToWord(cm, word, 1, "start");}, - "Shift-B": function(cm) {moveToWord(cm, bigWord, -1, "end");}, - "Shift-E": function(cm) {moveToWord(cm, bigWord, 1, "end");}, - "Shift-W": function(cm) {moveToWord(cm, bigWord, 1, "start");}, - "X": function(cm) {CodeMirror.commands.delCharRight(cm)}, - "P": function(cm) { - var cur = cm.getCursor().line; - if (buf!= "") { - CodeMirror.commands.goLineEnd(cm); - cm.replaceSelection(buf, "end"); - } - cm.setCursor(cur+1); - }, - "Shift-X": function(cm) {CodeMirror.commands.delCharLeft(cm)}, - "Shift-J": function(cm) {joinLineNext(cm)}, - "Shift-`": 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", "Shift-4": "goLineEnd"}, - function(key, cmd) { map[key] = countTimes(cmd); }); + 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) { moveToWord(cm, word, -1, "start");}), - "Shift-E": countTimes(function(cm) { moveToWord(cm, bigWord, -1, "start");}), - auto: "vim", - catchall: function(cm) {/*ignore*/} + "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-m"] = { - "A": function(cm) {mark["A"] = cm.getCursor().line;}, - "Shift-A": function(cm) {mark["Shift-A"] = cm.getCursor().line;}, - "B": function(cm) {mark["B"] = cm.getCursor().line;}, - "Shift-B": function(cm) {mark["Shift-B"] = cm.getCursor().line;}, - "C": function(cm) {mark["C"] = cm.getCursor().line;}, - "Shift-C": function(cm) {mark["Shift-C"] = cm.getCursor().line;}, - "D": function(cm) {mark["D"] = cm.getCursor().line;}, - "Shift-D": function(cm) {mark["Shift-D"] = cm.getCursor().line;}, - "E": function(cm) {mark["E"] = cm.getCursor().line;}, - "Shift-E": function(cm) {mark["Shift-E"] = cm.getCursor().line;}, - "F": function(cm) {mark["F"] = cm.getCursor().line;}, - "Shift-F": function(cm) {mark["Shift-F"] = cm.getCursor().line;}, - "G": function(cm) {mark["G"] = cm.getCursor().line;}, - "Shift-G": function(cm) {mark["Shift-G"] = cm.getCursor().line;}, - "H": function(cm) {mark["H"] = cm.getCursor().line;}, - "Shift-H": function(cm) {mark["Shift-H"] = cm.getCursor().line;}, - "I": function(cm) {mark["I"] = cm.getCursor().line;}, - "Shift-I": function(cm) {mark["Shift-I"] = cm.getCursor().line;}, - "J": function(cm) {mark["J"] = cm.getCursor().line;}, - "Shift-J": function(cm) {mark["Shift-J"] = cm.getCursor().line;}, - "K": function(cm) {mark["K"] = cm.getCursor().line;}, - "Shift-K": function(cm) {mark["Shift-K"] = cm.getCursor().line;}, - "L": function(cm) {mark["L"] = cm.getCursor().line;}, - "Shift-L": function(cm) {mark["Shift-L"] = cm.getCursor().line;}, - "M": function(cm) {mark["M"] = cm.getCursor().line;}, - "Shift-M": function(cm) {mark["Shift-M"] = cm.getCursor().line;}, - "N": function(cm) {mark["N"] = cm.getCursor().line;}, - "Shift-N": function(cm) {mark["Shift-N"] = cm.getCursor().line;}, - "O": function(cm) {mark["O"] = cm.getCursor().line;}, - "Shift-O": function(cm) {mark["Shift-O"] = cm.getCursor().line;}, - "P": function(cm) {mark["P"] = cm.getCursor().line;}, - "Shift-P": function(cm) {mark["Shift-P"] = cm.getCursor().line;}, - "Q": function(cm) {mark["Q"] = cm.getCursor().line;}, - "Shift-Q": function(cm) {mark["Shift-Q"] = cm.getCursor().line;}, - "R": function(cm) {mark["R"] = cm.getCursor().line;}, - "Shift-R": function(cm) {mark["Shift-R"] = cm.getCursor().line;}, - "S": function(cm) {mark["S"] = cm.getCursor().line;}, - "Shift-S": function(cm) {mark["Shift-S"] = cm.getCursor().line;}, - "T": function(cm) {mark["T"] = cm.getCursor().line;}, - "Shift-T": function(cm) {mark["Shift-T"] = cm.getCursor().line;}, - "U": function(cm) {mark["U"] = cm.getCursor().line;}, - "Shift-U": function(cm) {mark["Shift-U"] = cm.getCursor().line;}, - "V": function(cm) {mark["V"] = cm.getCursor().line;}, - "Shift-V": function(cm) {mark["Shift-V"] = cm.getCursor().line;}, - "W": function(cm) {mark["W"] = cm.getCursor().line;}, - "Shift-W": function(cm) {mark["Shift-W"] = cm.getCursor().line;}, - "X": function(cm) {mark["X"] = cm.getCursor().line;}, - "Shift-X": function(cm) {mark["Shift-X"] = cm.getCursor().line;}, - "Y": function(cm) {mark["Y"] = cm.getCursor().line;}, - "Shift-Y": function(cm) {mark["Shift-Y"] = cm.getCursor().line;}, - "Z": function(cm) {mark["Z"] = cm.getCursor().line;}, - "Shift-Z": function(cm) {mark["Shift-Z"] = cm.getCursor().line;}, - auto: "vim", - catchall: function(cm) {/*ignore*/} - } - CodeMirror.keyMap["vim-prefix-d"] = { - "D": countTimes(function(cm) { pushInBuffer("\n"+cm.getLine(cm.getCursor().line)); cm.removeLine(cm.getCursor().line); }), - "'": function(cm) {cm.setOption("keyMap", "vim-prefix-d'"); emptyBuffer();}, - auto: "vim", - catchall: function(cm) {/*ignore*/} + "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-d'"] = { - "A": function(cm) {delTillMark(cm,"A");}, - "Shift-A": function(cm) {delTillMark(cm,"Shift-A");}, - "B": function(cm) {delTillMark(cm,"B");}, - "Shift-B": function(cm) {delTillMark(cm,"Shift-B");}, - "C": function(cm) {delTillMark(cm,"C");}, - "Shift-C": function(cm) {delTillMark(cm,"Shift-C");}, - "D": function(cm) {delTillMark(cm,"D");}, - "Shift-D": function(cm) {delTillMark(cm,"Shift-D");}, - "E": function(cm) {delTillMark(cm,"E");}, - "Shift-E": function(cm) {delTillMark(cm,"Shift-E");}, - "F": function(cm) {delTillMark(cm,"F");}, - "Shift-F": function(cm) {delTillMark(cm,"Shift-F");}, - "G": function(cm) {delTillMark(cm,"G");}, - "Shift-G": function(cm) {delTillMark(cm,"Shift-G");}, - "H": function(cm) {delTillMark(cm,"H");}, - "Shift-H": function(cm) {delTillMark(cm,"Shift-H");}, - "I": function(cm) {delTillMark(cm,"I");}, - "Shift-I": function(cm) {delTillMark(cm,"Shift-I");}, - "J": function(cm) {delTillMark(cm,"J");}, - "Shift-J": function(cm) {delTillMark(cm,"Shift-J");}, - "K": function(cm) {delTillMark(cm,"K");}, - "Shift-K": function(cm) {delTillMark(cm,"Shift-K");}, - "L": function(cm) {delTillMark(cm,"L");}, - "Shift-L": function(cm) {delTillMark(cm,"Shift-L");}, - "M": function(cm) {delTillMark(cm,"M");}, - "Shift-M": function(cm) {delTillMark(cm,"Shift-M");}, - "N": function(cm) {delTillMark(cm,"N");}, - "Shift-N": function(cm) {delTillMark(cm,"Shift-N");}, - "O": function(cm) {delTillMark(cm,"O");}, - "Shift-O": function(cm) {delTillMark(cm,"Shift-O");}, - "P": function(cm) {delTillMark(cm,"P");}, - "Shift-P": function(cm) {delTillMark(cm,"Shift-P");}, - "Q": function(cm) {delTillMark(cm,"Q");}, - "Shift-Q": function(cm) {delTillMark(cm,"Shift-Q");}, - "R": function(cm) {delTillMark(cm,"R");}, - "Shift-R": function(cm) {delTillMark(cm,"Shift-R");}, - "S": function(cm) {delTillMark(cm,"S");}, - "Shift-S": function(cm) {delTillMark(cm,"Shift-S");}, - "T": function(cm) {delTillMark(cm,"T");}, - "Shift-T": function(cm) {delTillMark(cm,"Shift-T");}, - "U": function(cm) {delTillMark(cm,"U");}, - "Shift-U": function(cm) {delTillMark(cm,"Shift-U");}, - "V": function(cm) {delTillMark(cm,"V");}, - "Shift-V": function(cm) {delTillMark(cm,"Shift-V");}, - "W": function(cm) {delTillMark(cm,"W");}, - "Shift-W": function(cm) {delTillMark(cm,"Shift-W");}, - "X": function(cm) {delTillMark(cm,"X");}, - "Shift-X": function(cm) {delTillMark(cm,"Shift-X");}, - "Y": function(cm) {delTillMark(cm,"Y");}, - "Shift-Y": function(cm) {delTillMark(cm,"Shift-Y");}, - "Z": function(cm) {delTillMark(cm,"Z");}, - "Shift-Z": function(cm) {delTillMark(cm,"Shift-Z");}, - auto: "vim", - catchall: function(cm) {/*ignore*/} + 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" }; - CodeMirror.keyMap["vim-prefix-y'"] = { - "A": function(cm) {yankTillMark(cm,"A");}, - "Shift-A": function(cm) {yankTillMark(cm,"Shift-A");}, - "B": function(cm) {yankTillMark(cm,"B");}, - "Shift-B": function(cm) {yankTillMark(cm,"Shift-B");}, - "C": function(cm) {yankTillMark(cm,"C");}, - "Shift-C": function(cm) {yankTillMark(cm,"Shift-C");}, - "D": function(cm) {yankTillMark(cm,"D");}, - "Shift-D": function(cm) {yankTillMark(cm,"Shift-D");}, - "E": function(cm) {yankTillMark(cm,"E");}, - "Shift-E": function(cm) {yankTillMark(cm,"Shift-E");}, - "F": function(cm) {yankTillMark(cm,"F");}, - "Shift-F": function(cm) {yankTillMark(cm,"Shift-F");}, - "G": function(cm) {yankTillMark(cm,"G");}, - "Shift-G": function(cm) {yankTillMark(cm,"Shift-G");}, - "H": function(cm) {yankTillMark(cm,"H");}, - "Shift-H": function(cm) {yankTillMark(cm,"Shift-H");}, - "I": function(cm) {yankTillMark(cm,"I");}, - "Shift-I": function(cm) {yankTillMark(cm,"Shift-I");}, - "J": function(cm) {yankTillMark(cm,"J");}, - "Shift-J": function(cm) {yankTillMark(cm,"Shift-J");}, - "K": function(cm) {yankTillMark(cm,"K");}, - "Shift-K": function(cm) {yankTillMark(cm,"Shift-K");}, - "L": function(cm) {yankTillMark(cm,"L");}, - "Shift-L": function(cm) {yankTillMark(cm,"Shift-L");}, - "M": function(cm) {yankTillMark(cm,"M");}, - "Shift-M": function(cm) {yankTillMark(cm,"Shift-M");}, - "N": function(cm) {yankTillMark(cm,"N");}, - "Shift-N": function(cm) {yankTillMark(cm,"Shift-N");}, - "O": function(cm) {yankTillMark(cm,"O");}, - "Shift-O": function(cm) {yankTillMark(cm,"Shift-O");}, - "P": function(cm) {yankTillMark(cm,"P");}, - "Shift-P": function(cm) {yankTillMark(cm,"Shift-P");}, - "Q": function(cm) {yankTillMark(cm,"Q");}, - "Shift-Q": function(cm) {yankTillMark(cm,"Shift-Q");}, - "R": function(cm) {yankTillMark(cm,"R");}, - "Shift-R": function(cm) {yankTillMark(cm,"Shift-R");}, - "S": function(cm) {yankTillMark(cm,"S");}, - "Shift-S": function(cm) {yankTillMark(cm,"Shift-S");}, - "T": function(cm) {yankTillMark(cm,"T");}, - "Shift-T": function(cm) {yankTillMark(cm,"Shift-T");}, - "U": function(cm) {yankTillMark(cm,"U");}, - "Shift-U": function(cm) {yankTillMark(cm,"Shift-U");}, - "V": function(cm) {yankTillMark(cm,"V");}, - "Shift-V": function(cm) {yankTillMark(cm,"Shift-V");}, - "W": function(cm) {yankTillMark(cm,"W");}, - "Shift-W": function(cm) {yankTillMark(cm,"Shift-W");}, - "X": function(cm) {yankTillMark(cm,"X");}, - "Shift-X": function(cm) {yankTillMark(cm,"Shift-X");}, - "Y": function(cm) {yankTillMark(cm,"Y");}, - "Shift-Y": function(cm) {yankTillMark(cm,"Shift-Y");}, - "Z": function(cm) {yankTillMark(cm,"Z");}, - "Shift-Z": function(cm) {yankTillMark(cm,"Shift-Z");}, - auto: "vim", - catchall: function(cm) {/*ignore*/} + 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();}, - auto: "vim", - catchall: function(cm) {/*ignore*/} + nofallthrough: true, style: "fat-cursor" }; CodeMirror.keyMap["vim-insert"] = { + // TODO: override navigation keys so that Esc will cancel automatic indentation from o, O, i_ "Esc": function(cm) { - cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1, true); - cm.setOption("keyMap", "vim"); - editCursor("vim"); - }, - "Ctrl-N": function(cm) {/* Code to bring up autocomplete hint */}, - "Ctrl-P": function(cm) {/* Code to bring up autocomplete hint */}, + 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) + } })(); diff --git a/imports/codemirror/lib/codemirror.css b/imports/codemirror/lib/codemirror.css index 2d79f4aa..191ac25a 100644 --- a/imports/codemirror/lib/codemirror.css +++ b/imports/codemirror/lib/codemirror.css @@ -1,10 +1,16 @@ .CodeMirror { line-height: 1em; font-family: monospace; + + /* Necessary so the scrollbar can be absolutely positioned within the wrapper on Lion. */ + position: relative; + /* This prevents unwanted scrollbars from showing up on the body and wrapper in IE. */ + overflow: hidden; } .CodeMirror-scroll { - overflow: auto; + overflow-x: auto; + overflow-y: hidden; height: 300px; /* This is needed to prevent an IE[67] bug where the scrolled content is visible outside of the scrolling box. */ @@ -12,6 +18,37 @@ outline: none; } +/* Vertical scrollbar */ +.CodeMirror-scrollbar { + float: right; + overflow-x: hidden; + overflow-y: scroll; + + /* This corrects for the 1px gap introduced to the left of the scrollbar + by the rule for .CodeMirror-scrollbar-inner. */ + margin-left: -1px; +} +.CodeMirror-scrollbar-inner { + /* This needs to have a nonzero width in order for the scrollbar to appear + in Firefox and IE9. */ + width: 1px; +} +.CodeMirror-scrollbar.cm-sb-overlap { + /* Ensure that the scrollbar appears in Lion, and that it overlaps the content + rather than sitting to the right of it. */ + position: absolute; + z-index: 1; + float: none; + right: 0; + min-width: 12px; +} +.CodeMirror-scrollbar.cm-sb-nonoverlap { + min-width: 12px; +} +.CodeMirror-scrollbar.cm-sb-ie7 { + min-width: 18px; +} + .CodeMirror-gutter { position: absolute; left: 0; top: 0; z-index: 10; @@ -29,6 +66,11 @@ .CodeMirror-lines { padding: .4em; white-space: pre; + cursor: text; +} +.CodeMirror-lines * { + /* Necessary for throw-scrolling to decelerate properly on Safari. */ + pointer-events: none; } .CodeMirror pre { @@ -42,11 +84,14 @@ padding: 0; margin: 0; white-space: pre; word-wrap: normal; + line-height: inherit; + color: inherit; } .CodeMirror-wrap pre { word-wrap: break-word; white-space: pre-wrap; + word-break: normal; } .CodeMirror-wrap .CodeMirror-scroll { overflow-x: hidden; @@ -61,8 +106,19 @@ position: absolute; visibility: hidden; border-left: 1px solid black; - border-right:none; - width:0; + border-right: none; + width: 0; +} +.cm-keymap-fat-cursor pre.CodeMirror-cursor { + width: auto; + border: 0; + background: transparent; + background: rgba(0, 200, 0, .4); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800); +} +/* Kludge to turn off filter in ie9+, which also accepts rgba */ +.cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) { + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); } .CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {} .CodeMirror-focused pre.CodeMirror-cursor { @@ -98,7 +154,7 @@ div.CodeMirror-selected { background: #d9d9d9; } .cm-s-default span.cm-bracket {color: #cc7;} .cm-s-default span.cm-tag {color: #170;} .cm-s-default span.cm-attribute {color: #00c;} -.cm-s-default span.cm-header {color: #a0a;} +.cm-s-default span.cm-header {color: blue;} .cm-s-default span.cm-quote {color: #090;} .cm-s-default span.cm-hr {color: #999;} .cm-s-default span.cm-link {color: #00c;} diff --git a/imports/codemirror/lib/codemirror.js b/imports/codemirror/lib/codemirror.js index 5434a8dd..250d5b18 100644 --- a/imports/codemirror/lib/codemirror.js +++ b/imports/codemirror/lib/codemirror.js @@ -1,4 +1,4 @@ -// CodeMirror version 2.23 +// CodeMirror version 2.3 // // All functions that need access to the editor's state live inside // the CodeMirror function. Below that, at the bottom of the file, @@ -23,14 +23,19 @@ var CodeMirror = (function() { '
' + // Wraps and hides input textarea '
' + + '
' + // The vertical scrollbar. Horizontal scrolling is handled by the scroller itself. + '
' + // The empty scrollbar content, used solely for managing the scrollbar thumb. + '
' + // This must be before the scroll area because it's float-right. '
' + '
' + // Set to the height of the text, causes scrolling '
' + // Moved around its parent to cover visible view '
' + // Provides positioning relative to (visible) text origin '
' + + // Used to measure text size '
' + '
 
' + // Absolutely positioned blinky cursor + '' + // Used to force a width '
' + // DIVs containing the selection and the actual code '
'; if (place.appendChild) place.appendChild(wrapper); else place(wrapper); @@ -39,12 +44,13 @@ var CodeMirror = (function() { scroller = wrapper.lastChild, code = scroller.firstChild, mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild, lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild, - cursor = measure.nextSibling, selectionDiv = cursor.nextSibling, - lineDiv = selectionDiv.nextSibling; - themeChanged(); + cursor = measure.nextSibling, widthForcer = cursor.nextSibling, + selectionDiv = widthForcer.nextSibling, lineDiv = selectionDiv.nextSibling, + scrollbar = inputDiv.nextSibling, scrollbarInner = scrollbar.firstChild; + themeChanged(); keyMapChanged(); // Needed to hide big blue blinking cursor on Mobile Safari if (ios) input.style.width = "0px"; - if (!webkit) lineSpace.draggable = true; + if (!webkit) scroller.draggable = true; lineSpace.style.outline = "none"; if (options.tabindex != null) input.tabIndex = options.tabindex; if (options.autofocus) focusInput(); @@ -52,6 +58,17 @@ var CodeMirror = (function() { // Needed to handle Tab key in KHTML if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute"; + // Check for OS X >= 10.7. If so, we need to force a width on the scrollbar, and + // make it overlap the content. (But we only do this if the scrollbar doesn't already + // have a natural width. If the mouse is plugged in or the user sets the system pref + // to always show scrollbars, the scrollbar shouldn't overlap.) + if (mac_geLion) { + scrollbar.className += (overlapScrollbars() ? " cm-sb-overlap" : " cm-sb-nonoverlap"); + } else if (ie_lt8) { + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + scrollbar.className += " cm-sb-ie7"; + } + // Check for problem with IE innerHTML not working when we have a // P (or similar) parent node. try { stringWidth("x"); } @@ -75,7 +92,7 @@ var CodeMirror = (function() { var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false}; // Selection-related flags. shiftSelecting obviously tracks // whether the user is holding shift. - var shiftSelecting, lastClick, lastDoubleClick, lastScrollPos = 0, draggingText, + var shiftSelecting, lastClick, lastDoubleClick, lastScrollTop = 0, lastScrollLeft = 0, draggingText, overwrite = false, suppressEdits = false; // Variables used by startOperation/endOperation to track what // happened during the operation. @@ -88,7 +105,7 @@ var CodeMirror = (function() { var bracketHighlighted; // Tracks the maximum line length so that the horizontal scrollbar // can be kept static when scrolling. - var maxLine = "", maxWidth; + var maxLine = "", updateMaxLine = false, maxLineChanged = true; var tabCache = {}; // Initialize the content. @@ -98,18 +115,16 @@ var CodeMirror = (function() { // Register our event handlers. connect(scroller, "mousedown", operation(onMouseDown)); connect(scroller, "dblclick", operation(onDoubleClick)); - connect(lineSpace, "dragstart", onDragStart); connect(lineSpace, "selectstart", e_preventDefault); // Gecko browsers fire contextmenu *after* opening the menu, at // which point we can't mess with it anymore. Context menu is // handled in onMouseDown for Gecko. if (!gecko) connect(scroller, "contextmenu", onContextMenu); - connect(scroller, "scroll", function() { - lastScrollPos = scroller.scrollTop; - updateDisplay([]); - if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px"; - if (options.onScroll) options.onScroll(instance); - }); + connect(scroller, "scroll", onScroll); + connect(scrollbar, "scroll", onScroll); + connect(scrollbar, "mousedown", function() {setTimeout(focusInput, 0);}); + connect(scroller, "mousewheel", onMouseWheel); + connect(scroller, "DOMMouseScroll", onMouseWheel); connect(window, "resize", function() {updateDisplay(true);}); connect(input, "keyup", operation(onKeyUp)); connect(input, "input", fastPoll); @@ -118,9 +133,16 @@ var CodeMirror = (function() { connect(input, "focus", onFocus); connect(input, "blur", onBlur); - connect(scroller, "dragenter", e_stop); - connect(scroller, "dragover", e_stop); - connect(scroller, "drop", operation(onDrop)); + if (options.dragDrop) { + connect(scroller, "dragstart", onDragStart); + function drag_(e) { + if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return; + e_stop(e); + } + connect(scroller, "dragenter", drag_); + connect(scroller, "dragover", drag_); + connect(scroller, "drop", operation(onDrop)); + } connect(scroller, "paste", function(){focusInput(); fastPoll();}); connect(input, "paste", fastPoll); connect(input, "cut", operation(function(){ @@ -160,6 +182,7 @@ var CodeMirror = (function() { else if (option == "theme") themeChanged(); else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)(); else if (option == "tabSize") updateDisplay(true); + else if (option == "keyMap") keyMapChanged(); if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme") { gutterChanged(); updateDisplay(true); @@ -307,15 +330,20 @@ var CodeMirror = (function() { }, scrollTo: function(x, y) { if (x != null) scroller.scrollLeft = x; - if (y != null) scroller.scrollTop = y; + if (y != null) scrollbar.scrollTop = y; updateDisplay([]); }, + getScrollInfo: function() { + return {x: scroller.scrollLeft, y: scrollbar.scrollTop, + height: scrollbar.scrollHeight, width: scroller.scrollWidth}; + }, operation: function(f){return operation(f)();}, + compoundChange: function(f){return compoundChange(f);}, refresh: function(){ updateDisplay(true); - if (scroller.scrollHeight > lastScrollPos) - scroller.scrollTop = lastScrollPos; + if (scrollbar.scrollHeight > lastScrollTop) + scrollbar.scrollTop = lastScrollTop; }, getInputField: function(){return input;}, getWrapperElement: function(){return wrapper;}, @@ -336,12 +364,22 @@ var CodeMirror = (function() { splitLines(code), top, top); updateInput = true; } - function getValue(code) { + function getValue() { var text = []; doc.iter(0, doc.size, function(line) { text.push(line.text); }); return text.join("\n"); } + function onScroll(e) { + if (lastScrollTop != scrollbar.scrollTop || lastScrollLeft != scroller.scrollLeft) { + lastScrollTop = scrollbar.scrollTop; + lastScrollLeft = scroller.scrollLeft; + updateDisplay([]); + if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px"; + if (options.onScroll) options.onScroll(instance); + } + } + function onMouseDown(e) { setShift(e_prop(e, "shiftKey")); // Check whether this is a click in a widget @@ -364,6 +402,8 @@ var CodeMirror = (function() { return; case 2: if (start) setCursor(start.line, start.ch, true); + setTimeout(focusInput, 20); + e_preventDefault(e); return; } // For button 1, if it was clicked inside the editor @@ -385,23 +425,25 @@ var CodeMirror = (function() { } else { lastClick = {time: now, pos: start}; } var last = start, going; - if (dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) && + if (options.dragDrop && dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) && !posLess(start, sel.from) && !posLess(sel.to, start)) { // Let the drag handler handle this. - if (webkit) lineSpace.draggable = true; - var up = connect(document, "mouseup", operation(function(e2) { - if (webkit) lineSpace.draggable = false; + if (webkit) scroller.draggable = true; + function dragEnd(e2) { + if (webkit) scroller.draggable = false; draggingText = false; - up(); + up(); drop(); if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { e_preventDefault(e2); setCursor(start.line, start.ch, true); focusInput(); } - }), true); + } + var up = connect(document, "mouseup", operation(dragEnd), true); + var drop = connect(scroller, "drop", operation(dragEnd), true); draggingText = true; // IE's approach to draggable - if (lineSpace.dragDrop) lineSpace.dragDrop(); + if (scroller.dragDrop) scroller.dragDrop(); return; } e_preventDefault(e); @@ -447,6 +489,7 @@ var CodeMirror = (function() { selectWordAt(start); } function onDrop(e) { + if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return; e.preventDefault(); var pos = posFromMouse(e, true), files = e.dataTransfer.files; if (!pos || options.readOnly) return; @@ -456,28 +499,31 @@ var CodeMirror = (function() { reader.onload = function() { text[i] = reader.result; if (++read == n) { - pos = clipPos(pos); - operation(function() { + pos = clipPos(pos); + operation(function() { var end = replaceRange(text.join(""), pos, pos); setSelectionUser(pos, end); })(); - } + } }; reader.readAsText(file); } var n = files.length, text = Array(n), read = 0; for (var i = 0; i < n; ++i) loadFile(files[i], i); - } - else { + } else { + // Don't do a replace if the drop happened inside of the selected text. + if (draggingText && !(posLess(pos, sel.from) || posLess(sel.to, pos))) return; try { var text = e.dataTransfer.getData("Text"); if (text) { - var curFrom = sel.from, curTo = sel.to; - setSelectionUser(pos, pos); - if (draggingText) replaceRange("", curFrom, curTo); - replaceSelection(text); - focusInput(); - } + compoundChange(function() { + var curFrom = sel.from, curTo = sel.to; + setSelectionUser(pos, pos); + if (draggingText) replaceRange("", curFrom, curTo); + replaceSelection(text); + focusInput(); + }); + } } catch(e){} } @@ -487,7 +533,7 @@ var CodeMirror = (function() { e.dataTransfer.setData("Text", txt); // Use dummy image instead of default browsers image. - if (gecko || chrome) { + if (gecko || chrome || opera) { var img = document.createElement('img'); img.scr = 'data:image/gif;base64,R0lGODdhAgACAIAAAAAAAP///ywAAAAAAgACAAACAoRRADs='; //1x1 image e.dataTransfer.setDragImage(img, 0, 0); @@ -529,25 +575,33 @@ var CodeMirror = (function() { if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name; if (e_prop(e, "metaKey")) name = "Cmd-" + name; + var stopped = false; + function stop() { stopped = true; } + if (e_prop(e, "shiftKey")) { handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap, - function(b) {return