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.
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.