diff --git a/.gitignore b/.gitignore index bc20ab58d5..b471fe6e63 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ /npm-debug.log test.html .tern-* +*~ +*.swp diff --git a/addon/comment/comment.js b/addon/comment/comment.js index 3c76744654..cd2123e175 100644 --- a/addon/comment/comment.js +++ b/addon/comment/comment.js @@ -17,7 +17,7 @@ CodeMirror.defineExtension("lineComment", function(from, to, options) { if (!options) options = noOptions; - var self = this, mode = CodeMirror.innerMode(self.getMode(), self.getTokenAt(from).state).mode; + var self = this, mode = self.getModeAt(from); var commentString = options.lineComment || mode.lineComment; if (!commentString) { if (options.blockCommentStart || mode.blockCommentStart) { @@ -52,7 +52,7 @@ CodeMirror.defineExtension("blockComment", function(from, to, options) { if (!options) options = noOptions; - var self = this, mode = CodeMirror.innerMode(self.getMode(), self.getTokenAt(from).state).mode; + var self = this, mode = self.getModeAt(from); var startString = options.blockCommentStart || mode.blockCommentStart; var endString = options.blockCommentEnd || mode.blockCommentEnd; if (!startString || !endString) { @@ -85,7 +85,7 @@ CodeMirror.defineExtension("uncomment", function(from, to, options) { if (!options) options = noOptions; - var self = this, mode = CodeMirror.innerMode(self.getMode(), self.getTokenAt(from).state).mode; + var self = this, mode = self.getModeAt(from); var end = Math.min(to.line, self.lastLine()), start = Math.min(from.line, end); // Try finding line comments diff --git a/addon/edit/closebrackets.js b/addon/edit/closebrackets.js index 6fbf38ae67..88718b7729 100644 --- a/addon/edit/closebrackets.js +++ b/addon/edit/closebrackets.js @@ -54,7 +54,9 @@ if (cm.somethingSelected()) return surround(cm); if (left == right && maybeOverwrite(cm) != CodeMirror.Pass) return; var cur = cm.getCursor(), ahead = CodeMirror.Pos(cur.line, cur.ch + 1); - var line = cm.getLine(cur.line), nextChar = line.charAt(cur.ch); + var line = cm.getLine(cur.line), nextChar = line.charAt(cur.ch), curChar = cur.ch > 0 ? line.charAt(cur.ch - 1) : ""; + if (left == right && CodeMirror.isWordChar(curChar)) + return CodeMirror.Pass; if (line.length == cur.ch || closingBrackets.indexOf(nextChar) >= 0 || SPACE_CHAR_REGEX.test(nextChar)) cm.replaceSelection(left + right, {head: ahead, anchor: ahead}); else diff --git a/addon/edit/matchtags.js b/addon/edit/matchtags.js new file mode 100644 index 0000000000..3eabfeb8d3 --- /dev/null +++ b/addon/edit/matchtags.js @@ -0,0 +1,51 @@ +(function() { + "use strict"; + + CodeMirror.defineOption("matchTags", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) { + cm.off("cursorActivity", doMatchTags); + cm.off("viewportChange", maybeUpdateMatch); + clear(cm); + } + if (val) { + cm.on("cursorActivity", doMatchTags); + cm.on("viewportChange", maybeUpdateMatch); + doMatchTags(cm); + } + }); + + function clear(cm) { + if (cm.state.matchedTag) { + cm.state.matchedTag.clear(); + cm.state.matchedTag = null; + } + } + + function doMatchTags(cm) { + cm.state.failedTagMatch = false; + cm.operation(function() { + clear(cm); + var cur = cm.getCursor(), range = cm.getViewport(); + range.from = Math.min(range.from, cur.line); range.to = Math.max(cur.line + 1, range.to); + var match = CodeMirror.findMatchingTag(cm, cur, range); + if (!match) return; + var other = match.at == "close" ? match.open : match.close; + if (other) + cm.state.matchedTag = cm.markText(other.from, other.to, {className: "CodeMirror-matchingtag"}); + else + cm.state.failedTagMatch = true; + }); + } + + function maybeUpdateMatch(cm) { + if (cm.state.failedTagMatch) doMatchTags(cm); + } + + CodeMirror.commands.toMatchingTag = function(cm) { + var found = CodeMirror.findMatchingTag(cm, cm.getCursor()); + if (found) { + var other = found.at == "close" ? found.open : found.close; + if (other) cm.setSelection(other.to, other.from); + } + }; +})(); diff --git a/addon/fold/brace-fold.js b/addon/fold/brace-fold.js index e35115b8b9..0b4d7dc34d 100644 --- a/addon/fold/brace-fold.js +++ b/addon/fold/brace-fold.js @@ -1,4 +1,4 @@ -CodeMirror.braceRangeFinder = function(cm, start) { +CodeMirror.registerHelper("fold", "brace", function(cm, start) { var line = start.line, lineText = cm.getLine(line); var startCh, tokenType; @@ -44,9 +44,10 @@ CodeMirror.braceRangeFinder = function(cm, start) { if (end == null || line == end && endCh == startCh) return; return {from: CodeMirror.Pos(line, startCh), to: CodeMirror.Pos(end, endCh)}; -}; +}); +CodeMirror.braceRangeFinder = CodeMirror.fold.brace; // deprecated -CodeMirror.importRangeFinder = function(cm, start) { +CodeMirror.registerHelper("fold", "import", function(cm, start) { function hasImport(line) { if (line < cm.firstLine() || line > cm.lastLine()) return null; var start = cm.getTokenAt(CodeMirror.Pos(line, 1)); @@ -68,9 +69,10 @@ CodeMirror.importRangeFinder = function(cm, start) { end = next.end; } return {from: cm.clipPos(CodeMirror.Pos(start, has.startCh + 1)), to: end}; -}; +}); +CodeMirror.importRangeFinder = CodeMirror.fold["import"]; // deprecated -CodeMirror.includeRangeFinder = function(cm, start) { +CodeMirror.registerHelper("fold", "include", function(cm, start) { function hasInclude(line) { if (line < cm.firstLine() || line > cm.lastLine()) return null; var start = cm.getTokenAt(CodeMirror.Pos(line, 1)); @@ -87,4 +89,5 @@ CodeMirror.includeRangeFinder = function(cm, start) { } return {from: CodeMirror.Pos(start, has + 1), to: cm.clipPos(CodeMirror.Pos(end))}; -}; +}); +CodeMirror.includeRangeFinder = CodeMirror.fold.include; // deprecated diff --git a/addon/fold/foldcode.js b/addon/fold/foldcode.js index 2743d3e2c9..931c3726cc 100644 --- a/addon/fold/foldcode.js +++ b/addon/fold/foldcode.js @@ -2,7 +2,8 @@ "use strict"; function doFold(cm, pos, options) { - var finder = options.call ? options : (options && options.rangeFinder); + var finder = options && (options.call ? options : options.rangeFinder); + if (!finder) finder = cm.getHelper(pos, "fold"); if (!finder) return; if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0); var minSize = options && options.minFoldSize || 0; @@ -29,12 +30,16 @@ if (!range || range.cleared) return; var myWidget = makeWidget(options); - CodeMirror.on(myWidget, "mousedown", function() {myRange.clear();}); + CodeMirror.on(myWidget, "mousedown", function() { myRange.clear(); }); var myRange = cm.markText(range.from, range.to, { replacedWith: myWidget, clearOnEnter: true, __isFold: true }); + myRange.on("clear", function(from, to) { + CodeMirror.signal(cm, "unfold", cm, from, to); + }); + CodeMirror.signal(cm, "fold", cm, range.from, range.to); } function makeWidget(options) { @@ -56,7 +61,7 @@ // New-style interface CodeMirror.defineExtension("foldCode", function(pos, options) { doFold(this, pos, options); }); - CodeMirror.combineRangeFinders = function() { + CodeMirror.registerHelper("fold", "combine", function() { var funcs = Array.prototype.slice.call(arguments, 0); return function(cm, start) { for (var i = 0; i < funcs.length; ++i) { @@ -64,5 +69,5 @@ if (found) return found; } }; - }; + }); })(); diff --git a/addon/fold/foldgutter.js b/addon/fold/foldgutter.js new file mode 100644 index 0000000000..b809c93f56 --- /dev/null +++ b/addon/fold/foldgutter.js @@ -0,0 +1,122 @@ +(function() { + "use strict"; + + CodeMirror.defineOption("foldGutter", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) { + cm.clearGutter(cm.state.foldGutter.options.gutter); + cm.state.foldGutter = null; + cm.off("gutterClick", onGutterClick); + cm.off("change", onChange); + cm.off("viewportChange", onViewportChange); + cm.off("fold", onFold); + cm.off("unfold", onFold); + } + if (val) { + cm.state.foldGutter = new State(parseOptions(val)); + updateInViewport(cm); + cm.on("gutterClick", onGutterClick); + cm.on("change", onChange); + cm.on("viewportChange", onViewportChange); + cm.on("fold", onFold); + cm.on("unfold", onFold); + } + }); + + var Pos = CodeMirror.Pos; + + function State(options) { + this.options = options; + this.from = this.to = 0; + } + + function parseOptions(opts) { + if (opts === true) opts = {}; + if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter"; + if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open"; + if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded"; + return opts; + } + + function isFolded(cm, line) { + var marks = cm.findMarksAt(Pos(line)); + for (var i = 0; i < marks.length; ++i) + if (marks[i].__isFold && marks[i].find().from.line == line) return true; + } + + function marker(spec) { + if (typeof spec == "string") { + var elt = document.createElement("div"); + elt.className = spec; + return elt; + } else { + return spec.cloneNode(true); + } + } + + function updateFoldInfo(cm, from, to) { + var opts = cm.state.foldGutter.options, cur = from; + cm.eachLine(from, to, function(line) { + var mark = null; + if (isFolded(cm, cur)) { + mark = marker(opts.indicatorFolded); + } else { + var pos = Pos(cur, 0), func = opts.rangeFinder || cm.getHelper(pos, "fold"); + var range = func && func(cm, pos); + if (range && range.from.line + 1 < range.to.line) + mark = marker(opts.indicatorOpen); + } + cm.setGutterMarker(line, opts.gutter, mark); + ++cur; + }); + } + + function updateInViewport(cm) { + var vp = cm.getViewport(), state = cm.state.foldGutter; + if (!state) return; + cm.operation(function() { + updateFoldInfo(cm, vp.from, vp.to); + }); + state.from = vp.from; state.to = vp.to; + } + + function onGutterClick(cm, line, gutter) { + var opts = cm.state.foldGutter.options; + if (gutter != opts.gutter) return; + cm.foldCode(Pos(line, 0), opts.rangeFinder); + } + + function onChange(cm) { + var state = cm.state.foldGutter; + state.from = state.to = 0; + clearTimeout(state.changeUpdate); + state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, 600); + } + + function onViewportChange(cm) { + var state = cm.state.foldGutter; + clearTimeout(state.changeUpdate); + state.changeUpdate = setTimeout(function() { + var vp = cm.getViewport(); + if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) { + updateInViewport(cm); + } else { + cm.operation(function() { + if (vp.from < state.from) { + updateFoldInfo(cm, vp.from, state.from); + state.from = vp.from; + } + if (vp.to > state.to) { + updateFoldInfo(cm, state.to, vp.to); + state.to = vp.to; + } + }); + } + }, 400); + } + + function onFold(cm, from) { + var state = cm.state.foldGutter, line = from.line; + if (line >= state.from && line < state.to) + updateFoldInfo(cm, line, line + 1); + } +})(); diff --git a/addon/fold/indent-fold.js b/addon/fold/indent-fold.js index 94a0a1ffae..fcbff96696 100644 --- a/addon/fold/indent-fold.js +++ b/addon/fold/indent-fold.js @@ -1,4 +1,4 @@ -CodeMirror.indentRangeFinder = function(cm, start) { +CodeMirror.registerHelper("fold", "indent", function(cm, start) { var tabSize = cm.getOption("tabSize"), firstLine = cm.getLine(start.line); var myIndent = CodeMirror.countColumn(firstLine, null, tabSize); for (var i = start.line + 1, end = cm.lineCount(); i < end; ++i) { @@ -8,4 +8,5 @@ CodeMirror.indentRangeFinder = function(cm, start) { return {from: CodeMirror.Pos(start.line, firstLine.length), to: CodeMirror.Pos(i, curLine.length)}; } -}; +}); +CodeMirror.indentRangeFinder = CodeMirror.fold.indent; // deprecated diff --git a/addon/fold/xml-fold.js b/addon/fold/xml-fold.js index b764bc0190..88a107c4a2 100644 --- a/addon/fold/xml-fold.js +++ b/addon/fold/xml-fold.js @@ -2,14 +2,17 @@ "use strict"; var Pos = CodeMirror.Pos; + function cmp(a, b) { return a.line - b.line || a.ch - b.ch; } var nameStartChar = "A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD"; var nameChar = nameStartChar + "\-\:\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040"; var xmlTagStart = new RegExp("<(/?)([" + nameStartChar + "][" + nameChar + "]*)", "g"); - function Iter(cm, line, ch) { + function Iter(cm, line, ch, range) { this.line = line; this.ch = ch; this.cm = cm; this.text = cm.getLine(line); + this.min = range ? range.from : cm.firstLine(); + this.max = range ? range.to - 1 : cm.lastLine(); } function tagAt(iter, ch) { @@ -18,13 +21,13 @@ } function nextLine(iter) { - if (iter.line >= iter.cm.lastLine()) return; + if (iter.line >= iter.max) return; iter.ch = 0; iter.text = iter.cm.getLine(++iter.line); return true; } function prevLine(iter) { - if (iter.line <= iter.cm.firstLine()) return; + if (iter.line <= iter.min) return; iter.text = iter.cm.getLine(--iter.line); iter.ch = iter.text.length; return true; @@ -43,7 +46,7 @@ } function toTagStart(iter) { for (;;) { - var lt = iter.text.lastIndexOf("<", iter.ch - 1); + var lt = iter.ch ? iter.text.lastIndexOf("<", iter.ch - 1) : -1; if (lt == -1) { if (prevLine(iter)) continue; else return; } if (!tagAt(iter, lt + 1)) { iter.ch = lt; continue; } xmlTagStart.lastIndex = lt; @@ -65,7 +68,7 @@ } function toPrevTag(iter) { for (;;) { - var gt = iter.text.lastIndexOf(">", iter.ch - 1); + var gt = iter.ch ? iter.text.lastIndexOf(">", iter.ch - 1) : -1; if (gt == -1) { if (prevLine(iter)) continue; else return; } if (!tagAt(iter, gt + 1)) { iter.ch = gt; continue; } var lastSlash = iter.text.lastIndexOf("/", gt); @@ -121,7 +124,7 @@ } } - CodeMirror.tagRangeFinder = function(cm, start) { + CodeMirror.registerHelper("fold", "xml", function(cm, start) { var iter = new Iter(cm, start.line, 0); for (;;) { var openTag = toNextTag(iter), end; @@ -132,27 +135,31 @@ return close && {from: start, to: close.from}; } } - }; + }); + CodeMirror.tagRangeFinder = CodeMirror.fold.xml; // deprecated - CodeMirror.findMatchingTag = function(cm, pos) { - var iter = new Iter(cm, pos.line, pos.ch); - var end = toTagEnd(iter), start = toTagStart(iter); - if (!end || end == "selfClose" || !start) return; + CodeMirror.findMatchingTag = function(cm, pos, range) { + var iter = new Iter(cm, pos.line, pos.ch, range); + if (iter.text.indexOf(">") == -1 && iter.text.indexOf("<") == -1) return; + var end = toTagEnd(iter), to = end && Pos(iter.line, iter.ch); + var start = end && toTagStart(iter); + if (!end || end == "selfClose" || !start || cmp(iter, pos) > 0) return; + var here = {from: Pos(iter.line, iter.ch), to: to, tag: start[2]}; if (start[1]) { // closing tag - return findMatchingOpen(iter, start[2]); + return {open: findMatchingOpen(iter, start[2]), close: here, at: "close"}; } else { // opening tag - toTagEnd(iter); - return findMatchingClose(iter, start[2]); + iter = new Iter(cm, to.line, to.ch, range); + return {open: here, close: findMatchingClose(iter, start[2]), at: "open"}; } }; - CodeMirror.findEnclosingTag = function(cm, pos) { - var iter = new Iter(cm, pos.line, pos.ch); + CodeMirror.findEnclosingTag = function(cm, pos, range) { + var iter = new Iter(cm, pos.line, pos.ch, range); for (;;) { var open = findMatchingOpen(iter); if (!open) break; - var forward = new Iter(cm, pos.line, pos.ch); + var forward = new Iter(cm, pos.line, pos.ch, range); var close = findMatchingClose(forward, open.tag); if (close) return {open: open, close: close}; } diff --git a/addon/hint/anyword-hint.js b/addon/hint/anyword-hint.js new file mode 100644 index 0000000000..36ff618e09 --- /dev/null +++ b/addon/hint/anyword-hint.js @@ -0,0 +1,34 @@ +(function() { + "use strict"; + + var WORD = /[\w$]+/, RANGE = 500; + + CodeMirror.registerHelper("hint", "anyword", function(editor, options) { + var word = options && options.word || WORD; + var range = options && options.range || RANGE; + var cur = editor.getCursor(), curLine = editor.getLine(cur.line); + var start = cur.ch, end = start; + while (end < curLine.length && word.test(curLine.charAt(end))) ++end; + while (start && word.test(curLine.charAt(start - 1))) --start; + var curWord = start != end && curLine.slice(start, end); + + var list = [], seen = {}; + function scan(dir) { + var line = cur.line, end = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir; + for (; line != end; line += dir) { + var text = editor.getLine(line), m; + var re = new RegExp(word.source, "g"); + while (m = re.exec(text)) { + if (line == cur.line && m[0] === curWord) continue; + if ((!curWord || m[0].indexOf(curWord) == 0) && !seen.hasOwnProperty(m[0])) { + seen[m[0]] = true; + list.push(m[0]); + } + } + } + } + scan(-1); + scan(1); + return {list: list, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)}; + }); +})(); diff --git a/addon/hint/html-hint.js b/addon/hint/html-hint.js index 23238df054..cf256851ef 100755 --- a/addon/hint/html-hint.js +++ b/addon/hint/html-hint.js @@ -327,9 +327,11 @@ populate(data[tag]); CodeMirror.htmlSchema = data; - CodeMirror.htmlHint = function(cm, options) { + function htmlHint(cm, options) { var local = {schemaInfo: data}; if (options) for (var opt in options) local[opt] = options[opt]; - return CodeMirror.xmlHint(cm, local); - }; + return CodeMirror.hint.xml(cm, local); + } + CodeMirror.htmlHint = htmlHint; // deprecated + CodeMirror.registerHelper("hint", "html", htmlHint); })(); diff --git a/addon/hint/javascript-hint.js b/addon/hint/javascript-hint.js index b961c5abe9..042fe1325d 100644 --- a/addon/hint/javascript-hint.js +++ b/addon/hint/javascript-hint.js @@ -56,11 +56,13 @@ to: Pos(cur.line, token.end)}; } - CodeMirror.javascriptHint = function(editor, options) { + function javascriptHint(editor, options) { return scriptHint(editor, javascriptKeywords, function (e, cur) {return e.getTokenAt(cur);}, options); }; + CodeMirror.javascriptHint = javascriptHint; // deprecated + CodeMirror.registerHelper("hint", "javascript", javascriptHint); function getCoffeeScriptToken(editor, cur) { // This getToken, it is for coffeescript, imitates the behavior of @@ -80,9 +82,11 @@ return token; } - CodeMirror.coffeescriptHint = function(editor, options) { + function coffeescriptHint(editor, options) { return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options); - }; + } + CodeMirror.coffeescriptHint = coffeescriptHint; // deprecated + CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint); var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " + "toUpperCase toLowerCase split concat match replace search").split(" "); diff --git a/addon/hint/pig-hint.js b/addon/hint/pig-hint.js index d831ccfe93..155973f22d 100644 --- a/addon/hint/pig-hint.js +++ b/addon/hint/pig-hint.js @@ -41,9 +41,11 @@ to: CodeMirror.Pos(cur.line, token.end)}; } - CodeMirror.pigHint = function(editor) { + function pigHint(editor) { return scriptHint(editor, pigKeywordsU, function (e, cur) {return e.getTokenAt(cur);}); - }; + } + CodeMirror.pigHint = pigHint; // deprecated + CodeMirror.registerHelper("hint", "pig", hinter); var pigKeywords = "VOID IMPORT RETURNS DEFINE LOAD FILTER FOREACH ORDER CUBE DISTINCT COGROUP " + "JOIN CROSS UNION SPLIT INTO IF OTHERWISE ALL AS BY USING INNER OUTER ONSCHEMA PARALLEL " diff --git a/addon/hint/python-hint.js b/addon/hint/python-hint.js index 60221b89ec..98d2a5897f 100644 --- a/addon/hint/python-hint.js +++ b/addon/hint/python-hint.js @@ -41,9 +41,11 @@ to: CodeMirror.Pos(cur.line, token.end)}; } - CodeMirror.pythonHint = function(editor) { + function pythonHint(editor) { return scriptHint(editor, pythonKeywordsU, function (e, cur) {return e.getTokenAt(cur);}); - }; + } + CodeMirror.pythonHint = pythonHint; // deprecated + CodeMirror.registerHelper("hint", "python", pythonHint); var pythonKeywords = "and del from not while as elif global or with assert else if pass yield" + "break except import print class exec in raise continue finally is return def for lambda try"; diff --git a/addon/hint/show-hint.js b/addon/hint/show-hint.js index 35e5cbb721..300f68831d 100644 --- a/addon/hint/show-hint.js +++ b/addon/hint/show-hint.js @@ -4,6 +4,8 @@ CodeMirror.showHint = function(cm, getHints, options) { // We want a single cursor position. if (cm.somethingSelected()) return; + if (getHints == null) getHints = cm.getHelper(cm.getCursor(), "hint"); + if (getHints == null) return; if (cm.state.completionActive) cm.state.completionActive.close(); @@ -25,10 +27,10 @@ Completion.prototype = { close: function() { if (!this.active()) return; + this.cm.state.completionActive = null; if (this.widget) this.widget.close(); if (this.onClose) this.onClose(); - this.cm.state.completionActive = null; CodeMirror.signal(this.cm, "endCompletion", this.cm); }, @@ -74,12 +76,14 @@ function update() { if (isDone()) return; + CodeMirror.signal(data, "update"); if (completion.options.async) completion.getHints(completion.cm, finishUpdate, completion.options); else finishUpdate(completion.getHints(completion.cm, completion.options)); } - function finishUpdate(data) { + function finishUpdate(data_) { + data = data_; if (isDone()) return; if (!data || !data.list.length) return done(); completion.widget.close(); diff --git a/addon/hint/xml-hint.js b/addon/hint/xml-hint.js index ea5b8d546a..b6c1da2ce2 100644 --- a/addon/hint/xml-hint.js +++ b/addon/hint/xml-hint.js @@ -3,7 +3,7 @@ var Pos = CodeMirror.Pos; - CodeMirror.xmlHint = function(cm, options) { + function getHints(cm, options) { var tags = options && options.schemaInfo; var quote = (options && options.quoteChar) || '"'; if (!tags) return; @@ -61,5 +61,8 @@ from: replaceToken ? Pos(cur.line, token.start) : cur, to: replaceToken ? Pos(cur.line, token.end) : cur }; - }; + } + + CodeMirror.xmlHint = getHints; // deprecated + CodeMirror.registerHelper("hint", "xml", getHints); })(); diff --git a/addon/lint/coffeescript-lint.js b/addon/lint/coffeescript-lint.js index 9c30de51c2..75f8db6565 100644 --- a/addon/lint/coffeescript-lint.js +++ b/addon/lint/coffeescript-lint.js @@ -1,6 +1,6 @@ // Depends on coffeelint.js from http://www.coffeelint.org/js/coffeelint.js -CodeMirror.coffeeValidator = function(text) { +CodeMirror.registerHelper("lint", "coffeescript", function(text) { var found = []; var parseError = function(err) { var loc = err.lineNumber; @@ -21,4 +21,5 @@ CodeMirror.coffeeValidator = function(text) { message: e.message}); } return found; -}; +}); +CodeMirror.coffeeValidator = CodeMirror.lint.coffeescript; // deprecated diff --git a/addon/lint/javascript-lint.js b/addon/lint/javascript-lint.js index 9a815c824e..d7a6f6af28 100644 --- a/addon/lint/javascript-lint.js +++ b/addon/lint/javascript-lint.js @@ -9,18 +9,15 @@ "Unmatched ", " and instead saw", " is not defined", "Unclosed string", "Stopping, unable to continue" ]; - function validator(options, text) { + function validator(text, options) { JSHINT(text, options); var errors = JSHINT.data().errors, result = []; if (errors) parseErrors(errors, result); return result; } - CodeMirror.javascriptValidatorWithOptions = function(options) { - return function(text) { return validator(options, text); }; - }; - - CodeMirror.javascriptValidator = CodeMirror.javascriptValidatorWithOptions(null); + CodeMirror.registerHelper("lint", "javascript", validator); + CodeMirror.javascriptValidator = CodeMirror.lint.javascript; // deprecated function cleanup(error) { // All problems are warnings by default diff --git a/addon/lint/json-lint.js b/addon/lint/json-lint.js index 42b36abb39..1dfc6b8fdc 100644 --- a/addon/lint/json-lint.js +++ b/addon/lint/json-lint.js @@ -1,6 +1,6 @@ // Depends on jsonlint.js from https://github.com/zaach/jsonlint -CodeMirror.jsonValidator = function(text) { +CodeMirror.registerHelper("lint", "json", function(text) { var found = []; jsonlint.parseError = function(str, hash) { var loc = hash.loc; @@ -11,4 +11,5 @@ CodeMirror.jsonValidator = function(text) { try { jsonlint.parse(text); } catch(e) {} return found; -}; +}); +CodeMirror.jsonValidator = CodeMirror.lint.json; // deprecated diff --git a/addon/lint/lint.js b/addon/lint/lint.js index 2e7cea1925..67a92970a3 100644 --- a/addon/lint/lint.js +++ b/addon/lint/lint.js @@ -1,4 +1,5 @@ -CodeMirror.validate = (function() { +(function() { + "use strict"; var GUTTER_ID = "CodeMirror-lint-markers"; var SEVERITIES = /^(?:error|warning)$/; @@ -52,9 +53,11 @@ CodeMirror.validate = (function() { this.onMouseOver = function(e) { onMouseOver(cm, e); }; } - function parseOptions(options) { + function parseOptions(cm, options) { if (options instanceof Function) return {getAnnotations: options}; - else if (!options || !options.getAnnotations) throw new Error("Required option 'getAnnotations' missing (lint addon)"); + if (!options || options === true) options = {}; + if (!options.getAnnotations) options.getAnnotations = cm.getHelper(CodeMirror.Pos(0, 0), "lint"); + if (!options.getAnnotations) throw new Error("Required option 'getAnnotations' missing (lint addon)"); return options; } @@ -175,7 +178,7 @@ CodeMirror.validate = (function() { } } - CodeMirror.defineOption("lintWith", false, function(cm, val, old) { + function optionHandler(cm, val, old) { if (old && old != CodeMirror.Init) { clearMarks(cm); cm.off("change", onChange); @@ -186,12 +189,15 @@ CodeMirror.validate = (function() { if (val) { var gutters = cm.getOption("gutters"), hasLintGutter = false; for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true; - var state = cm.state.lint = new LintState(cm, parseOptions(val), hasLintGutter); + var state = cm.state.lint = new LintState(cm, parseOptions(cm, val), hasLintGutter); cm.on("change", onChange); if (state.options.tooltips != false) CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver); startLinting(cm); } - }); + } + + CodeMirror.defineOption("lintWith", false, optionHandler); // deprecated + CodeMirror.defineOption("lint", false, optionHandler); // deprecated })(); diff --git a/addon/merge/merge.css b/addon/merge/merge.css index 1f20f88a43..63237fc8e9 100644 --- a/addon/merge/merge.css +++ b/addon/merge/merge.css @@ -1,24 +1,34 @@ -.CodeMirror-diff { +.CodeMirror-merge { position: relative; border: 1px solid #ddd; + white-space: pre; } -.CodeMirror-diff, .CodeMirror-diff .CodeMirror { +.CodeMirror-merge, .CodeMirror-merge .CodeMirror { height: 350px; } -.CodeMirror-diff-2pane .CodeMirror-diff-pane { width: 47%; } -.CodeMirror-diff-2pane .CodeMirror-diff-gap { width: 6%; } -.CodeMirror-diff-3pane .CodeMirror-diff-pane { width: 31%; } -.CodeMirror-diff-3pane .CodeMirror-diff-gap { width: 3.5%; } +.CodeMirror-merge-2pane .CodeMirror-merge-pane { width: 47%; } +.CodeMirror-merge-2pane .CodeMirror-merge-gap { width: 6%; } +.CodeMirror-merge-3pane .CodeMirror-merge-pane { width: 31%; } +.CodeMirror-merge-3pane .CodeMirror-merge-gap { width: 3.5%; } -.CodeMirror-diff-pane { - float: left; +.CodeMirror-merge-pane { + display: inline-block; + white-space: normal; + vertical-align: top; +} +.CodeMirror-merge-pane-rightmost { + position: absolute; + right: 0px; + z-index: 1; } -.CodeMirror-diff-gap { - float: left; +.CodeMirror-merge-gap { + z-index: 2; + display: inline-block; height: 100%; + -moz-box-sizing: border-box; box-sizing: border-box; overflow: hidden; border-left: 1px solid #ddd; @@ -27,11 +37,11 @@ background: #f8f8f8; } -.CodeMirror-diff-scrolllock-wrap { +.CodeMirror-merge-scrolllock-wrap { position: absolute; bottom: 0; left: 50%; } -.CodeMirror-diff-scrolllock { +.CodeMirror-merge-scrolllock { position: relative; left: -50%; cursor: pointer; @@ -39,44 +49,44 @@ line-height: 1; } -.CodeMirror-diff-copybuttons-left, .CodeMirror-diff-copybuttons-right { +.CodeMirror-merge-copybuttons-left, .CodeMirror-merge-copybuttons-right { position: absolute; left: 0; top: 0; right: 0; bottom: 0; line-height: 1; } -.CodeMirror-diff-copy { +.CodeMirror-merge-copy { position: absolute; cursor: pointer; color: #44c; } -.CodeMirror-diff-copybuttons-left .CodeMirror-diff-copy { left: 2px; } -.CodeMirror-diff-copybuttons-right .CodeMirror-diff-copy { right: 2px; } +.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy { left: 2px; } +.CodeMirror-merge-copybuttons-right .CodeMirror-merge-copy { right: 2px; } -.CodeMirror-diff-r-inserted, .CodeMirror-diff-l-inserted { +.CodeMirror-merge-r-inserted, .CodeMirror-merge-l-inserted { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12MwuCXy3+CWyH8GBgYGJgYkAABZbAQ9ELXurwAAAABJRU5ErkJggg==); background-position: bottom left; background-repeat: repeat-x; } -.CodeMirror-diff-r-deleted, .CodeMirror-diff-l-deleted { +.CodeMirror-merge-r-deleted, .CodeMirror-merge-l-deleted { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12M4Kyb2/6yY2H8GBgYGJgYkAABURgPz6Ks7wQAAAABJRU5ErkJggg==); background-position: bottom left; background-repeat: repeat-x; } -.CodeMirror-diff-r-chunk { background: #ffffe0; } -.CodeMirror-diff-r-chunk-start { border-top: 1px solid #ee8; } -.CodeMirror-diff-r-chunk-end { border-bottom: 1px solid #ee8; } -.CodeMirror-diff-r-connect { fill: #ffffe0; stroke: #ee8; stroke-width: 1px; } +.CodeMirror-merge-r-chunk { background: #ffffe0; } +.CodeMirror-merge-r-chunk-start { border-top: 1px solid #ee8; } +.CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #ee8; } +.CodeMirror-merge-r-connect { fill: #ffffe0; stroke: #ee8; stroke-width: 1px; } -.CodeMirror-diff-l-chunk { background: #eef; } -.CodeMirror-diff-l-chunk-start { border-top: 1px solid #88e; } -.CodeMirror-diff-l-chunk-end { border-bottom: 1px solid #88e; } -.CodeMirror-diff-l-connect { fill: #eef; stroke: #88e; stroke-width: 1px; } +.CodeMirror-merge-l-chunk { background: #eef; } +.CodeMirror-merge-l-chunk-start { border-top: 1px solid #88e; } +.CodeMirror-merge-l-chunk-end { border-bottom: 1px solid #88e; } +.CodeMirror-merge-l-connect { fill: #eef; stroke: #88e; stroke-width: 1px; } -.CodeMirror-diff-l-chunk.CodeMirror-diff-r-chunk { background: #dfd; } -.CodeMirror-diff-l-chunk-start.CodeMirror-diff-r-chunk-start { border-top: 1px solid #4e4; } -.CodeMirror-diff-l-chunk-end.CodeMirror-diff-r-chunk-end { border-bottom: 1px solid #4e4; } +.CodeMirror-merge-l-chunk.CodeMirror-merge-r-chunk { background: #dfd; } +.CodeMirror-merge-l-chunk-start.CodeMirror-merge-r-chunk-start { border-top: 1px solid #4e4; } +.CodeMirror-merge-l-chunk-end.CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #4e4; } diff --git a/addon/merge/merge.js b/addon/merge/merge.js index b112518596..16c3356c20 100644 --- a/addon/merge/merge.js +++ b/addon/merge/merge.js @@ -8,18 +8,18 @@ this.mv = mv; this.type = type; this.classes = type == "left" - ? {chunk: "CodeMirror-diff-l-chunk", - start: "CodeMirror-diff-l-chunk-start", - end: "CodeMirror-diff-l-chunk-end", - insert: "CodeMirror-diff-l-inserted", - del: "CodeMirror-diff-l-deleted", - connect: "CodeMirror-diff-l-connect"} - : {chunk: "CodeMirror-diff-r-chunk", - start: "CodeMirror-diff-r-chunk-start", - end: "CodeMirror-diff-r-chunk-end", - insert: "CodeMirror-diff-r-inserted", - del: "CodeMirror-diff-r-deleted", - connect: "CodeMirror-diff-r-connect"}; + ? {chunk: "CodeMirror-merge-l-chunk", + start: "CodeMirror-merge-l-chunk-start", + end: "CodeMirror-merge-l-chunk-end", + insert: "CodeMirror-merge-l-inserted", + del: "CodeMirror-merge-l-deleted", + connect: "CodeMirror-merge-l-connect"} + : {chunk: "CodeMirror-merge-r-chunk", + start: "CodeMirror-merge-r-chunk-start", + end: "CodeMirror-merge-r-chunk-end", + insert: "CodeMirror-merge-r-inserted", + del: "CodeMirror-merge-r-deleted", + connect: "CodeMirror-merge-r-connect"}; } DiffView.prototype = { @@ -93,7 +93,21 @@ var off = getOffsets(editor, type == DIFF_INSERT ? around.edit : around.orig); var offOther = getOffsets(other, type == DIFF_INSERT ? around.orig : around.edit); var ratio = (midY - off.top) / (off.bot - off.top); - other.scrollTo(null, (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top)); + var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top); + + var botDist, mix; + // Some careful tweaking to make sure no space is left out of view + // when scrolling to top or bottom. + if (targetPos > sInfo.top && (mix = sInfo.top / halfScreen) < 1) { + targetPos = targetPos * mix + sInfo.top * (1 - mix); + } else if ((botDist = sInfo.height - sInfo.clientHeight - sInfo.top) < halfScreen) { + var otherInfo = other.getScrollInfo(); + var botDistOther = otherInfo.height - otherInfo.clientHeight - targetPos; + if (botDistOther > botDist && (mix = botDist / halfScreen) < 1) + targetPos = targetPos * mix + (otherInfo.height - otherInfo.clientHeight - botDist) * (1 - mix); + } + + other.scrollTo(sInfo.left, targetPos); other.state.scrollSetAt = now; other.state.scrollSetBy = dv; return true; @@ -208,8 +222,8 @@ var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport(); var sTopEdit = dv.edit.getScrollInfo().top, sTopOrig = dv.orig.getScrollInfo().top; iterateChunks(dv.diff, function(topOrig, botOrig, topEdit, botEdit) { - if (topEdit >= vpEdit.to || botEdit < vpEdit.from || - topOrig >= vpOrig.to || botOrig < vpOrig.from) + if (topEdit > vpEdit.to || botEdit < vpEdit.from || + topOrig > vpOrig.to || botOrig < vpOrig.from) return; var topLpx = dv.orig.heightAtLine(topOrig, "local") - sTopOrig, top = topLpx; if (dv.svg) { @@ -225,7 +239,7 @@ "class", dv.classes.connect); } var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc", - "CodeMirror-diff-copy")); + "CodeMirror-merge-copy")); copy.title = "Revert chunk"; copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig}; copy.style.top = top + "px"; @@ -250,23 +264,25 @@ if (hasLeft) { left = this.left = new DiffView(this, "left"); - var leftPane = elt("div", null, "CodeMirror-diff-pane"); + var leftPane = elt("div", null, "CodeMirror-merge-pane"); wrap.push(leftPane); wrap.push(buildGap(left)); } - var editPane = elt("div", null, "CodeMirror-diff-pane"); + var editPane = elt("div", null, "CodeMirror-merge-pane"); wrap.push(editPane); if (hasRight) { right = this.right = new DiffView(this, "right"); wrap.push(buildGap(right)); - var rightPane = elt("div", null, "CodeMirror-diff-pane"); + var rightPane = elt("div", null, "CodeMirror-merge-pane"); wrap.push(rightPane); } + (hasRight ? rightPane : editPane).className += " CodeMirror-merge-pane-rightmost"; + wrap.push(elt("div", null, null, "height: 0; clear: both;")); - var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-diff CodeMirror-diff-" + panes + "pane")); + var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-merge CodeMirror-merge-" + panes + "pane")); this.edit = CodeMirror(editPane, copyObj(options)); if (left) left.init(leftPane, origLeft, options); @@ -284,11 +300,11 @@ }; function buildGap(dv) { - var lock = dv.lockButton = elt("div", null, "CodeMirror-diff-scrolllock"); + var lock = dv.lockButton = elt("div", null, "CodeMirror-merge-scrolllock"); lock.title = "Toggle locked scrolling"; - var lockWrap = elt("div", [lock], "CodeMirror-diff-scrolllock-wrap"); + var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap"); CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); }); - dv.copyButtons = elt("div", null, "CodeMirror-diff-copybuttons-" + dv.type); + dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type); CodeMirror.on(dv.copyButtons, "click", function(e) { var node = e.target || e.srcElement; if (node.chunk) copyChunk(dv, node.chunk); @@ -299,7 +315,7 @@ dv.svg = svg; if (svg) gapElts.push(svg); - return dv.gap = elt("div", gapElts, "CodeMirror-diff-gap"); + return dv.gap = elt("div", gapElts, "CodeMirror-merge-gap"); } MergeView.prototype = { diff --git a/addon/runmode/runmode-standalone.js b/addon/runmode/runmode-standalone.js index 7a9b82ffa8..5be7d74405 100644 --- a/addon/runmode/runmode-standalone.js +++ b/addon/runmode/runmode-standalone.js @@ -43,12 +43,14 @@ StringStream.prototype = { match: function(pattern, consume, caseInsensitive) { if (typeof pattern == "string") { var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; - if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { + var substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { if (consume !== false) this.pos += pattern.length; return true; } } else { var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) return null; if (match && consume !== false) this.pos += match[0].length; return match; } diff --git a/addon/runmode/runmode.node.js b/addon/runmode/runmode.node.js index a6ea919d83..ffdcc16a83 100644 --- a/addon/runmode/runmode.node.js +++ b/addon/runmode/runmode.node.js @@ -41,12 +41,14 @@ StringStream.prototype = { match: function(pattern, consume, caseInsensitive) { if (typeof pattern == "string") { var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; - if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { + var substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { if (consume !== false) this.pos += pattern.length; return true; } } else { var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) return null; if (match && consume !== false) this.pos += match[0].length; return match; } diff --git a/addon/search/match-highlighter.js b/addon/search/match-highlighter.js index 212167580a..3df6985984 100644 --- a/addon/search/match-highlighter.js +++ b/addon/search/match-highlighter.js @@ -55,11 +55,13 @@ cm.removeOverlay(state.overlay); state.overlay = null; } - if (!cm.somethingSelected() && state.showToken) { - var tok = cm.getTokenAt(cm.getCursor()).string; - if (/\w/.test(tok)) - cm.addOverlay(state.overlay = makeOverlay(tok, true, state.style)); + var re = state.showToken === true ? /[\w$]/ : state.showToken; + var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start; + while (start && re.test(line.charAt(start - 1))) --start; + while (end < line.length && re.test(line.charAt(end))) ++end; + if (start < end) + cm.addOverlay(state.overlay = makeOverlay(line.slice(start, end), re, state.style)); return; } if (cm.getCursor("head").line != cm.getCursor("anchor").line) return; @@ -69,15 +71,15 @@ }); } - function boundariesAround(stream) { - return (stream.start || /.\b./.test(stream.string.slice(stream.start - 1, stream.start + 1))) && - (stream.pos == stream.string.length || /.\b./.test(stream.string.slice(stream.pos - 1, stream.pos + 1))); + function boundariesAround(stream, re) { + return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) && + (stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos))); } - function makeOverlay(query, wordBoundaries, style) { + function makeOverlay(query, hasBoundary, style) { return {token: function(stream) { if (stream.match(query) && - (!wordBoundaries || boundariesAround(stream))) + (!hasBoundary || boundariesAround(stream, hasBoundary))) return style; stream.next(); stream.skipTo(query.charAt(0)) || stream.skipToEnd(); diff --git a/addon/selection/active-line.js b/addon/selection/active-line.js index 65fab6f162..e50508653a 100644 --- a/addon/selection/active-line.js +++ b/addon/selection/active-line.js @@ -29,7 +29,7 @@ } function updateActiveLine(cm) { - var line = cm.getLineHandle(cm.getCursor().line); + var line = cm.getLineHandleVisualStart(cm.getCursor().line); if (cm.state.activeLine == line) return; clearActiveLine(cm); cm.addLineClass(line, "wrap", WRAP_CLASS); diff --git a/addon/tern/tern.css b/addon/tern/tern.css new file mode 100644 index 0000000000..eacc2f053a --- /dev/null +++ b/addon/tern/tern.css @@ -0,0 +1,85 @@ +.CodeMirror-Tern-completion { + padding-left: 22px; + position: relative; +} +.CodeMirror-Tern-completion:before { + position: absolute; + left: 2px; + bottom: 2px; + border-radius: 50%; + font-size: 12px; + font-weight: bold; + height: 15px; + width: 15px; + line-height: 16px; + text-align: center; + color: white; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.CodeMirror-Tern-completion-unknown:before { + content: "?"; + background: #4bb; +} +.CodeMirror-Tern-completion-object:before { + content: "O"; + background: #77c; +} +.CodeMirror-Tern-completion-fn:before { + content: "F"; + background: #7c7; +} +.CodeMirror-Tern-completion-array:before { + content: "A"; + background: #c66; +} +.CodeMirror-Tern-completion-number:before { + content: "1"; + background: #999; +} +.CodeMirror-Tern-completion-string:before { + content: "S"; + background: #999; +} +.CodeMirror-Tern-completion-bool:before { + content: "B"; + background: #999; +} + +.CodeMirror-Tern-completion-guess { + color: #999; +} + +.CodeMirror-Tern-tooltip { + border: 1px solid silver; + border-radius: 3px; + color: #444; + padding: 2px 5px; + font-size: 90%; + font-family: monospace; + background-color: white; + white-space: pre-wrap; + + max-width: 40em; + position: absolute; + z-index: 10; + -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2); + -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2); + box-shadow: 2px 3px 5px rgba(0,0,0,.2); + + transition: opacity 1s; + -moz-transition: opacity 1s; + -webkit-transition: opacity 1s; + -o-transition: opacity 1s; + -ms-transition: opacity 1s; +} + +.CodeMirror-Tern-hint-doc { + max-width: 25em; +} + +.CodeMirror-Tern-fname { color: black; } +.CodeMirror-Tern-farg { color: #70a; } +.CodeMirror-Tern-farg-current { text-decoration: underline; } +.CodeMirror-Tern-type { color: #07c; } +.CodeMirror-Tern-fhint-guess { opacity: .7; } diff --git a/addon/tern/tern.js b/addon/tern/tern.js new file mode 100644 index 0000000000..1be5ba7f5d --- /dev/null +++ b/addon/tern/tern.js @@ -0,0 +1,608 @@ +// Glue code between CodeMirror and Tern. +// +// Create a CodeMirror.TernServer to wrap an actual Tern server, +// register open documents (CodeMirror.Doc instances) with it, and +// call its methods to activate the assisting functions that Tern +// provides. +// +// Options supported (all optional): +// * defs: An array of JSON definition data structures. +// * plugins: An object mapping plugin names to configuration +// options. +// * getFile: A function(name, c) that can be used to access files in +// the project that haven't been loaded yet. Simply do c(null) to +// indicate that a file is not available. +// * fileFilter: A function(value, docName, doc) that will be applied +// to documents before passing them on to Tern. +// * switchToDoc: A function(name) that should, when providing a +// multi-file view, switch the view or focus to the named file. +// * showError: A function(editor, message) that can be used to +// override the way errors are displayed. +// * completionTip: Customize the content in tooltips for completions. +// Is passed a single argument—the completion's data as returned by +// Tern—and may return a string, DOM node, or null to indicate that +// no tip should be shown. By default the docstring is shown. +// * typeTip: Like completionTip, but for the tooltips shown for type +// queries. +// +// It is possible to run the Tern server in a web worker by specifying +// these additional options: +// * useWorker: Set to true to enable web worker mode. You'll probably +// want to feature detect the actual value you use here, for example +// !!window.Worker. +// * workerScript: The main script of the worker. Point this to +// wherever you are hosting worker.js from this directory. +// * workerDeps: An array of paths pointing (relative to workerScript) +// to the Acorn and Tern libraries and any Tern plugins you want to +// load. Or, if you minified those into a single script and included +// them in the workerScript, simply leave this undefined. + +(function() { + "use strict"; + + CodeMirror.TernServer = function(options) { + var self = this; + this.options = options || {}; + var plugins = this.options.plugins || (this.options.plugins = {}); + if (!plugins.doc_comment) plugins.doc_comment = true; + if (this.options.useWorker) { + this.server = new WorkerServer(this); + } else { + this.server = new tern.Server({ + getFile: function(name, c) { return getFile(self, name, c); }, + async: true, + defs: this.options.defs || [], + plugins: plugins + }); + } + this.docs = Object.create(null); + this.trackChange = function(doc, change) { trackChange(self, doc, change); }; + + this.cachedArgHints = null; + this.activeArgHints = null; + this.jumpStack = []; + }; + + CodeMirror.TernServer.prototype = { + addDoc: function(name, doc) { + var data = {doc: doc, name: name, changed: null}; + this.server.addFile(name, docValue(this, data)); + CodeMirror.on(doc, "change", this.trackChange); + return this.docs[name] = data; + }, + + delDoc: function(name) { + var found = this.docs[name]; + if (!found) return; + CodeMirror.off(found.doc, "change", this.trackChange); + delete this.docs[name]; + this.server.delFile(name); + }, + + hideDoc: function(name) { + closeArgHints(this); + var found = this.docs[name]; + if (found && found.changed) sendDoc(this, found); + }, + + complete: function(cm) { + var self = this; + CodeMirror.showHint(cm, function(cm, c) { return hint(self, cm, c); }, {async: true}); + }, + + getHint: function(cm, c) { return hint(this, cm, c); }, + + showType: function(cm) { showType(this, cm); }, + + updateArgHints: function(cm) { updateArgHints(this, cm); }, + + jumpToDef: function(cm) { jumpToDef(this, cm); }, + + jumpBack: function(cm) { jumpBack(this, cm); }, + + rename: function(cm) { rename(this, cm); }, + + request: function(cm, query, c) { + this.server.request(buildRequest(this, findDoc(this, cm.getDoc()), query), c); + } + }; + + var Pos = CodeMirror.Pos; + var cls = "CodeMirror-Tern-"; + var bigDoc = 250; + + function getFile(ts, name, c) { + var buf = ts.docs[name]; + if (buf) + c(docValue(ts, buf)); + else if (ts.options.getFile) + ts.options.getFile(name, c); + else + c(null); + } + + function findDoc(ts, doc, name) { + for (var n in ts.docs) { + var cur = ts.docs[n]; + if (cur.doc == doc) return cur; + } + if (!name) for (var i = 0;; ++i) { + n = "[doc" + (i || "") + "]"; + if (!ts.docs[n]) { name = n; break; } + } + return ts.addDoc(name, doc); + } + + function trackChange(ts, doc, change) { + var data = findDoc(ts, doc); + + var argHints = ts.cachedArgHints; + if (argHints && argHints.doc == doc && cmpPos(argHints.start, change.to) <= 0) + ts.cachedArgHints = null; + + var changed = data.changed; + if (changed == null) + data.changed = changed = {from: change.from.line, to: change.from.line}; + var end = change.from.line + (change.text.length - 1); + if (change.from.line < changed.to) changed.to = changed.to - (change.to.line - end); + if (end >= changed.to) changed.to = end + 1; + if (changed.from > change.from.line) changed.from = change.from.line; + + if (doc.lineCount() > bigDoc && change.to - changed.from > 100) setTimeout(function() { + if (data.changed && data.changed.to - data.changed.from > 100) sendDoc(ts, data); + }, 200); + } + + function sendDoc(ts, doc) { + ts.server.request({files: [{type: "full", name: doc.name, text: docValue(ts, doc)}]}, function(error) { + if (error) console.error(error); + else doc.changed = null; + }); + } + + // Completion + + function hint(ts, cm, c) { + ts.request(cm, {type: "completions", types: true, docs: true, urls: true}, function(error, data) { + if (error) return showError(ts, cm, error); + var completions = [], after = ""; + var from = data.start, to = data.end; + if (cm.getRange(Pos(from.line, from.ch - 2), from) == "[\"" && + cm.getRange(to, Pos(to.line, to.ch + 2)) != "\"]") + after = "\"]"; + + for (var i = 0; i < data.completions.length; ++i) { + var completion = data.completions[i], className = typeToIcon(completion.type); + if (data.guess) className += " " + cls + "guess"; + completions.push({text: completion.name + after, + displayText: completion.name, + className: className, + data: completion}); + } + + var obj = {from: from, to: to, list: completions}; + var tooltip = null; + CodeMirror.on(obj, "close", function() { remove(tooltip); }); + CodeMirror.on(obj, "update", function() { remove(tooltip); }); + CodeMirror.on(obj, "select", function(cur, node) { + remove(tooltip); + var content = ts.options.completionTip ? ts.options.completionTip(cur.data) : cur.data.doc; + if (content) { + tooltip = makeTooltip(node.parentNode.getBoundingClientRect().right + window.pageXOffset, + node.getBoundingClientRect().top + window.pageYOffset, content); + tooltip.className += " " + cls + "hint-doc"; + } + }); + c(obj); + }); + } + + function typeToIcon(type) { + var suffix; + if (type == "?") suffix = "unknown"; + else if (type == "number" || type == "string" || type == "bool") suffix = type; + else if (/^fn\(/.test(type)) suffix = "fn"; + else if (/^\[/.test(type)) suffix = "array"; + else suffix = "object"; + return cls + "completion " + cls + "completion-" + suffix; + } + + // Type queries + + function showType(ts, cm) { + ts.request(cm, "type", function(error, data) { + if (error) return showError(ts, cm, error); + if (ts.options.typeTip) { + var tip = ts.options.typeTip(data); + } else { + var tip = elt("span", null, elt("strong", null, data.type || "not found")); + if (data.doc) + tip.appendChild(document.createTextNode(" — " + data.doc)); + if (data.url) { + tip.appendChild(document.createTextNode(" ")); + tip.appendChild(elt("a", null, "[docs]")).href = data.url; + } + } + tempTooltip(cm, tip); + }); + } + + // Maintaining argument hints + + function updateArgHints(ts, cm) { + closeArgHints(ts); + + if (cm.somethingSelected()) return; + var lex = cm.getTokenAt(cm.getCursor()).state.lexical; + if (lex.info != "call") return; + + var ch = lex.column, pos = lex.pos || 0; + for (var line = cm.getCursor().line, e = Math.max(0, line - 9), found = false; line >= e; --line) + if (cm.getLine(line).charAt(ch) == "(") {found = true; break;} + if (!found) return; + + var start = Pos(line, ch); + var cache = ts.cachedArgHints; + if (cache && cache.doc == cm.getDoc() && cmpPos(start, cache.start) == 0) + return showArgHints(ts, cm, pos); + + ts.request(cm, {type: "type", preferFunction: true, end: start}, function(error, data) { + if (error || !data.type || !(/^fn\(/).test(data.type)) return; + ts.cachedArgHints = { + start: pos, + type: parseFnType(data.type), + name: data.exprName || data.name || "fn", + guess: data.guess, + doc: cm.getDoc() + }; + showArgHints(ts, cm, pos); + }); + } + + function showArgHints(ts, cm, pos) { + closeArgHints(ts); + + var cache = ts.cachedArgHints, tp = cache.type; + var tip = elt("span", cache.guess ? cls + "fhint-guess" : null, + elt("span", cls + "fname", cache.name), "("); + for (var i = 0; i < tp.args.length; ++i) { + if (i) tip.appendChild(document.createTextNode(", ")); + var arg = tp.args[i]; + tip.appendChild(elt("span", cls + "farg" + (i == pos ? " " + cls + "farg-current" : ""), arg.name || "?")); + if (arg.type != "?") { + tip.appendChild(document.createTextNode(":\u00a0")); + tip.appendChild(elt("span", cls + "type", arg.type)); + } + } + tip.appendChild(document.createTextNode(tp.rettype ? ") ->\u00a0" : ")")); + if (tp.rettype) tip.appendChild(elt("span", cls + "type", tp.rettype)); + var place = cm.cursorCoords(null, "page"); + ts.activeArgHints = makeTooltip(place.right + 1, place.bottom, tip); + } + + function parseFnType(text) { + var args = [], pos = 3; + + function skipMatching(upto) { + var depth = 0, start = pos; + for (;;) { + var next = text.charAt(pos); + if (upto.test(next) && !depth) return text.slice(start, pos); + if (/[{\[\(]/.test(next)) ++depth; + else if (/[}\]\)]/.test(next)) --depth; + ++pos; + } + } + + // Parse arguments + if (text.charAt(pos) != ")") for (;;) { + var name = text.slice(pos).match(/^([^, \(\[\{]+): /); + if (name) { + pos += name[0].length; + name = name[1]; + } + args.push({name: name, type: skipMatching(/[\),]/)}); + if (text.charAt(pos) == ")") break; + pos += 2; + } + + var rettype = text.slice(pos).match(/^\) -> (.*)$/); + + return {args: args, rettype: rettype && rettype[1]}; + } + + // Moving to the definition of something + + function jumpToDef(ts, cm) { + function inner(varName) { + var req = {type: "definition", variable: varName || null}; + var doc = findDoc(ts, cm.getDoc()); + ts.server.request(buildRequest(ts, doc, req), function(error, data) { + if (error) return showError(ts, cm, error); + if (!data.file && data.url) { window.open(data.url); return; } + + if (data.file) { + var localDoc = ts.docs[data.file], found; + if (localDoc && (found = findContext(localDoc.doc, data))) { + ts.jumpStack.push({file: doc.name, + start: cm.getCursor("from"), + end: cm.getCursor("to")}); + moveTo(ts, doc, localDoc, found.start, found.end); + return; + } + } + showError(ts, cm, "Could not find a definition."); + }); + } + + if (!atInterestingExpression(cm)) + dialog(cm, "Jump to variable", function(name) { if (name) inner(name); }); + else + inner(); + } + + function jumpBack(ts, cm) { + var pos = ts.jumpStack.pop(), doc = pos && ts.docs[pos.file]; + if (!doc) return; + moveTo(ts, findDoc(ts, cm.getDoc()), doc, pos.start, pos.end); + } + + function moveTo(ts, curDoc, doc, start, end) { + doc.doc.setSelection(end, start); + if (curDoc != doc && ts.options.switchToDoc) { + closeArgHints(ts); + ts.options.switchToDoc(doc.name); + } + } + + // The {line,ch} representation of positions makes this rather awkward. + function findContext(doc, data) { + var before = data.context.slice(0, data.contextOffset).split("\n"); + var startLine = data.start.line - (before.length - 1); + var start = Pos(startLine, (before.length == 1 ? data.start.ch : doc.getLine(startLine).length) - before[0].length); + + var text = doc.getLine(startLine).slice(start.ch); + for (var cur = startLine + 1; cur < doc.lineCount() && text.length < data.context.length; ++cur) + text += "\n" + doc.getLine(cur); + if (text.slice(0, data.context.length) == data.context) return data; + + var cursor = doc.getSearchCursor(data.context, 0, false); + var nearest, nearestDist = Infinity; + while (cursor.findNext()) { + var from = cursor.from(), dist = Math.abs(from.line - start.line) * 10000; + if (!dist) dist = Math.abs(from.ch - start.ch); + if (dist < nearestDist) { nearest = from; nearestDist = dist; } + } + if (!nearest) return null; + + if (before.length == 1) + nearest.ch += before[0].length; + else + nearest = Pos(nearest.line + (before.length - 1), before[before.length - 1].length); + if (data.start.line == data.end.line) + var end = Pos(nearest.line, nearest.ch + (data.end.ch - data.start.ch)); + else + var end = Pos(nearest.line + (data.end.line - data.start.line), data.end.ch); + return {start: nearest, end: end}; + } + + function atInterestingExpression(cm) { + var pos = cm.getCursor("end"), tok = cm.getTokenAt(pos); + if (tok.start < pos.ch && (tok.type == "comment" || tok.type == "string")) return false; + return /\w/.test(cm.getLine(pos.line).slice(Math.max(pos.ch - 1, 0), pos.ch + 1)); + } + + // Variable renaming + + function rename(ts, cm) { + var token = cm.getTokenAt(cm.getCursor()); + if (!/\w/.test(token.string)) showError(ts, cm, "Not at a variable"); + dialog(cm, "New name for " + token.string, function(newName) { + ts.request(cm, {type: "rename", newName: newName, fullDocs: true}, function(error, data) { + if (error) return showError(ts, cm, error); + applyChanges(ts, data.changes); + }); + }); + } + + var nextChangeOrig = 0; + function applyChanges(ts, changes) { + var perFile = Object.create(null); + for (var i = 0; i < changes.length; ++i) { + var ch = changes[i]; + (perFile[ch.file] || (perFile[ch.file] = [])).push(ch); + } + for (var file in perFile) { + var known = ts.docs[file], chs = perFile[file];; + if (!known) continue; + chs.sort(function(a, b) { return cmpPos(b, a); }); + var origin = "*rename" + (++nextChangeOrig); + for (var i = 0; i < chs.length; ++i) { + var ch = chs[i]; + known.doc.replaceRange(ch.text, ch.start, ch.end, origin); + } + } + } + + // Generic request-building helper + + function buildRequest(ts, doc, query) { + var files = [], offsetLines = 0, allowFragments = !query.fullDocs; + if (!allowFragments) delete query.fullDocs; + if (typeof query == "string") query = {type: query}; + query.lineCharPositions = true; + if (query.end == null) { + query.end = doc.doc.getCursor("end"); + if (doc.doc.somethingSelected()) + query.start = doc.doc.getCursor("start"); + } + var startPos = query.start || query.end; + + if (doc.changed) { + if (doc.doc.lineCount() > bigDoc && allowFragments !== false && + doc.changed.to - doc.changed.from < 100 && + doc.changed.from <= startPos.line && doc.changed.to > query.end.line) { + files.push(getFragmentAround(doc, startPos, query.end)); + query.file = "#0"; + var offsetLines = files[0].offsetLines; + if (query.start != null) query.start = Pos(query.start.line - -offsetLines, query.start.ch); + query.end = Pos(query.end.line - offsetLines, query.end.ch); + } else { + files.push({type: "full", + name: doc.name, + text: docValue(ts, doc)}); + query.file = doc.name; + doc.changed = null; + } + } else { + query.file = doc.name; + } + for (var name in ts.docs) { + var cur = ts.docs[name]; + if (cur.changed && cur != doc) { + files.push({type: "full", name: cur.name, text: docValue(ts, cur)}); + cur.changed = null; + } + } + + return {query: query, files: files}; + } + + function getFragmentAround(data, start, end) { + var doc = data.doc; + var minIndent = null, minLine = null, endLine, tabSize = 4; + for (var p = start.line - 1, min = Math.max(0, p - 50); p >= min; --p) { + var line = doc.getLine(p), fn = line.search(/\bfunction\b/); + if (fn < 0) continue; + var indent = CodeMirror.countColumn(line, null, tabSize); + if (minIndent != null && minIndent <= indent) continue; + minIndent = indent; + minLine = p; + } + if (minLine == null) minLine = min; + var max = Math.min(doc.lastLine(), end.line + 20); + if (minIndent == null || minIndent == CodeMirror.countColumn(doc.getLine(start.line), null, tabSize)) + endLine = max; + else for (endLine = end.line + 1; endLine < max; ++endLine) { + var indent = CodeMirror.countColumn(doc.getLine(endLine), null, tabSize); + if (indent <= minIndent) break; + } + var from = Pos(minLine, 0); + + return {type: "part", + name: data.name, + offsetLines: from.line, + text: doc.getRange(from, Pos(endLine, 0))}; + } + + // Generic utilities + + function cmpPos(a, b) { return a.line - b.line || a.ch - b.ch; } + + function elt(tagname, cls /*, ... elts*/) { + var e = document.createElement(tagname); + if (cls) e.className = cls; + for (var i = 2; i < arguments.length; ++i) { + var elt = arguments[i]; + if (typeof elt == "string") elt = document.createTextNode(elt); + e.appendChild(elt); + } + return e; + } + + function dialog(cm, text, f) { + if (cm.openDialog) + cm.openDialog(text + ": ", f); + else + f(prompt(text, "")); + } + + // Tooltips + + function tempTooltip(cm, content) { + var where = cm.cursorCoords(); + var tip = makeTooltip(where.right + 1, where.bottom, content); + function clear() { + if (!tip.parentNode) return; + cm.off("cursorActivity", clear); + fadeOut(tip); + } + setTimeout(clear, 1700); + cm.on("cursorActivity", clear); + } + + function makeTooltip(x, y, content) { + var node = elt("div", cls + "tooltip", content); + node.style.left = x + "px"; + node.style.top = y + "px"; + document.body.appendChild(node); + return node; + } + + function remove(node) { + var p = node && node.parentNode; + if (p) p.removeChild(node); + } + + function fadeOut(tooltip) { + tooltip.style.opacity = "0"; + setTimeout(function() { remove(tooltip); }, 1100); + } + + function showError(ts, cm, msg) { + if (ts.options.showError) + ts.options.showError(cm, msg); + else + tempTooltip(cm, String(msg)); + } + + function closeArgHints(ts) { + if (ts.activeArgHints) { remove(ts.activeArgHints); ts.activeArgHints = null; } + } + + function docValue(ts, doc) { + var val = doc.doc.getValue(); + if (ts.options.fileFilter) val = ts.options.fileFilter(val, doc.name, doc.doc); + return val; + } + + // Worker wrapper + + function WorkerServer(ts) { + var worker = new Worker(ts.options.workerScript); + worker.postMessage({type: "init", + defs: ts.options.defs, + plugins: ts.options.plugins, + scripts: ts.options.workerDeps}); + var msgId = 0, pending = {}; + + function send(data, c) { + if (c) { + data.id = ++msgId; + pending[msgId] = c; + } + worker.postMessage(data); + } + worker.onmessage = function(e) { + var data = e.data; + if (data.type == "getFile") { + getFile(ts, name, function(err, text) { + send({type: "getFile", err: String(err), text: text, id: data.id}); + }); + } else if (data.type == "debug") { + console.log(data.message); + } else if (data.id && pending[data.id]) { + pending[data.id](data.err, data.body); + delete pending[data.id]; + } + }; + worker.onerror = function(e) { + for (var id in pending) pending[id](e); + pending = {}; + }; + + this.addFile = function(name, text) { send({type: "add", name: name, text: text}); }; + this.delFile = function(name) { send({type: "del", name: name}); }; + this.request = function(body, c) { send({type: "req", body: body}, c); }; + } +})(); diff --git a/addon/tern/worker.js b/addon/tern/worker.js new file mode 100644 index 0000000000..0164762f59 --- /dev/null +++ b/addon/tern/worker.js @@ -0,0 +1,39 @@ +var server; + +this.onmessage = function(e) { + var data = e.data; + switch (data.type) { + case "init": return startServer(data.defs, data.plugins, data.scripts); + case "add": return server.addFile(data.name, data.text); + case "del": return server.delFile(data.name); + case "req": return server.request(data.body, function(err, reqData) { + postMessage({id: data.id, body: reqData, err: err && String(err)}); + }); + case "getFile": + var c = pending[data.id]; + delete pending[data.id]; + return c(data.err, data.text); + default: throw new Error("Unknown message type: " + data.type); + } +}; + +var nextId = 0, pending = {}; +function getFile(file, c) { + postMessage({type: "getFile", name: file, id: ++nextId}); + pending[nextId] = c; +} + +function startServer(defs, plugins, scripts) { + if (scripts) importScripts.apply(null, scripts); + + server = new tern.Server({ + getFile: getFile, + async: true, + defs: defs, + plugins: plugins + }); +} + +var console = { + log: function(v) { postMessage({type: "debug", message: v}); } +}; diff --git a/bower.json b/bower.json index eb2b75b8d1..fae2426c89 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "CodeMirror", - "version": "3.13.0", + "version": "3.15.0", "main": ["lib/codemirror.js", "lib/codemirror.css"], "ignore": [ "**/.*", diff --git a/demo/anywordhint.html b/demo/anywordhint.html new file mode 100644 index 0000000000..5bb0a62a10 --- /dev/null +++ b/demo/anywordhint.html @@ -0,0 +1,69 @@ + + + + + CodeMirror: Any Word Completion Demo + + + + + + + + + +

CodeMirror: Any Word Completion Demo

+ +
+ +

Press ctrl-space to activate autocompletion. The +completion uses +the anyword-hint.js +module, which simply looks at nearby words in the buffer and completes +to those.

+ + + + diff --git a/demo/complete.html b/demo/complete.html index 02ce658880..1a65f6e0d9 100644 --- a/demo/complete.html +++ b/demo/complete.html @@ -59,7 +59,7 @@ + @@ -21,14 +22,29 @@ line-height: .3; cursor: pointer; } + .CodeMirror-foldgutter { + width: .7em; + } + .CodeMirror-foldgutter-open, + .CodeMirror-foldgutter-folded { + color: #555; + cursor: pointer; + } + .CodeMirror-foldgutter-open:after { + content: "\25BE"; + } + .CodeMirror-foldgutter-folded:after { + content: "\25B8"; + }

CodeMirror: Code Folding Demo

Demonstration of code folding using the code - in foldcode.js. - Press ctrl-q or click on the gutter to fold a block, again + in foldcode.js + and foldgutter.js. + Press ctrl-q or click on the gutter markers to fold a block, again to unfold.

JavaScript:
@@ -43,26 +59,26 @@ var te_html = document.getElementById("code-html"); te_html.value = "\n " + document.documentElement.innerHTML + "\n"; - function foldJS(cm, where) { cm.foldCode(where, CodeMirror.braceRangeFinder); } window.editor = CodeMirror.fromTextArea(te, { mode: "javascript", lineNumbers: true, lineWrapping: true, - extraKeys: {"Ctrl-Q": function(cm){foldJS(cm, cm.getCursor());}} + extraKeys: {"Ctrl-Q": function(cm){ cm.foldCode(cm.getCursor()); }}, + foldGutter: true, + gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"] }); - editor.on("gutterClick", foldJS); - foldJS(editor, 9); + editor.foldCode(CodeMirror.Pos(8, 0)); - function foldHTML(cm, where) { cm.foldCode(where, CodeMirror.tagRangeFinder); } window.editor_html = CodeMirror.fromTextArea(te_html, { mode: "text/html", lineNumbers: true, lineWrapping: true, - extraKeys: {"Ctrl-Q": function(cm){foldHTML(cm, cm.getCursor());}} + extraKeys: {"Ctrl-Q": function(cm){ cm.foldCode(cm.getCursor()); }}, + foldGutter: true, + gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"] }); - editor_html.on("gutterClick", foldHTML); - foldHTML(editor_html, 13); - foldHTML(editor_html, 1); + editor_html.foldCode(CodeMirror.Pos(13, 0)); + editor_html.foldCode(CodeMirror.Pos(1, 0)); }; diff --git a/demo/html5complete.html b/demo/html5complete.html index 5b2dd0de1d..9ea918228d 100644 --- a/demo/html5complete.html +++ b/demo/html5complete.html @@ -30,7 +30,7 @@ - + @@ -75,14 +75,14 @@ lineNumbers: true, mode: "javascript", gutters: ["CodeMirror-lint-markers"], - lintWith: CodeMirror.javascriptValidator + lint: true }); var editor_json = CodeMirror.fromTextArea(document.getElementById("code-json"), { lineNumbers: true, mode: "application/json", gutters: ["CodeMirror-lint-markers"], - lintWith: CodeMirror.jsonValidator + lint: true }); diff --git a/demo/matchhighlighter.html b/demo/matchhighlighter.html index c574fbae81..9d2fdd098a 100644 --- a/demo/matchhighlighter.html +++ b/demo/matchhighlighter.html @@ -28,7 +28,7 @@ diff --git a/demo/matchtags.html b/demo/matchtags.html new file mode 100644 index 0000000000..8f4217debe --- /dev/null +++ b/demo/matchtags.html @@ -0,0 +1,38 @@ + + + + + CodeMirror: Tag Matcher Demo + + + + + + + + + + +

CodeMirror: Tag Matcher Demo

+ +
+ + + +

Put the cursor on or inside a pair of tags to highlight them. + Press Ctrl-J to jump to the tag that matches the one under the + cursor.

+ + diff --git a/demo/tern.html b/demo/tern.html new file mode 100644 index 0000000000..bbc5e478c1 --- /dev/null +++ b/demo/tern.html @@ -0,0 +1,122 @@ + + + + + CodeMirror: Tern Demo + + + + + + + + + + + + + + + + + + + + + + + + + +

CodeMirror: Tern Demo

+ +

+ +

Demonstrates integration of Tern +and CodeMirror. The following keys are bound:

+ +
+
Ctrl-Space
Autocomplete
+
Ctrl-I
Find type at cursor
+
Alt-.
Jump to definition (Alt-, to jump back)
+
Ctrl-Q
Rename variable
+
+ +

Documentation is sparse for now. See the top of +the script for a rough API +overview.

+ + + + + diff --git a/demo/theme.html b/demo/theme.html index 147a89ff14..62544be1a7 100644 --- a/demo/theme.html +++ b/demo/theme.html @@ -22,6 +22,11 @@ + + + + + @@ -49,7 +54,11 @@

Select a theme:

Version:

+ +

The Jade Templating Mode

+

Created by Drew Bratcher. Managed as part of an Adobe Brackets extension at https://github.com/dbratcher/brackets-jade.

+

MIME type defined: text/x-jade.

+ + diff --git a/mode/jade/jade.js b/mode/jade/jade.js new file mode 100644 index 0000000000..61abb27ab7 --- /dev/null +++ b/mode/jade/jade.js @@ -0,0 +1,90 @@ +CodeMirror.defineMode("jade", function () { + var symbol_regex1 = /^(?:~|!|%|\^|\*|\+|=|\\|:|;|,|\/|\?|&|<|>|\|)/; + var open_paren_regex = /^(\(|\[)/; + var close_paren_regex = /^(\)|\])/; + var keyword_regex1 = /^(if|else|return|var|function|include|doctype|each)/; + var keyword_regex2 = /^(#|{|}|\.)/; + var keyword_regex3 = /^(in)/; + var html_regex1 = /^(html|head|title|meta|link|script|body|br|div|input|span|a|img)/; + var html_regex2 = /^(h1|h2|h3|h4|h5|p|strong|em)/; + return { + startState: function () { + return { + inString: false, + stringType: "", + beforeTag: true, + justMatchedKeyword: false, + afterParen: false + }; + }, + token: function (stream, state) { + //check for state changes + if (!state.inString && ((stream.peek() == '"') || (stream.peek() == "'"))) { + state.stringType = stream.peek(); + stream.next(); // Skip quote + state.inString = true; // Update state + } + + //return state + if (state.inString) { + if (stream.skipTo(state.stringType)) { // Quote found on this line + stream.next(); // Skip quote + state.inString = false; // Clear flag + } else { + stream.skipToEnd(); // Rest of line is string + } + state.justMatchedKeyword = false; + return "string"; // Token style + } else if (stream.sol() && stream.eatSpace()) { + if (stream.match(keyword_regex1)) { + state.justMatchedKeyword = true; + stream.eatSpace(); + return "keyword"; + } + if (stream.match(html_regex1) || stream.match(html_regex2)) { + state.justMatchedKeyword = true; + return "variable"; + } + } else if (stream.sol() && stream.match(keyword_regex1)) { + state.justMatchedKeyword = true; + stream.eatSpace(); + return "keyword"; + } else if (stream.sol() && (stream.match(html_regex1) || stream.match(html_regex2))) { + state.justMatchedKeyword = true; + return "variable"; + } else if (stream.eatSpace()) { + state.justMatchedKeyword = false; + if (stream.match(keyword_regex3) && stream.eatSpace()) { + state.justMatchedKeyword = true; + return "keyword"; + } + } else if (stream.match(symbol_regex1)) { + state.justMatchedKeyword = false; + return "atom"; + } else if (stream.match(open_paren_regex)) { + state.afterParen = true; + state.justMatchedKeyword = true; + return "def"; + } else if (stream.match(close_paren_regex)) { + state.afterParen = false; + state.justMatchedKeyword = true; + return "def"; + } else if (stream.match(keyword_regex2)) { + state.justMatchedKeyword = true; + return "keyword"; + } else if (stream.eatSpace()) { + state.justMatchedKeyword = false; + } else { + stream.next(); + if (state.justMatchedKeyword) { + return "property"; + } else if (state.afterParen) { + return "property"; + } + } + return null; + } + }; +}); + +CodeMirror.defineMIME('text/x-jade', 'jade'); diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js index 6435e138d6..0be9b79f7c 100644 --- a/mode/javascript/javascript.js +++ b/mode/javascript/javascript.js @@ -258,17 +258,17 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == "keyword b") return cont(pushlex("form"), statement, poplex); if (type == "{") return cont(pushlex("}"), block, poplex); if (type == ";") return cont(); - if (type == "if") return cont(pushlex("form"), expression, statement, poplex, maybeelse(cx.state.indented)); + if (type == "if") return cont(pushlex("form"), expression, statement, poplex, maybeelse); if (type == "function") return cont(functiondef); if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), - poplex, statement, poplex); + poplex, statement, poplex); if (type == "variable") return cont(pushlex("stat"), maybelabel); if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), - block, poplex, poplex); + block, poplex, poplex); if (type == "case") return cont(expression, expect(":")); if (type == "default") return cont(expect(":")); if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), - statement, poplex, popcontext); + statement, poplex, popcontext); return pass(pushlex("stat"), expression, expect(";"), poplex); } function expression(type) { @@ -299,19 +299,20 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { function maybeoperatorComma(type, value) { if (type == ",") return cont(expression); - return maybeoperatorNoComma(type, value, maybeoperatorComma); + return maybeoperatorNoComma(type, value, false); } - function maybeoperatorNoComma(type, value, me) { - if (!me) me = maybeoperatorNoComma; + function maybeoperatorNoComma(type, value, noComma) { + var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma; + var expr = noComma == false ? expression : expressionNoComma; if (type == "operator") { if (/\+\+|--/.test(value)) return cont(me); - if (value == "?") return cont(expression, expect(":"), expression); - return cont(expression); + if (value == "?") return cont(expression, expect(":"), expr); + return cont(expr); } if (type == ";") return; if (type == "(") return cont(pushlex(")", "call"), commasep(expressionNoComma, ")"), poplex, me); if (type == ".") return cont(property, me); - if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, me); + if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me); } function maybelabel(type) { if (type == ":") return cont(poplex, statement); @@ -373,14 +374,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (value == "=") return cont(expressionNoComma, vardef2); if (type == ",") return cont(vardef1); } - function maybeelse(indent) { - return function(type, value) { - if (type == "keyword b" && value == "else") { - cx.state.lexical = new JSLexical(indent, 0, "form", null, cx.state.lexical); - return cont(statement, poplex); - } - return pass(); - }; + function maybeelse(type, value) { + if (type == "keyword b" && value == "else") return cont(pushlex("form"), statement, poplex); } function forspec1(type) { if (type == "var") return cont(vardef1, expect(";"), forspec2); @@ -441,6 +436,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (state.tokenize == jsTokenComment) return CodeMirror.Pass; if (state.tokenize != jsTokenBase) return 0; var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; + // Kludge to prevent 'maybelse' from blocking lexical scope pops + for (var i = state.cc.length - 1; i >= 0; --i) { + var c = state.cc[i]; + if (c == poplex) lexical = lexical.prev; + else if (c != maybeelse || /^else\b/.test(textAfter)) break; + } if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat") lexical = lexical.prev; @@ -461,7 +462,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { blockCommentStart: jsonMode ? null : "/*", blockCommentEnd: jsonMode ? null : "*/", lineComment: jsonMode ? null : "//", + fold: "brace", + helperType: jsonMode ? "json" : "javascript", jsonMode: jsonMode }; }); diff --git a/mode/javascript/test.js b/mode/javascript/test.js new file mode 100644 index 0000000000..a2af527bee --- /dev/null +++ b/mode/javascript/test.js @@ -0,0 +1,10 @@ +(function() { + var mode = CodeMirror.getMode({indentUnit: 2}, "javascript"); + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } + + MT("locals", + "[keyword function] [variable foo]([def a], [def b]) { [keyword var] [def c] = [number 10]; [keyword return] [variable-2 a] + [variable-2 c] + [variable d]; }"); + + MT("comma-and-binop", + "[keyword function](){ [keyword var] [def x] = [number 1] + [number 2], [def y]; }"); +})(); diff --git a/mode/markdown/index.html b/mode/markdown/index.html index 6f97b10e73..bb785b13fc 100644 --- a/mode/markdown/index.html +++ b/mode/markdown/index.html @@ -8,7 +8,12 @@ - + diff --git a/mode/markdown/markdown.js b/mode/markdown/markdown.js index 72b0d6ced2..bf1750d5b6 100644 --- a/mode/markdown/markdown.js +++ b/mode/markdown/markdown.js @@ -103,6 +103,9 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { state.f = inlineNormal; state.block = blockNormal; } + // Reset state.trailingSpace + state.trailingSpace = 0; + state.trailingSpaceNewLine = false; // Mark this line as blank state.thisLineHasContent = false; return null; @@ -217,6 +220,12 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { } } + if (state.trailingSpaceNewLine) { + styles.push("trailing-space-new-line"); + } else if (state.trailingSpace) { + styles.push("trailing-space-" + (state.trailingSpace % 2 ? "a" : "b")); + } + return styles.length ? styles.join(' ') : null; } @@ -369,6 +378,14 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { } } + if (ch === ' ') { + if (stream.match(/ +$/, false)) { + state.trailingSpace++; + } else if (state.trailingSpace) { + state.trailingSpaceNewLine = true; + } + } + return getType(state); } @@ -453,7 +470,9 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { taskList: false, list: false, listDepth: 0, - quote: 0 + quote: 0, + trailingSpace: 0, + trailingSpaceNewLine: false }; }, @@ -481,6 +500,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { list: s.list, listDepth: s.listDepth, quote: s.quote, + trailingSpace: s.trailingSpace, + trailingSpaceNewLine: s.trailingSpaceNewLine, md_inside: s.md_inside }; }, @@ -504,6 +525,10 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { // Reset state.code state.code = false; + // Reset state.trailingSpace + state.trailingSpace = 0; + state.trailingSpaceNewLine = false; + state.f = state.block; var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, ' ').length; var difference = Math.floor((indentation - state.indentation) / 4) * 4; diff --git a/mode/markdown/test.js b/mode/markdown/test.js index 99ae056132..f167917289 100644 --- a/mode/markdown/test.js +++ b/mode/markdown/test.js @@ -5,6 +5,20 @@ MT("plainText", "foo"); + // Don't style single trailing space + MT("trailingSpace1", + "foo "); + + // Two or more trailing spaces should be styled with line break character + MT("trailingSpace2", + "foo[trailing-space-a ][trailing-space-new-line ]"); + + MT("trailingSpace3", + "foo[trailing-space-a ][trailing-space-b ][trailing-space-new-line ]"); + + MT("trailingSpace4", + "foo[trailing-space-a ][trailing-space-b ][trailing-space-a ][trailing-space-new-line ]"); + // Code blocks using 4 spaces (regardless of CodeMirror.tabSize value) MT("codeBlocksUsing4Spaces", " [comment foo]"); diff --git a/mode/meta.js b/mode/meta.js index e8cfc8fb63..8e8c8f5f5c 100644 --- a/mode/meta.js +++ b/mode/meta.js @@ -16,7 +16,7 @@ CodeMirror.modeInfo = [ {name: 'ECL', mime: 'text/x-ecl', mode: 'ecl'}, {name: 'Erlang', mime: 'text/x-erlang', mode: 'erlang'}, {name: 'Gas', mime: 'text/x-gas', mode: 'gas'}, - {name: 'GitHub Flavored Markdown', mode: 'gfm'}, + {name: 'GitHub Flavored Markdown', mime: 'text/x-gfm', mode: 'gfm'}, {name: 'GO', mime: 'text/x-go', mode: 'go'}, {name: 'Groovy', mime: 'text/x-groovy', mode: 'groovy'}, {name: 'Haskell', mime: 'text/x-haskell', mode: 'haskell'}, @@ -26,6 +26,7 @@ CodeMirror.modeInfo = [ {name: 'JavaServer Pages', mime: 'application/x-jsp', mode: 'htmlembedded'}, {name: 'HTML', mime: 'text/html', mode: 'htmlmixed'}, {name: 'HTTP', mime: 'message/http', mode: 'http'}, + {name: 'Jade', mime: 'text/x-jade', mode: 'jade'}, {name: 'JavaScript', mime: 'text/javascript', mode: 'javascript'}, {name: 'JSON', mime: 'application/x-json', mode: 'javascript'}, {name: 'JSON', mime: 'application/json', mode: 'javascript'}, @@ -36,6 +37,7 @@ CodeMirror.modeInfo = [ {name: 'Lua', mime: 'text/x-lua', mode: 'lua'}, {name: 'Markdown (GitHub-flavour)', mime: 'text/x-markdown', mode: 'markdown'}, {name: 'mIRC', mime: 'text/mirc', mode: 'mirc'}, + {name: 'Nginx', mime: 'text/x-nginx-conf', mode: 'nginx'}, {name: 'NTriples', mime: 'text/n-triples', mode: 'ntriples'}, {name: 'OCaml', mime: 'text/x-ocaml', mode: 'ocaml'}, {name: 'Pascal', mime: 'text/x-pascal', mode: 'pascal'}, @@ -46,6 +48,7 @@ CodeMirror.modeInfo = [ {name: 'Plain Text', mime: 'text/plain', mode: 'null'}, {name: 'Properties files', mime: 'text/x-properties', mode: 'clike'}, {name: 'Python', mime: 'text/x-python', mode: 'python'}, + {name: 'Cython', mime: 'text/x-cython', mode: 'python'}, {name: 'R', mime: 'text/x-rsrc', mode: 'r'}, {name: 'reStructuredText', mime: 'text/x-rst', mode: 'rst'}, {name: 'Ruby', mime: 'text/x-ruby', mode: 'ruby'}, @@ -57,6 +60,7 @@ CodeMirror.modeInfo = [ {name: 'Sieve', mime: 'application/sieve', mode: 'sieve'}, {name: 'Smalltalk', mime: 'text/x-stsrc', mode: 'smalltalk'}, {name: 'Smarty', mime: 'text/x-smarty', mode: 'smarty'}, + {name: 'SmartyMixed', mime: 'text/x-smarty', mode: 'smartymixed'}, {name: 'SPARQL', mime: 'application/x-sparql-query', mode: 'sparql'}, {name: 'SQL', mime: 'text/x-sql', mode: 'sql'}, {name: 'MariaDB', mime: 'text/x-mariadb', mode: 'sql'}, diff --git a/mode/nginx/index.html b/mode/nginx/index.html new file mode 100644 index 0000000000..aa712e1e32 --- /dev/null +++ b/mode/nginx/index.html @@ -0,0 +1,167 @@ + + + + CodeMirror: NGINX mode + + + + + + + + + + +

CodeMirror: NGINX mode

+
+ + +

MIME types defined: text/nginx.

+ + + diff --git a/mode/nginx/nginx.js b/mode/nginx/nginx.js new file mode 100644 index 0000000000..c98c8a1dd3 --- /dev/null +++ b/mode/nginx/nginx.js @@ -0,0 +1,163 @@ +CodeMirror.defineMode("nginx", function(config) { + + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + var keywords = words( + /* ngxDirectiveControl */ "break return rewrite set" + + /* ngxDirective */ " accept_mutex accept_mutex_delay access_log add_after_body add_before_body add_header addition_types aio alias allow ancient_browser ancient_browser_value auth_basic auth_basic_user_file auth_http auth_http_header auth_http_timeout autoindex autoindex_exact_size autoindex_localtime charset charset_types client_body_buffer_size client_body_in_file_only client_body_in_single_buffer client_body_temp_path client_body_timeout client_header_buffer_size client_header_timeout client_max_body_size connection_pool_size create_full_put_path daemon dav_access dav_methods debug_connection debug_points default_type degradation degrade deny devpoll_changes devpoll_events directio directio_alignment empty_gif env epoll_events error_log eventport_events expires fastcgi_bind fastcgi_buffer_size fastcgi_buffers fastcgi_busy_buffers_size fastcgi_cache fastcgi_cache_key fastcgi_cache_methods fastcgi_cache_min_uses fastcgi_cache_path fastcgi_cache_use_stale fastcgi_cache_valid fastcgi_catch_stderr fastcgi_connect_timeout fastcgi_hide_header fastcgi_ignore_client_abort fastcgi_ignore_headers fastcgi_index fastcgi_intercept_errors fastcgi_max_temp_file_size fastcgi_next_upstream fastcgi_param fastcgi_pass_header fastcgi_pass_request_body fastcgi_pass_request_headers fastcgi_read_timeout fastcgi_send_lowat fastcgi_send_timeout fastcgi_split_path_info fastcgi_store fastcgi_store_access fastcgi_temp_file_write_size fastcgi_temp_path fastcgi_upstream_fail_timeout fastcgi_upstream_max_fails flv geoip_city geoip_country google_perftools_profiles gzip gzip_buffers gzip_comp_level gzip_disable gzip_hash gzip_http_version gzip_min_length gzip_no_buffer gzip_proxied gzip_static gzip_types gzip_vary gzip_window if_modified_since ignore_invalid_headers image_filter image_filter_buffer image_filter_jpeg_quality image_filter_transparency imap_auth imap_capabilities imap_client_buffer index ip_hash keepalive_requests keepalive_timeout kqueue_changes kqueue_events large_client_header_buffers limit_conn limit_conn_log_level limit_rate limit_rate_after limit_req limit_req_log_level limit_req_zone limit_zone lingering_time lingering_timeout lock_file log_format log_not_found log_subrequest map_hash_bucket_size map_hash_max_size master_process memcached_bind memcached_buffer_size memcached_connect_timeout memcached_next_upstream memcached_read_timeout memcached_send_timeout memcached_upstream_fail_timeout memcached_upstream_max_fails merge_slashes min_delete_depth modern_browser modern_browser_value msie_padding msie_refresh multi_accept open_file_cache open_file_cache_errors open_file_cache_events open_file_cache_min_uses open_file_cache_valid open_log_file_cache output_buffers override_charset perl perl_modules perl_require perl_set pid pop3_auth pop3_capabilities port_in_redirect postpone_gzipping postpone_output protocol proxy proxy_bind proxy_buffer proxy_buffer_size proxy_buffering proxy_buffers proxy_busy_buffers_size proxy_cache proxy_cache_key proxy_cache_methods proxy_cache_min_uses proxy_cache_path proxy_cache_use_stale proxy_cache_valid proxy_connect_timeout proxy_headers_hash_bucket_size proxy_headers_hash_max_size proxy_hide_header proxy_ignore_client_abort proxy_ignore_headers proxy_intercept_errors proxy_max_temp_file_size proxy_method proxy_next_upstream proxy_pass_error_message proxy_pass_header proxy_pass_request_body proxy_pass_request_headers proxy_read_timeout proxy_redirect proxy_send_lowat proxy_send_timeout proxy_set_body proxy_set_header proxy_ssl_session_reuse proxy_store proxy_store_access proxy_temp_file_write_size proxy_temp_path proxy_timeout proxy_upstream_fail_timeout proxy_upstream_max_fails random_index read_ahead real_ip_header recursive_error_pages request_pool_size reset_timedout_connection resolver resolver_timeout rewrite_log rtsig_overflow_events rtsig_overflow_test rtsig_overflow_threshold rtsig_signo satisfy secure_link_secret send_lowat send_timeout sendfile sendfile_max_chunk server_name_in_redirect server_names_hash_bucket_size server_names_hash_max_size server_tokens set_real_ip_from smtp_auth smtp_capabilities smtp_client_buffer smtp_greeting_delay so_keepalive source_charset ssi ssi_ignore_recycled_buffers ssi_min_file_chunk ssi_silent_errors ssi_types ssi_value_length ssl ssl_certificate ssl_certificate_key ssl_ciphers ssl_client_certificate ssl_crl ssl_dhparam ssl_engine ssl_prefer_server_ciphers ssl_protocols ssl_session_cache ssl_session_timeout ssl_verify_client ssl_verify_depth starttls stub_status sub_filter sub_filter_once sub_filter_types tcp_nodelay tcp_nopush thread_stack_size timeout timer_resolution types_hash_bucket_size types_hash_max_size underscores_in_headers uninitialized_variable_warn use user userid userid_domain userid_expires userid_mark userid_name userid_p3p userid_path userid_service valid_referers variables_hash_bucket_size variables_hash_max_size worker_connections worker_cpu_affinity worker_priority worker_processes worker_rlimit_core worker_rlimit_nofile worker_rlimit_sigpending worker_threads working_directory xclient xml_entities xslt_stylesheet xslt_typesdrew@li229-23" + ); + + var keywords_block = words( + /* ngxDirectiveBlock */ "http mail events server types location upstream charset_map limit_except if geo map" + ); + + var keywords_important = words( + /* ngxDirectiveImportant */ "include root server server_name listen internal proxy_pass memcached_pass fastcgi_pass try_files" + ); + + var indentUnit = config.indentUnit, type; + function ret(style, tp) {type = tp; return style;} + + function tokenBase(stream, state) { + + + stream.eatWhile(/[\w\$_]/); + + var cur = stream.current(); + + + if (keywords.propertyIsEnumerable(cur)) { + return "keyword"; + } + else if (keywords_block.propertyIsEnumerable(cur)) { + return "variable-2"; + } + else if (keywords_important.propertyIsEnumerable(cur)) { + return "string-2"; + } + /**/ + + var ch = stream.next(); + if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("meta", stream.current());} + else if (ch == "/" && stream.eat("*")) { + state.tokenize = tokenCComment; + return tokenCComment(stream, state); + } + else if (ch == "<" && stream.eat("!")) { + state.tokenize = tokenSGMLComment; + return tokenSGMLComment(stream, state); + } + else if (ch == "=") ret(null, "compare"); + else if ((ch == "~" || ch == "|") && stream.eat("=")) return ret(null, "compare"); + else if (ch == "\"" || ch == "'") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + else if (ch == "#") { + stream.skipToEnd(); + return ret("comment", "comment"); + } + else if (ch == "!") { + stream.match(/^\s*\w*/); + return ret("keyword", "important"); + } + else if (/\d/.test(ch)) { + stream.eatWhile(/[\w.%]/); + return ret("number", "unit"); + } + else if (/[,.+>*\/]/.test(ch)) { + return ret(null, "select-op"); + } + else if (/[;{}:\[\]]/.test(ch)) { + return ret(null, ch); + } + else { + stream.eatWhile(/[\w\\\-]/); + return ret("variable", "variable"); + } + } + + function tokenCComment(stream, state) { + var maybeEnd = false, ch; + while ((ch = stream.next()) != null) { + if (maybeEnd && ch == "/") { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return ret("comment", "comment"); + } + + function tokenSGMLComment(stream, state) { + var dashes = 0, ch; + while ((ch = stream.next()) != null) { + if (dashes >= 2 && ch == ">") { + state.tokenize = tokenBase; + break; + } + dashes = (ch == "-") ? dashes + 1 : 0; + } + return ret("comment", "comment"); + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) + break; + escaped = !escaped && ch == "\\"; + } + if (!escaped) state.tokenize = tokenBase; + return ret("string", "string"); + }; + } + + return { + startState: function(base) { + return {tokenize: tokenBase, + baseIndent: base || 0, + stack: []}; + }, + + token: function(stream, state) { + if (stream.eatSpace()) return null; + type = null; + var style = state.tokenize(stream, state); + + var context = state.stack[state.stack.length-1]; + if (type == "hash" && context == "rule") style = "atom"; + else if (style == "variable") { + if (context == "rule") style = "number"; + else if (!context || context == "@media{") style = "tag"; + } + + if (context == "rule" && /^[\{\};]$/.test(type)) + state.stack.pop(); + if (type == "{") { + if (context == "@media") state.stack[state.stack.length-1] = "@media{"; + else state.stack.push("{"); + } + else if (type == "}") state.stack.pop(); + else if (type == "@media") state.stack.push("@media"); + else if (context == "{" && type != "comment") state.stack.push("rule"); + return style; + }, + + indent: function(state, textAfter) { + var n = state.stack.length; + if (/^\}/.test(textAfter)) + n -= state.stack[state.stack.length-1] == "rule" ? 2 : 1; + return state.baseIndent + n * indentUnit; + }, + + electricChars: "}" + }; +}); + +CodeMirror.defineMIME("text/nginx", "text/x-nginx-conf"); diff --git a/mode/python/index.html b/mode/python/index.html index 4244c6fc5a..1229a8bf83 100644 --- a/mode/python/index.html +++ b/mode/python/index.html @@ -12,7 +12,7 @@

CodeMirror: Python mode

- +

Python mode

+ + +

Cython mode

+ +
+ -

Configuration Options:

+

Configuration Options for Python mode:

-

MIME types defined: text/x-python.

+

MIME types defined: text/x-python and text/x-cython.

diff --git a/mode/python/python.js b/mode/python/python.js index b623972b88..4fe4d28912 100644 --- a/mode/python/python.js +++ b/mode/python/python.js @@ -36,6 +36,12 @@ CodeMirror.defineMode("python", function(conf, parserConf) { var py3 = {'builtins': ['ascii', 'bytes', 'exec', 'print'], 'keywords': ['nonlocal', 'False', 'True', 'None']}; + if(parserConf.extra_keywords != undefined){ + commonkeywords = commonkeywords.concat(parserConf.extra_keywords); + } + if(parserConf.extra_builtins != undefined){ + commonBuiltins = commonBuiltins.concat(parserConf.extra_builtins); + } if (!!parserConf.version && parseInt(parserConf.version, 10) === 3) { commonkeywords = commonkeywords.concat(py3.keywords); commonBuiltins = commonBuiltins.concat(py3.builtins); @@ -318,7 +324,7 @@ CodeMirror.defineMode("python", function(conf, parserConf) { state.lastToken = style; - if (stream.eol() && stream.lambda) { + if (stream.eol() && state.lambda) { state.lambda = false; } @@ -333,9 +339,20 @@ CodeMirror.defineMode("python", function(conf, parserConf) { return state.scopes[0].offset; }, - lineComment: "#" + lineComment: "#", + fold: "indent" }; return external; }); CodeMirror.defineMIME("text/x-python", "python"); + +var words = function(str){return str.split(' ');}; + + +CodeMirror.defineMIME("text/x-cython", { + name: "python", + extra_keywords: words("by cdef cimport cpdef ctypedef enum except"+ + "extern gil include nogil property public"+ + "readonly struct union DEF IF ELIF ELSE") +}); diff --git a/mode/rst/LICENSE.txt b/mode/rst/LICENSE.txt index 39484fabb9..c40984a84d 100644 --- a/mode/rst/LICENSE.txt +++ b/mode/rst/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2013 Hasan Karahan +Copyright (c) 2013 Hasan Karahan 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/mode/rst/rst.js b/mode/rst/rst.js index 8de9d75415..508896d25b 100644 --- a/mode/rst/rst.js +++ b/mode/rst/rst.js @@ -36,9 +36,11 @@ CodeMirror.defineMode('rst-base', function (config) { var TAIL = "(?:\\s*|\\W|$)", rx_TAIL = new RegExp(format('^{0}', TAIL)); - var NAME = "(?:[^\\W\\d_](?:[\\w\\+\\.\\-:]*[^\\W_])?)", + var NAME = + "(?:[^\\W\\d_](?:[\\w!\"#$%&'()\\*\\+,\\-\\.\/:;<=>\\?]*[^\\W_])?)", rx_NAME = new RegExp(format('^{0}', NAME)); - var NAME_WWS = "(?:[^\\W\\d_](?:[\\w\\s\\+\\.\\-:]*[^\\W_])?)"; + var NAME_WWS = + "(?:[^\\W\\d_](?:[\\w\\s!\"#$%&'()\\*\\+,\\-\\.\/:;<=>\\?]*[^\\W_])?)"; var REF_NAME = format('(?:{0}|`{1}`)', NAME, NAME_WWS); var TEXT1 = "(?:[^\\s\\|](?:[^\\|]*[^\\s\\|])?)"; @@ -480,7 +482,9 @@ CodeMirror.defineMode('rst-base', function (config) { }, innerMode: function (state) { - return {state: state.ctx.local, mode: state.ctx.mode}; + return state.tmp ? {state: state.tmp.local, mode: state.tmp.mode} + : state.ctx ? {state: state.ctx.local, mode: state.ctx.mode} + : null; }, token: function (stream, state) { @@ -494,6 +498,14 @@ CodeMirror.defineMode('rst-base', function (config) { CodeMirror.defineMode('rst', function (config, options) { + var rx_strong = /^\*\*[^\*\s](?:[^\*]*[^\*\s])?\*\*/; + var rx_emphasis = /^\*[^\*\s](?:[^\*]*[^\*\s])?\*/; + var rx_literal = /^``[^`\s](?:[^`]*[^`\s])``/; + + var rx_number = /^(?:[\d]+(?:[\.,]\d+)*)/; + var rx_positive = /^(?:\s\+[\d]+(?:[\.,]\d+)*)/; + var rx_negative = /^(?:\s\-[\d]+(?:[\.,]\d+)*)/; + var rx_uri_protocol = "[Hh][Tt][Tt][Pp][Ss]?://"; var rx_uri_domain = "(?:[\\d\\w.-]+)\\.(?:\\w{2,6})"; var rx_uri_path = "(?:/[\\d\\w\\#\\%\\&\\-\\.\\,\\/\\:\\=\\?\\~]+)*"; @@ -501,33 +513,32 @@ CodeMirror.defineMode('rst', function (config, options) { rx_uri_protocol + rx_uri_domain + rx_uri_path ); - var rx_strong = /^\*\*[^\*\s](?:[^\*]*[^\*\s])?\*\*(\s+|$)/; - var rx_emphasis = /^[^\*]\*[^\*\s](?:[^\*]*[^\*\s])?\*(\s+|$)/; - var rx_literal = /^``[^`\s](?:[^`]*[^`\s])``(\s+|$)/; - - var rx_number = /^(?:[\d]+(?:[\.,]\d+)*)/; - var rx_positive = /^(?:\s\+[\d]+(?:[\.,]\d+)*)/; - var rx_negative = /^(?:\s\-[\d]+(?:[\.,]\d+)*)/; - var overlay = { token: function (stream) { - if (stream.match(rx_uri)) return 'link'; - if (stream.match(rx_strong)) return 'strong'; - if (stream.match(rx_emphasis)) return 'em'; - if (stream.match(rx_literal)) return 'string-2'; - if (stream.match(rx_number)) return 'number'; - if (stream.match(rx_positive)) return 'positive'; - if (stream.match(rx_negative)) return 'negative'; + if (stream.match(rx_strong) && stream.match (/\W+|$/, false)) + return 'strong'; + if (stream.match(rx_emphasis) && stream.match (/\W+|$/, false)) + return 'em'; + if (stream.match(rx_literal) && stream.match (/\W+|$/, false)) + return 'string-2'; + if (stream.match(rx_number)) + return 'number'; + if (stream.match(rx_positive)) + return 'positive'; + if (stream.match(rx_negative)) + return 'negative'; + if (stream.match(rx_uri)) + return 'link'; while (stream.next() != null) { - if (stream.match(rx_uri, false)) break; if (stream.match(rx_strong, false)) break; if (stream.match(rx_emphasis, false)) break; if (stream.match(rx_literal, false)) break; if (stream.match(rx_number, false)) break; if (stream.match(rx_positive, false)) break; if (stream.match(rx_negative, false)) break; + if (stream.match(rx_uri, false)) break; } return null; diff --git a/mode/ruby/ruby.js b/mode/ruby/ruby.js index af0ffe9e54..96cdd5f9dd 100644 --- a/mode/ruby/ruby.js +++ b/mode/ruby/ruby.js @@ -36,11 +36,11 @@ CodeMirror.defineMode("ruby", function(config) { } else if (ch == "/" && !stream.eol() && stream.peek() != " ") { return chain(readQuoted(ch, "string-2", true), stream, state); } else if (ch == "%") { - var style = "string", embed = false; + var style = "string", embed = true; if (stream.eat("s")) style = "atom"; - else if (stream.eat(/[WQ]/)) { style = "string"; embed = true; } - else if (stream.eat(/[r]/)) { style = "string-2"; embed = true; } - else if (stream.eat(/[wxq]/)) style = "string"; + else if (stream.eat(/[WQ]/)) style = "string"; + else if (stream.eat(/[r]/)) style = "string-2"; + else if (stream.eat(/[wxq]/)) { style = "string"; embed = false; } var delim = stream.eat(/[^\w\s]/); if (!delim) return "operator"; if (matching.propertyIsEnumerable(delim)) delim = matching[delim]; diff --git a/mode/rust/rust.js b/mode/rust/rust.js index 7bee489b47..c7530b6cc6 100644 --- a/mode/rust/rust.js +++ b/mode/rust/rust.js @@ -428,7 +428,8 @@ CodeMirror.defineMode("rust", function() { electricChars: "{}", blockCommentStart: "/*", blockCommentEnd: "*/", - lineComment: "//" + lineComment: "//", + fold: "brace" }; }); diff --git a/mode/smalltalk/smalltalk.js b/mode/smalltalk/smalltalk.js index eb32417514..f2d4cb3185 100644 --- a/mode/smalltalk/smalltalk.js +++ b/mode/smalltalk/smalltalk.js @@ -36,8 +36,13 @@ CodeMirror.defineMode('smalltalk', function(config) { token = nextString(stream, new Context(nextString, context)); } else if (aChar === '#') { - stream.eatWhile(/[^ .\[\]()]/); - token.name = 'string-2'; + if (stream.peek() === '\'') { + stream.next(); + token = nextSymbol(stream, new Context(nextSymbol, context)); + } else { + stream.eatWhile(/[^ .\[\]()]/); + token.name = 'string-2'; + } } else if (aChar === '$') { if (stream.next() === '<') { @@ -89,6 +94,11 @@ CodeMirror.defineMode('smalltalk', function(config) { return new Token('string', stream.eat('\'') ? context.parent : context, false); }; + var nextSymbol = function(stream, context) { + stream.eatWhile(/[^']/); + return new Token('string-2', stream.eat('\'') ? context.parent : context, false); + }; + var nextTemporaries = function(stream, context) { var token = new Token(null, context, false); var aChar = stream.next(); diff --git a/mode/smartymixed/index.html b/mode/smartymixed/index.html new file mode 100644 index 0000000000..6567b27d6b --- /dev/null +++ b/mode/smartymixed/index.html @@ -0,0 +1,107 @@ + + + + + CodeMirror: Smarty mixed mode + + + + + + + + + + + + + + + +

CodeMirror: Smarty mixed mode

+
+ + + +

The Smarty mixed mode depends on the Smarty and HTML mixed modes. HTML + mixed mode itself depends on XML, JavaScript, and CSS modes.

+ +

It takes the same options, as Smarty and HTML mixed modes.

+ +

MIME types defined: text/x-smarty.

+ + + diff --git a/mode/smartymixed/smartymixed.js b/mode/smartymixed/smartymixed.js new file mode 100644 index 0000000000..c5d008885a --- /dev/null +++ b/mode/smartymixed/smartymixed.js @@ -0,0 +1,170 @@ +/** +* @file smartymixed.js +* @brief Smarty Mixed Codemirror mode (Smarty + Mixed HTML) +* @author Ruslan Osmanov +* @version 3.0 +* @date 05.07.2013 +*/ +CodeMirror.defineMode("smartymixed", function(config) { + var settings, regs, helpers, parsers, + htmlMixedMode = CodeMirror.getMode(config, "htmlmixed"), + smartyMode = CodeMirror.getMode(config, "smarty"), + + settings = { + rightDelimiter: '}', + leftDelimiter: '{' + }; + + if (config.hasOwnProperty("leftDelimiter")) { + settings.leftDelimiter = config.leftDelimiter; + } + if (config.hasOwnProperty("rightDelimiter")) { + settings.rightDelimiter = config.rightDelimiter; + } + + regs = { + smartyComment: new RegExp("^" + settings.leftDelimiter + "\\*"), + literalOpen: new RegExp(settings.leftDelimiter + "literal" + settings.rightDelimiter), + literalClose: new RegExp(settings.leftDelimiter + "\/literal" + settings.rightDelimiter), + hasLeftDelimeter: new RegExp(".*" + settings.leftDelimiter), + htmlHasLeftDelimeter: new RegExp("[^<>]*" + settings.leftDelimiter) + }; + + helpers = { + chain: function(stream, state, parser) { + state.tokenize = parser; + return parser(stream, state); + }, + + cleanChain: function(stream, state, parser) { + state.tokenize = null; + state.localState = null; + state.localMode = null; + return (typeof parser == "string") ? (parser ? parser : null) : parser(stream, state); + }, + + maybeBackup: function(stream, pat, style) { + var cur = stream.current(); + var close = cur.search(pat), + m; + if (close > - 1) stream.backUp(cur.length - close); + else if (m = cur.match(/<\/?$/)) { + stream.backUp(cur.length); + if (!stream.match(pat, false)) stream.match(cur[0]); + } + return style; + } + }; + + parsers = { + html: function(stream, state) { + if (!state.inLiteral && stream.match(regs.htmlHasLeftDelimeter, false)) { + state.tokenize = parsers.smarty; + state.localMode = smartyMode; + state.localState = smartyMode.startState(htmlMixedMode.indent(state.htmlMixedState, "")); + return helpers.maybeBackup(stream, settings.leftDelimiter, smartyMode.token(stream, state.localState)); + } + return htmlMixedMode.token(stream, state.htmlMixedState); + }, + + smarty: function(stream, state) { + if (stream.match(settings.leftDelimiter, false)) { + if (stream.match(regs.smartyComment, false)) { + return helpers.chain(stream, state, parsers.inBlock("comment", "*" + settings.rightDelimiter)); + } + } else if (stream.match(settings.rightDelimiter, false)) { + stream.eat(settings.rightDelimiter); + state.tokenize = parsers.html; + state.localMode = htmlMixedMode; + state.localState = state.htmlMixedState; + return "tag"; + } + + return helpers.maybeBackup(stream, settings.rightDelimiter, smartyMode.token(stream, state.localState)); + }, + + inBlock: function(style, terminator) { + return function(stream, state) { + while (!stream.eol()) { + if (stream.match(terminator)) { + helpers.cleanChain(stream, state, ""); + break; + } + stream.next(); + } + return style; + }; + } + }; + + return { + startState: function() { + var state = htmlMixedMode.startState(); + return { + token: parsers.html, + localMode: null, + localState: null, + htmlMixedState: state, + tokenize: null, + inLiteral: false + }; + }, + + copyState: function(state) { + var local = null, tok = (state.tokenize || state.token); + if (state.localState) { + local = CodeMirror.copyState((tok != parsers.html ? smartyMode : htmlMixedMode), state.localState); + } + return { + token: state.token, + tokenize: state.tokenize, + localMode: state.localMode, + localState: local, + htmlMixedState: CodeMirror.copyState(htmlMixedMode, state.htmlMixedState), + inLiteral: state.inLiteral + }; + }, + + token: function(stream, state) { + if (stream.match(settings.leftDelimiter, false)) { + if (!state.inLiteral && stream.match(regs.literalOpen, true)) { + state.inLiteral = true; + return "keyword"; + } else if (state.inLiteral && stream.match(regs.literalClose, true)) { + state.inLiteral = false; + return "keyword"; + } + } + if (state.inLiteral && state.localState != state.htmlMixedState) { + state.tokenize = parsers.html; + state.localMode = htmlMixedMode; + state.localState = state.htmlMixedState; + } + + var style = (state.tokenize || state.token)(stream, state); + return style; + }, + + indent: function(state, textAfter) { + if (state.localMode == smartyMode + || (state.inLiteral && !state.localMode) + || regs.hasLeftDelimeter.test(textAfter)) { + return CodeMirror.Pass; + } + return htmlMixedMode.indent(state.htmlMixedState, textAfter); + }, + + electricChars: "/{}:", + + innerMode: function(state) { + return { + state: state.localState || state.htmlMixedState, + mode: state.localMode || htmlMixedMode + }; + } + }; +}, +"htmlmixed"); + +CodeMirror.defineMIME("text/x-smarty", "smartymixed"); +// vim: et ts=2 sts=2 sw=2 diff --git a/mode/sparql/sparql.js b/mode/sparql/sparql.js index f7237698d3..0329057f69 100644 --- a/mode/sparql/sparql.js +++ b/mode/sparql/sparql.js @@ -6,10 +6,12 @@ CodeMirror.defineMode("sparql", function(config) { return new RegExp("^(?:" + words.join("|") + ")$", "i"); } var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri", - "isblank", "isliteral", "union", "a"]); + "isblank", "isliteral", "a"]); var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe", "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional", - "graph", "by", "asc", "desc"]); + "graph", "by", "asc", "desc", "as", "having", "undef", "values", "group", + "minus", "in", "not", "service", "silent", "using", "insert", "delete", "union", + "data", "copy", "to", "move", "add", "create", "drop", "clear", "load"]); var operatorChars = /[*+\-<>=&|]/; function tokenBase(stream, state) { diff --git a/mode/vbscript/index.html b/mode/vbscript/index.html index 8c86f9ef94..9ae46676b2 100644 --- a/mode/vbscript/index.html +++ b/mode/vbscript/index.html @@ -32,7 +32,8 @@ diff --git a/mode/vbscript/vbscript.js b/mode/vbscript/vbscript.js index 3e1eedc7db..0a97fb6405 100644 --- a/mode/vbscript/vbscript.js +++ b/mode/vbscript/vbscript.js @@ -1,26 +1,334 @@ -CodeMirror.defineMode("vbscript", function() { - var regexVBScriptKeyword = /^(?:Call|Case|CDate|Clear|CInt|CLng|Const|CStr|Description|Dim|Do|Each|Else|ElseIf|End|Err|Error|Exit|False|For|Function|If|LCase|Loop|LTrim|Next|Nothing|Now|Number|On|Preserve|Quit|ReDim|Resume|RTrim|Select|Set|Sub|Then|To|Trim|True|UBound|UCase|Until|VbCr|VbCrLf|VbLf|VbTab)$/im; - - return { - token: function(stream) { - if (stream.eatSpace()) return null; - var ch = stream.next(); - if (ch == "'") { - stream.skipToEnd(); - return "comment"; - } - if (ch == '"') { - stream.skipTo('"'); - return "string"; - } - - if (/\w/.test(ch)) { - stream.eatWhile(/\w/); - if (regexVBScriptKeyword.test(stream.current())) return "keyword"; - } - return null; +/* +For extra ASP classic objects, initialize CodeMirror instance with this option: + isASP: true + +E.G.: + var editor = CodeMirror.fromTextArea(document.getElementById("code"), { + lineNumbers: true, + isASP: true + }); +*/ +CodeMirror.defineMode("vbscript", function(conf, parserConf) { + var ERRORCLASS = 'error'; + + function wordRegexp(words) { + return new RegExp("^((" + words.join(")|(") + "))\\b", "i"); + } + + var singleOperators = new RegExp("^[\\+\\-\\*/&\\\\\\^<>=]"); + var doubleOperators = new RegExp("^((<>)|(<=)|(>=))"); + var singleDelimiters = new RegExp('^[\\.,]'); + var brakets = new RegExp('^[\\(\\)]'); + var identifiers = new RegExp("^[A-Za-z][_A-Za-z0-9]*"); + + var openingKeywords = ['class','sub','select','while','if','function', 'property', 'with', 'for']; + var middleKeywords = ['else','elseif','case']; + var endKeywords = ['next','loop','wend']; + + var wordOperators = wordRegexp(['and', 'or', 'not', 'xor', 'is', 'mod', 'eqv', 'imp']); + var commonkeywords = ['dim', 'redim', 'then', 'until', 'randomize', + 'byval','byref','new','property', 'exit', 'in', + 'const','private', 'public', + 'get','set','let', 'stop', 'on error resume next', 'on error goto 0', 'option explicit', 'call', 'me']; + + //This list was from: http://msdn.microsoft.com/en-us/library/f8tbc79x(v=vs.84).aspx + var atomWords = ['true', 'false', 'nothing', 'empty', 'null']; + //This list was from: http://msdn.microsoft.com/en-us/library/3ca8tfek(v=vs.84).aspx + var builtinFuncsWords = ['abs', 'array', 'asc', 'atn', 'cbool', 'cbyte', 'ccur', 'cdate', 'cdbl', 'chr', 'cint', 'clng', 'cos', 'csng', 'cstr', 'date', 'dateadd', 'datediff', 'datepart', + 'dateserial', 'datevalue', 'day', 'escape', 'eval', 'execute', 'exp', 'filter', 'formatcurrency', 'formatdatetime', 'formatnumber', 'formatpercent', 'getlocale', 'getobject', + 'getref', 'hex', 'hour', 'inputbox', 'instr', 'instrrev', 'int', 'fix', 'isarray', 'isdate', 'isempty', 'isnull', 'isnumeric', 'isobject', 'join', 'lbound', 'lcase', 'left', + 'len', 'loadpicture', 'log', 'ltrim', 'rtrim', 'trim', 'maths', 'mid', 'minute', 'month', 'monthname', 'msgbox', 'now', 'oct', 'replace', 'rgb', 'right', 'rnd', 'round', + 'scriptengine', 'scriptenginebuildversion', 'scriptenginemajorversion', 'scriptengineminorversion', 'second', 'setlocale', 'sgn', 'sin', 'space', 'split', 'sqr', 'strcomp', + 'string', 'strreverse', 'tan', 'time', 'timer', 'timeserial', 'timevalue', 'typename', 'ubound', 'ucase', 'unescape', 'vartype', 'weekday', 'weekdayname', 'year']; + + //This list was from: http://msdn.microsoft.com/en-us/library/ydz4cfk3(v=vs.84).aspx + var builtinConsts = ['vbBlack', 'vbRed', 'vbGreen', 'vbYellow', 'vbBlue', 'vbMagenta', 'vbCyan', 'vbWhite', 'vbBinaryCompare', 'vbTextCompare', + 'vbSunday', 'vbMonday', 'vbTuesday', 'vbWednesday', 'vbThursday', 'vbFriday', 'vbSaturday', 'vbUseSystemDayOfWeek', 'vbFirstJan1', 'vbFirstFourDays', 'vbFirstFullWeek', + 'vbGeneralDate', 'vbLongDate', 'vbShortDate', 'vbLongTime', 'vbShortTime', 'vbObjectError', + 'vbOKOnly', 'vbOKCancel', 'vbAbortRetryIgnore', 'vbYesNoCancel', 'vbYesNo', 'vbRetryCancel', 'vbCritical', 'vbQuestion', 'vbExclamation', 'vbInformation', 'vbDefaultButton1', 'vbDefaultButton2', + 'vbDefaultButton3', 'vbDefaultButton4', 'vbApplicationModal', 'vbSystemModal', 'vbOK', 'vbCancel', 'vbAbort', 'vbRetry', 'vbIgnore', 'vbYes', 'vbNo', + 'vbCr', 'VbCrLf', 'vbFormFeed', 'vbLf', 'vbNewLine', 'vbNullChar', 'vbNullString', 'vbTab', 'vbVerticalTab', 'vbUseDefault', 'vbTrue', 'vbFalse', + 'vbEmpty', 'vbNull', 'vbInteger', 'vbLong', 'vbSingle', 'vbDouble', 'vbCurrency', 'vbDate', 'vbString', 'vbObject', 'vbError', 'vbBoolean', 'vbVariant', 'vbDataObject', 'vbDecimal', 'vbByte', 'vbArray']; + //This list was from: http://msdn.microsoft.com/en-us/library/hkc375ea(v=vs.84).aspx + var builtinObjsWords = ['WScript', 'err', 'debug', 'RegExp']; + var knownProperties = ['description', 'firstindex', 'global', 'helpcontext', 'helpfile', 'ignorecase', 'length', 'number', 'pattern', 'source', 'value', 'count']; + var knownMethods = ['clear', 'execute', 'raise', 'replace', 'test', 'write', 'writeline', 'close', 'open', 'state', 'eof', 'update', 'addnew', 'end', 'createobject', 'quit']; + + var aspBuiltinObjsWords = ['server', 'response', 'request', 'session', 'application']; + var aspKnownProperties = ['buffer', 'cachecontrol', 'charset', 'contenttype', 'expires', 'expiresabsolute', 'isclientconnected', 'pics', 'status', //response + 'clientcertificate', 'cookies', 'form', 'querystring', 'servervariables', 'totalbytes', //request + 'contents', 'staticobjects', //application + 'codepage', 'lcid', 'sessionid', 'timeout', //session + 'scripttimeout']; //server + var aspKnownMethods = ['addheader', 'appendtolog', 'binarywrite', 'end', 'flush', 'redirect', //response + 'binaryread', //request + 'remove', 'removeall', 'lock', 'unlock', //application + 'abandon', //session + 'getlasterror', 'htmlencode', 'mappath', 'transfer', 'urlencode']; //server + + var knownWords = knownMethods.concat(knownProperties); + + builtinObjsWords = builtinObjsWords.concat(builtinConsts); + + if (conf.isASP){ + builtinObjsWords = builtinObjsWords.concat(aspBuiltinObjsWords); + knownWords = knownWords.concat(aspKnownMethods, aspKnownProperties); + }; + + var keywords = wordRegexp(commonkeywords); + var atoms = wordRegexp(atomWords); + var builtinFuncs = wordRegexp(builtinFuncsWords); + var builtinObjs = wordRegexp(builtinObjsWords); + var known = wordRegexp(knownWords); + var stringPrefixes = '"'; + + var opening = wordRegexp(openingKeywords); + var middle = wordRegexp(middleKeywords); + var closing = wordRegexp(endKeywords); + var doubleClosing = wordRegexp(['end']); + var doOpening = wordRegexp(['do']); + var noIndentWords = wordRegexp(['on error resume next', 'exit']); + var comment = wordRegexp(['rem']); + + + function indent(_stream, state) { + state.currentIndent++; + } + + function dedent(_stream, state) { + state.currentIndent--; + } + // tokenizers + function tokenBase(stream, state) { + if (stream.eatSpace()) { + return 'space'; + //return null; + } + + var ch = stream.peek(); + + // Handle Comments + if (ch === "'") { + stream.skipToEnd(); + return 'comment'; + } + if (stream.match(comment)){ + stream.skipToEnd(); + return 'comment'; + } + + + // Handle Number Literals + if (stream.match(/^((&H)|(&O))?[0-9\.]/i, false) && !stream.match(/^((&H)|(&O))?[0-9\.]+[a-z_]/i, false)) { + var floatLiteral = false; + // Floats + if (stream.match(/^\d*\.\d+/i)) { floatLiteral = true; } + else if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; } + else if (stream.match(/^\.\d+/)) { floatLiteral = true; } + + if (floatLiteral) { + // Float literals may be "imaginary" + stream.eat(/J/i); + return 'number'; + } + // Integers + var intLiteral = false; + // Hex + if (stream.match(/^&H[0-9a-f]+/i)) { intLiteral = true; } + // Octal + else if (stream.match(/^&O[0-7]+/i)) { intLiteral = true; } + // Decimal + else if (stream.match(/^[1-9]\d*F?/)) { + // Decimal literals may be "imaginary" + stream.eat(/J/i); + // TODO - Can you have imaginary longs? + intLiteral = true; + } + // Zero by itself with no other piece of number. + else if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; } + if (intLiteral) { + // Integer literals may be "long" + stream.eat(/L/i); + return 'number'; + } + } + + // Handle Strings + if (stream.match(stringPrefixes)) { + state.tokenize = tokenStringFactory(stream.current()); + return state.tokenize(stream, state); + } + + // Handle operators and Delimiters + if (stream.match(doubleOperators) + || stream.match(singleOperators) + || stream.match(wordOperators)) { + return 'operator'; + } + if (stream.match(singleDelimiters)) { + return null; + } + + if (stream.match(brakets)) { + return "bracket"; + } + + if (stream.match(noIndentWords)) { + state.doInCurrentLine = true; + + return 'keyword'; + } + + if (stream.match(doOpening)) { + indent(stream,state); + state.doInCurrentLine = true; + + return 'keyword'; + } + if (stream.match(opening)) { + if (! state.doInCurrentLine) + indent(stream,state); + else + state.doInCurrentLine = false; + + return 'keyword'; + } + if (stream.match(middle)) { + return 'keyword'; + } + + + if (stream.match(doubleClosing)) { + dedent(stream,state); + dedent(stream,state); + + return 'keyword'; + } + if (stream.match(closing)) { + if (! state.doInCurrentLine) + dedent(stream,state); + else + state.doInCurrentLine = false; + + return 'keyword'; + } + + if (stream.match(keywords)) { + return 'keyword'; + } + + if (stream.match(atoms)) { + return 'atom'; + } + + if (stream.match(known)) { + return 'variable-2'; + } + + if (stream.match(builtinFuncs)) { + return 'builtin'; + } + + if (stream.match(builtinObjs)){ + return 'variable-2'; + } + + if (stream.match(identifiers)) { + return 'variable'; + } + + // Handle non-detected items + stream.next(); + return ERRORCLASS; } - }; + + function tokenStringFactory(delimiter) { + var singleline = delimiter.length == 1; + var OUTCLASS = 'string'; + + return function(stream, state) { + while (!stream.eol()) { + stream.eatWhile(/[^'"]/); + if (stream.match(delimiter)) { + state.tokenize = tokenBase; + return OUTCLASS; + } else { + stream.eat(/['"]/); + } + } + if (singleline) { + if (parserConf.singleLineStringErrors) { + return ERRORCLASS; + } else { + state.tokenize = tokenBase; + } + } + return OUTCLASS; + }; + } + + + function tokenLexer(stream, state) { + var style = state.tokenize(stream, state); + var current = stream.current(); + + // Handle '.' connected identifiers + if (current === '.') { + style = state.tokenize(stream, state); + + current = stream.current(); + if (style.substr(0, 8) === 'variable' || style==='builtin' || style==='keyword'){//|| knownWords.indexOf(current.substring(1)) > -1) { + if (style === 'builtin' || style === 'keyword') style='variable'; + if (knownWords.indexOf(current.substr(1)) > -1) style='variable-2'; + + return style; + } else { + return ERRORCLASS; + } + } + + return style; + } + + var external = { + electricChars:"dDpPtTfFeE ", + startState: function() { + return { + tokenize: tokenBase, + lastToken: null, + currentIndent: 0, + nextLineIndent: 0, + doInCurrentLine: false, + ignoreKeyword: false + + + }; + }, + + token: function(stream, state) { + if (stream.sol()) { + state.currentIndent += state.nextLineIndent; + state.nextLineIndent = 0; + state.doInCurrentLine = 0; + } + var style = tokenLexer(stream, state); + + state.lastToken = {style:style, content: stream.current()}; + + if (style==='space') style=null; + + return style; + }, + + indent: function(state, textAfter) { + var trueText = textAfter.replace(/^\s+|\s+$/g, '') ; + if (trueText.match(closing) || trueText.match(doubleClosing) || trueText.match(middle)) return conf.indentUnit*(state.currentIndent-1); + if(state.currentIndent < 0) return 0; + return state.currentIndent * conf.indentUnit; + } + + }; + return external; }); CodeMirror.defineMIME("text/vbscript", "vbscript"); diff --git a/mode/xml/xml.js b/mode/xml/xml.js index ae71c64130..84f34a2934 100644 --- a/mode/xml/xml.js +++ b/mode/xml/xml.js @@ -1,6 +1,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { var indentUnit = config.indentUnit; var multilineTagIndentFactor = parserConfig.multilineTagIndentFactor || 1; + var multilineTagIndentPastTag = parserConfig.multilineTagIndentPastTag || true; var Kludges = parserConfig.htmlMode ? { autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true, @@ -111,6 +112,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { return "error"; } else if (/[\'\"]/.test(ch)) { state.tokenize = inAttribute(ch); + state.stringStartCol = stream.column(); return state.tokenize(stream, state); } else { stream.eatWhile(/[^\s\u00a0=<>\"\']/); @@ -119,7 +121,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { } function inAttribute(quote) { - return function(stream, state) { + var closure = function(stream, state) { while (!stream.eol()) { if (stream.next() == quote) { state.tokenize = inTag; @@ -128,6 +130,8 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { } return "string"; }; + closure.isInAttribute = true; + return closure; } function inBlock(style, terminator) { @@ -299,10 +303,20 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { indent: function(state, textAfter, fullLine) { var context = state.context; + // Indent multi-line strings (e.g. css). + if (state.tokenize.isInAttribute) { + return state.stringStartCol + 1; + } if ((state.tokenize != inTag && state.tokenize != inText) || context && context.noIndent) return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; - if (state.tagName) return state.tagStart + indentUnit * multilineTagIndentFactor; + // Indent the starts of attribute names. + if (state.tagName) { + if (multilineTagIndentPastTag) + return state.tagStart + state.tagName.length + 2; + else + return state.tagStart + indentUnit * multilineTagIndentFactor; + } if (alignCDATA && /", - configuration: parserConfig.htmlMode ? "html" : "xml" + configuration: parserConfig.htmlMode ? "html" : "xml", + helperType: parserConfig.htmlMode ? "html" : "xml" }; }); diff --git a/package.json b/package.json index 9cc8a6962b..7fc90708b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version":"3.14.0", + "version":"3.15.0", "main": "lib/codemirror.js", "description": "In-browser code editing made bearable", "licenses": [{"type": "MIT", diff --git a/test/index.html b/test/index.html index 4a827e643a..5ead383a1d 100644 --- a/test/index.html +++ b/test/index.html @@ -60,6 +60,7 @@ + diff --git a/test/mode_test.js b/test/mode_test.js index c759b257bf..1439cab32c 100644 --- a/test/mode_test.js +++ b/test/mode_test.js @@ -184,7 +184,7 @@ } s += ''; for (var i = 0; i < output.length; i += 2) { - s += '' + output[i] + ''; + s += '' + (output[i] || null) + ''; } s += ''; return s; diff --git a/test/test.js b/test/test.js index 2bcc450288..67b25d00e7 100644 --- a/test/test.js +++ b/test/test.js @@ -499,6 +499,24 @@ testCM("bookmarkInsertLeft", function(cm) { eqPos(bl.find(), Pos(0, 1)); }, {value: "abcdef"}); +testCM("bookmarkCursor", function(cm) { + var pos01 = cm.cursorCoords(Pos(0, 1)), pos11 = cm.cursorCoords(Pos(1, 1)), + pos20 = cm.cursorCoords(Pos(2, 0)), pos30 = cm.cursorCoords(Pos(3, 0)), + pos41 = cm.cursorCoords(Pos(4, 1)); + cm.setBookmark(Pos(0, 1), {widget: document.createTextNode("←"), insertLeft: true}); + cm.setBookmark(Pos(2, 0), {widget: document.createTextNode("←"), insertLeft: true}); + cm.setBookmark(Pos(1, 1), {widget: document.createTextNode("→")}); + cm.setBookmark(Pos(3, 0), {widget: document.createTextNode("→")}); + var new01 = cm.cursorCoords(Pos(0, 1)), new11 = cm.cursorCoords(Pos(1, 1)), + new20 = cm.cursorCoords(Pos(2, 0)), new30 = cm.cursorCoords(Pos(3, 0)); + is(new01.left == pos01.left && new01.top == pos01.top, "at left, middle of line"); + is(new11.left > pos11.left && new11.top == pos11.top, "at right, middle of line"); + is(new20.left == pos20.left && new20.top == pos20.top, "at left, empty line"); + is(new30.left > pos30.left && new30.top == pos30.top, "at right, empty line"); + cm.setBookmark(Pos(4, 0), {widget: document.createTextNode("→")}); + is(cm.cursorCoords(Pos(4, 1)).left > pos41.left, "single-char bug"); +}, {value: "foo\nbar\n\n\nx\ny"}); + testCM("getAllMarks", function(cm) { addDoc(cm, 10, 10); var m1 = cm.setBookmark(Pos(0, 2)); @@ -823,6 +841,17 @@ testCM("wrappingInlineWidget", function(cm) { eq(curR.bottom, cur1.bottom); }, {value: "1 2 3 xxx 4", lineWrapping: true}); +testCM("changedInlineWidget", function(cm) { + cm.setSize("10em"); + var w = document.createElement("span"); + w.innerHTML = "x"; + var m = cm.markText(Pos(0, 4), Pos(0, 5), {replacedWith: w}); + w.innerHTML = "and now the widget is really really long all of a sudden and a scrollbar is needed"; + m.changed(); + var hScroll = byClassName(cm.getWrapperElement(), "CodeMirror-hscrollbar")[0]; + is(hScroll.scrollWidth > hScroll.clientWidth); +}, {value: "hello there"}); + testCM("inlineWidget", function(cm) { var w = cm.setBookmark(Pos(0, 2), {widget: document.createTextNode("uu")}); cm.setCursor(0, 2); diff --git a/test/vim_test.js b/test/vim_test.js index 47fc6cd14f..ec0fae06c0 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -99,7 +99,7 @@ function copyCursor(cur) { function testVim(name, run, opts, expectedFail) { var vimOpts = { lineNumbers: true, - keyMap: 'vim', + vimMode: true, showCursorWhenSelecting: true, value: code }; @@ -111,8 +111,7 @@ function testVim(name, run, opts, expectedFail) { return test('vim_' + name, function() { var place = document.getElementById("testground"); var cm = CodeMirror(place, vimOpts); - CodeMirror.Vim.maybeInitState(cm); - var vim = cm.vimState; + var vim = CodeMirror.Vim.maybeInitVimState_(cm); function doKeysFn(cm) { return function(args) { @@ -140,7 +139,7 @@ function testVim(name, run, opts, expectedFail) { // Find key in keymap and handle. var handled = CodeMirror.lookupKey(key, ['vim-insert'], executeHandler); // Record for insert mode. - if (handled === true && cm.vimState.insertMode && arguments[i] != 'Esc') { + if (handled === true && cm.state.vim.insertMode && arguments[i] != 'Esc') { var lastChange = CodeMirror.Vim.getVimGlobalState_().macroModeState.lastInsertModeChanges; if (lastChange) { lastChange.changes.push(new CodeMirror.Vim.InsertModeKey(key)); @@ -184,7 +183,7 @@ function testVim(name, run, opts, expectedFail) { return CodeMirror.Vim.getRegisterController(); } } - CodeMirror.Vim.clearVimGlobalState_(); + CodeMirror.Vim.resetVimGlobalState_(); var successful = false; try { run(cm, vim, helpers); @@ -228,7 +227,7 @@ function testJumplist(name, keys, endPos, startPos, dialog) { endPos = makeCursor(endPos[0], endPos[1]); startPos = makeCursor(startPos[0], startPos[1]); testVim(name, function(cm, vim, helpers) { - CodeMirror.Vim.clearVimGlobalState_(); + CodeMirror.Vim.resetVimGlobalState_(); if(dialog)cm.openDialog = helpers.fakeOpenDialog('word'); cm.setCursor(startPos); helpers.doKeys.apply(null, keys); @@ -296,8 +295,10 @@ testMotion('l', 'l', makeCursor(0, 1)); testMotion('l_repeat', ['2', 'l'], makeCursor(0, 2)); testMotion('j', 'j', offsetCursor(word1.end, 1, 0), word1.end); testMotion('j_repeat', ['2', 'j'], offsetCursor(word1.end, 2, 0), word1.end); +testMotion('j_repeat_clip', ['1000', 'j'], endOfDocument); testMotion('k', 'k', offsetCursor(word3.end, -1, 0), word3.end); testMotion('k_repeat', ['2', 'k'], makeCursor(0, 4), makeCursor(2, 4)); +testMotion('k_repeat_clip', ['1000', 'k'], makeCursor(0, 4), makeCursor(2, 4)); testMotion('w', 'w', word1.start); testMotion('w_multiple_newlines_no_space', 'w', makeCursor(12, 2), makeCursor(11, 2)); testMotion('w_multiple_newlines_with_space', 'w', makeCursor(14, 0), makeCursor(12, 51)); @@ -499,15 +500,13 @@ testVim('dl', function(cm, vim, helpers) { eqPos(curStart, cm.getCursor()); }, { value: ' word1 ' }); testVim('dl_eol', function(cm, vim, helpers) { - // TODO: This test is incorrect. The cursor should end up at (0, 5). - var curStart = makeCursor(0, 6); - cm.setCursor(curStart); + cm.setCursor(0, 6); helpers.doKeys('d', 'l'); eq(' word1', cm.getValue()); var register = helpers.getRegisterController().getRegister(); eq(' ', register.text); 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); @@ -592,38 +591,35 @@ testVim('dw_word', function(cm, vim, helpers) { 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. - var curStart = makeCursor(0, 1); - cm.setCursor(curStart); + cm.setCursor(0, 1); helpers.doKeys('d', 'w'); eq(' ', cm.getValue()); var register = helpers.getRegisterController().getRegister(); eq('word1 ', register.text); is(!register.linewise); - eqPos(curStart, cm.getCursor()); + 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 // of line. - var curStart = makeCursor(0, 1); - cm.setCursor(curStart); + cm.setCursor(0, 1); helpers.doKeys('d', 'w'); eq(' \nword2', cm.getValue()); var register = helpers.getRegisterController().getRegister(); eq('word1', register.text); is(!register.linewise); - eqPos(curStart, cm.getCursor()); + 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 // of line and it is followed by multiple newlines. - var curStart = makeCursor(0, 1); - cm.setCursor(curStart); + cm.setCursor(0, 1); helpers.doKeys('d', 'w'); eq(' \n\nword2', cm.getValue()); var register = helpers.getRegisterController().getRegister(); eq('word1', register.text); is(!register.linewise); - eqPos(curStart, cm.getCursor()); + helpers.assertCursorAt(0, 0); }, { value: ' word1\n\nword2' }); testVim('dw_empty_line_followed_by_whitespace', function(cm, vim, helpers) { cm.setCursor(0, 0); @@ -663,14 +659,13 @@ testVim('dw_end_of_document', function(cm, vim, helpers) { testVim('dw_repeat', function(cm, vim, helpers) { // Assert that dw does delete newline if it should go to the next line, and // that repeat works properly. - var curStart = makeCursor(0, 1); - cm.setCursor(curStart); + cm.setCursor(0, 1); helpers.doKeys('d', '2', 'w'); eq(' ', cm.getValue()); var register = helpers.getRegisterController().getRegister(); eq('word1\nword2', register.text); is(!register.linewise); - eqPos(curStart, cm.getCursor()); + helpers.assertCursorAt(0, 0); }, { value: ' word1\nword2' }); testVim('de_word_start_and_empty_lines', function(cm, vim, helpers) { cm.setCursor(0, 0); @@ -818,12 +813,6 @@ testVim('dd_lastline', function(cm, vim, helpers) { eq(expectedLineCount, cm.lineCount()); helpers.assertCursorAt(cm.lineCount() - 1, 0); }); -testVim('cw', function(cm, vim, helpers) { - cm.setCursor(0, 0); - helpers.doKeys('c', '2', 'w'); - eq(' word3', cm.getValue()); - helpers.assertCursorAt(0, 0); -}, { value: 'word1 word2 word3'}); // Yank commands should behave the exact same as d commands, expect that nothing // gets deleted. testVim('yw_repeat', function(cm, vim, helpers) { @@ -854,6 +843,12 @@ testVim('yy_multiply_repeat', function(cm, vim, helpers) { // Change commands behave like d commands except that it also enters insert // mode. In addition, when the change is linewise, an additional newline is // inserted so that insert mode starts on that line. +testVim('cw', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('c', '2', 'w'); + eq(' word3', cm.getValue()); + helpers.assertCursorAt(0, 0); +}, { value: 'word1 word2 word3'}); testVim('cw_repeat', function(cm, vim, helpers) { // Assert that cw does delete newline if it should go to the next line, and // that repeat works properly. @@ -877,9 +872,14 @@ testVim('cc_multiply_repeat', function(cm, vim, helpers) { var register = helpers.getRegisterController().getRegister(); eq(expectedBuffer, register.text); is(register.linewise); - helpers.assertCursorAt(0, lines[0].textStart); eq('vim-insert', cm.getOption('keyMap')); }); +testVim('cc_append', function(cm, vim, helpers) { + var expectedLineCount = cm.lineCount(); + cm.setCursor(cm.lastLine(), 0); + helpers.doKeys('c', 'c'); + eq(expectedLineCount, cm.lineCount()); +}); // Swapcase commands edit in place and do not modify registers. testVim('g~w_repeat', function(cm, vim, helpers) { // Assert that dw does delete newline if it should go to the next line, and @@ -994,14 +994,13 @@ testEdit('daW_end_punct', 'foo \tbAr.', /A/, 'daW', 'foo'); // Operator-motion tests testVim('D', function(cm, vim, helpers) { - var curStart = makeCursor(0, 3); - cm.setCursor(curStart); + cm.setCursor(0, 3); helpers.doKeys('D'); eq(' wo\nword2\n word3', cm.getValue()); var register = helpers.getRegisterController().getRegister(); eq('rd1', register.text); 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); @@ -1011,7 +1010,7 @@ testVim('C', function(cm, vim, helpers) { var register = helpers.getRegisterController().getRegister(); eq('rd1', register.text); is(!register.linewise); - helpers.assertCursorAt(0, 3); + eqPos(curStart, cm.getCursor()); eq('vim-insert', cm.getOption('keyMap')); }, { value: ' word1\nword2\n word3' }); testVim('Y', function(cm, vim, helpers) { @@ -1024,6 +1023,11 @@ testVim('Y', function(cm, vim, helpers) { is(!register.linewise); helpers.assertCursorAt(0, 3); }, { value: ' word1\nword2\n word3' }); +testVim('~', function(cm, vim, helpers) { + helpers.doKeys('3', '~'); + eq('ABCdefg', cm.getValue()); + helpers.assertCursorAt(0, 3); +}, { value: 'abcdefg' }); // Action tests testVim('ctrl-a', function(cm, vim, helpers) { @@ -1471,6 +1475,10 @@ testVim('visual_join', function(cm, vim, helpers) { helpers.doKeys('l', 'V', 'l', 'j', 'j', 'J'); eq(' 1 2 3\n 4\n 5', cm.getValue()); }, { value: ' 1\n 2\n 3\n 4\n 5' }); +testVim('visual_blank', function(cm, vim, helpers) { + helpers.doKeys('v', 'k'); + eq(vim.visualMode, true); +}, { value: '\n' }); testVim('/ and n/N', function(cm, vim, helpers) { cm.openDialog = helpers.fakeOpenDialog('match'); helpers.doKeys('/'); diff --git a/theme/3024-day.css b/theme/3024-day.css new file mode 100644 index 0000000000..e931a49de4 --- /dev/null +++ b/theme/3024-day.css @@ -0,0 +1,33 @@ +/* + + Name: 3024 day + Author: Jan T. Sott (http://github.com/idleberg) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-3024-day.CodeMirror {background: #f7f7f7; color: #3a3432;} +.cm-s-3024-day div.CodeMirror-selected {background: #d6d5d4 !important;} +.cm-s-3024-day .CodeMirror-gutters {background: #f7f7f7; border-right: 0px;} +.cm-s-3024-day .CodeMirror-linenumber {color: #807d7c;} +.cm-s-3024-day .CodeMirror-cursor {border-left: 1px solid #5c5855 !important;} + +.cm-s-3024-day span.cm-comment {color: #cdab53;} +.cm-s-3024-day span.cm-atom {color: #a16a94;} +.cm-s-3024-day span.cm-number {color: #a16a94;} + +.cm-s-3024-day span.cm-property, .cm-s-3024-day span.cm-attribute {color: #01a252;} +.cm-s-3024-day span.cm-keyword {color: #db2d20;} +.cm-s-3024-day span.cm-string {color: #fded02;} + +.cm-s-3024-day span.cm-variable {color: #01a252;} +.cm-s-3024-day span.cm-variable-2 {color: #01a0e4;} +.cm-s-3024-day span.cm-def {color: #e8bbd0;} +.cm-s-3024-day span.cm-error {background: #db2d20; color: #5c5855;} +.cm-s-3024-day span.cm-bracket {color: #3a3432;} +.cm-s-3024-day span.cm-tag {color: #db2d20;} +.cm-s-3024-day span.cm-link {color: #a16a94;} + +.cm-s-3024-day .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} diff --git a/theme/3024-night.css b/theme/3024-night.css new file mode 100644 index 0000000000..87c7f8abe0 --- /dev/null +++ b/theme/3024-night.css @@ -0,0 +1,33 @@ +/* + + Name: 3024 night + Author: Jan T. Sott (http://github.com/idleberg) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-3024-night.CodeMirror {background: #090300; color: #d6d5d4;} +.cm-s-3024-night div.CodeMirror-selected {background: #3a3432 !important;} +.cm-s-3024-night .CodeMirror-gutters {background: #090300; border-right: 0px;} +.cm-s-3024-night .CodeMirror-linenumber {color: #5c5855;} +.cm-s-3024-night .CodeMirror-cursor {border-left: 1px solid #807d7c !important;} + +.cm-s-3024-night span.cm-comment {color: #cdab53;} +.cm-s-3024-night span.cm-atom {color: #a16a94;} +.cm-s-3024-night span.cm-number {color: #a16a94;} + +.cm-s-3024-night span.cm-property, .cm-s-3024-night span.cm-attribute {color: #01a252;} +.cm-s-3024-night span.cm-keyword {color: #db2d20;} +.cm-s-3024-night span.cm-string {color: #fded02;} + +.cm-s-3024-night span.cm-variable {color: #01a252;} +.cm-s-3024-night span.cm-variable-2 {color: #01a0e4;} +.cm-s-3024-night span.cm-def {color: #e8bbd0;} +.cm-s-3024-night span.cm-error {background: #db2d20; color: #807d7c;} +.cm-s-3024-night span.cm-bracket {color: #d6d5d4;} +.cm-s-3024-night span.cm-tag {color: #db2d20;} +.cm-s-3024-night span.cm-link {color: #a16a94;} + +.cm-s-3024-night .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} diff --git a/theme/base16-dark.css b/theme/base16-dark.css new file mode 100644 index 0000000000..7a818b75f5 --- /dev/null +++ b/theme/base16-dark.css @@ -0,0 +1,33 @@ +/* + + Name: Base16 Default Dark + Author: Chris Kempson (http://chriskempson.com) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-chrome-devtools) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-base16-dark.CodeMirror {background: #151515; color: #e0e0e0;} +.cm-s-base16-dark div.CodeMirror-selected {background: #202020 !important;} +.cm-s-base16-dark .CodeMirror-gutters {background: #151515; border-right: 0px;} +.cm-s-base16-dark .CodeMirror-linenumber {color: #505050;} +.cm-s-base16-dark .CodeMirror-cursor {border-left: 1px solid #b0b0b0 !important;} + +.cm-s-base16-dark span.cm-comment {color: #8f5536;} +.cm-s-base16-dark span.cm-atom {color: #aa759f;} +.cm-s-base16-dark span.cm-number {color: #aa759f;} + +.cm-s-base16-dark span.cm-property, .cm-s-base16-dark span.cm-attribute {color: #90a959;} +.cm-s-base16-dark span.cm-keyword {color: #ac4142;} +.cm-s-base16-dark span.cm-string {color: #f4bf75;} + +.cm-s-base16-dark span.cm-variable {color: #90a959;} +.cm-s-base16-dark span.cm-variable-2 {color: #6a9fb5;} +.cm-s-base16-dark span.cm-def {color: #d28445;} +.cm-s-base16-dark span.cm-error {background: #ac4142; color: #b0b0b0;} +.cm-s-base16-dark span.cm-bracket {color: #e0e0e0;} +.cm-s-base16-dark span.cm-tag {color: #ac4142;} +.cm-s-base16-dark span.cm-link {color: #aa759f;} + +.cm-s-base16-dark .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} diff --git a/theme/base16-light.css b/theme/base16-light.css new file mode 100644 index 0000000000..a3b2d4dcba --- /dev/null +++ b/theme/base16-light.css @@ -0,0 +1,33 @@ +/* + + Name: Base16 Default Light + Author: Chris Kempson (http://chriskempson.com) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-chrome-devtools) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-base16-light.CodeMirror {background: #f5f5f5; color: #202020;} +.cm-s-base16-light div.CodeMirror-selected {background: #e0e0e0 !important;} +.cm-s-base16-light .CodeMirror-gutters {background: #f5f5f5; border-right: 0px;} +.cm-s-base16-light .CodeMirror-linenumber {color: #b0b0b0;} +.cm-s-base16-light .CodeMirror-cursor {border-left: 1px solid #505050 !important;} + +.cm-s-base16-light span.cm-comment {color: #8f5536;} +.cm-s-base16-light span.cm-atom {color: #aa759f;} +.cm-s-base16-light span.cm-number {color: #aa759f;} + +.cm-s-base16-light span.cm-property, .cm-s-base16-light span.cm-attribute {color: #90a959;} +.cm-s-base16-light span.cm-keyword {color: #ac4142;} +.cm-s-base16-light span.cm-string {color: #f4bf75;} + +.cm-s-base16-light span.cm-variable {color: #90a959;} +.cm-s-base16-light span.cm-variable-2 {color: #6a9fb5;} +.cm-s-base16-light span.cm-def {color: #d28445;} +.cm-s-base16-light span.cm-error {background: #ac4142; color: #505050;} +.cm-s-base16-light span.cm-bracket {color: #202020;} +.cm-s-base16-light span.cm-tag {color: #ac4142;} +.cm-s-base16-light span.cm-link {color: #aa759f;} + +.cm-s-base16-light .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} diff --git a/theme/lesser-dark.css b/theme/lesser-dark.css index 67f71ad727..0bd08f5c6d 100644 --- a/theme/lesser-dark.css +++ b/theme/lesser-dark.css @@ -14,7 +14,7 @@ Ported to CodeMirror by Peter Kroon .cm-s-lesser-dark .CodeMirror-cursor { border-left: 1px solid white !important; } .cm-s-lesser-dark pre { padding: 0 8px; }/*editable code holder*/ -div.CodeMirror span.CodeMirror-matchingbracket { color: #7EFC7E; }/*65FC65*/ +.cm-s-lesser-dark.CodeMirror span.CodeMirror-matchingbracket { color: #7EFC7E; }/*65FC65*/ .cm-s-lesser-dark .CodeMirror-gutters { background: #262626; border-right:1px solid #aaa; } .cm-s-lesser-dark .CodeMirror-linenumber { color: #777; } diff --git a/theme/midnight.css b/theme/midnight.css index e567625c6c..bc2d49be1c 100644 --- a/theme/midnight.css +++ b/theme/midnight.css @@ -1,22 +1,18 @@ /* Based on the theme at http://bonsaiden.github.com/JavaScript-Garden */ -/**/ -.breakpoints {width: .8em;} -.breakpoint { color: #822; } - /**/ -span.CodeMirror-matchhighlight { background: #494949 } -.CodeMirror-focused span.CodeMirror-matchhighlight { background: #314D67; !important } +.cm-s-midnight span.CodeMirror-matchhighlight { background: #494949 } +.cm-s-midnight.CodeMirror-focused span.CodeMirror-matchhighlight { background: #314D67 !important; } /**/ -.activeline {background: #253540 !important;} +.cm-s-midnight .activeline {background: #253540 !important;} .cm-s-midnight.CodeMirror { background: #0F192A; color: #D1EDFF; } -.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;} +.cm-s-midnight.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;} .cm-s-midnight div.CodeMirror-selected {background: #314D67 !important;} .cm-s-midnight .CodeMirror-gutters {background: #0F192A; border-right: 1px solid;} @@ -29,7 +25,7 @@ span.CodeMirror-matchhighlight { background: #494949 } .cm-s-midnight span.cm-atom {color: #AE81FF;} .cm-s-midnight span.cm-number {color: #D1EDFF;} -.cm-s-midnight span.cm-property, .cm-s-tropicaleve span.cm-attribute {color: #A6E22E;} +.cm-s-midnight span.cm-property, .cm-s-midnight span.cm-attribute {color: #A6E22E;} .cm-s-midnight span.cm-keyword {color: #E83737;} .cm-s-midnight span.cm-string {color: #1DC116;} @@ -45,8 +41,3 @@ span.CodeMirror-matchhighlight { background: #494949 } text-decoration: underline; color: white !important; } - -.typ { color: #FFAA3E; } -.atn { color: #606; } -.atv { color: #080; } -.dec { color: #606; } diff --git a/theme/tomorrow-night-eighties.css b/theme/tomorrow-night-eighties.css new file mode 100644 index 0000000000..d682120c74 --- /dev/null +++ b/theme/tomorrow-night-eighties.css @@ -0,0 +1,33 @@ +/* + + Name: Tomorrow Night - Eighties + Author: Chris Kempson + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-tomorrow-night-eighties.CodeMirror {background: #000000; color: #CCCCCC;} +.cm-s-tomorrow-night-eighties div.CodeMirror-selected {background: #2D2D2D !important;} +.cm-s-tomorrow-night-eighties .CodeMirror-gutters {background: #000000; border-right: 0px;} +.cm-s-tomorrow-night-eighties .CodeMirror-linenumber {color: #515151;} +.cm-s-tomorrow-night-eighties .CodeMirror-cursor {border-left: 1px solid #6A6A6A !important;} + +.cm-s-tomorrow-night-eighties span.cm-comment {color: #d27b53;} +.cm-s-tomorrow-night-eighties span.cm-atom {color: #a16a94;} +.cm-s-tomorrow-night-eighties span.cm-number {color: #a16a94;} + +.cm-s-tomorrow-night-eighties span.cm-property, .cm-s-tomorrow-night-eighties span.cm-attribute {color: #99cc99;} +.cm-s-tomorrow-night-eighties span.cm-keyword {color: #f2777a;} +.cm-s-tomorrow-night-eighties span.cm-string {color: #ffcc66;} + +.cm-s-tomorrow-night-eighties span.cm-variable {color: #99cc99;} +.cm-s-tomorrow-night-eighties span.cm-variable-2 {color: #6699cc;} +.cm-s-tomorrow-night-eighties span.cm-def {color: #f99157;} +.cm-s-tomorrow-night-eighties span.cm-error {background: #f2777a; color: #6A6A6A;} +.cm-s-tomorrow-night-eighties span.cm-bracket {color: #CCCCCC;} +.cm-s-tomorrow-night-eighties span.cm-tag {color: #f2777a;} +.cm-s-tomorrow-night-eighties span.cm-link {color: #a16a94;} + +.cm-s-tomorrow-night-eighties .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} diff --git a/theme/vibrant-ink.css b/theme/vibrant-ink.css index 22024a489a..4445be2d71 100644 --- a/theme/vibrant-ink.css +++ b/theme/vibrant-ink.css @@ -11,8 +11,8 @@ .cm-s-vibrant-ink .cm-atom { color: #FC0; } .cm-s-vibrant-ink .cm-number { color: #FFEE98; } .cm-s-vibrant-ink .cm-def { color: #8DA6CE; } -.cm-s-vibrant-ink span.cm-variable-2, .cm-s-cobalt span.cm-tag { color: #FFC66D } -.cm-s-vibrant-ink span.cm-variable-3, .cm-s-cobalt span.cm-def { color: #FFC66D } +.cm-s-vibrant-ink span.cm-variable-2, .cm-s-vibrant span.cm-tag { color: #FFC66D } +.cm-s-vibrant-ink span.cm-variable-3, .cm-s-vibrant span.cm-def { color: #FFC66D } .cm-s-vibrant-ink .cm-operator { color: #888; } .cm-s-vibrant-ink .cm-comment { color: gray; font-weight: bold; } .cm-s-vibrant-ink .cm-string { color: #A5C25C }