From 43d0608edc5125606248d2d70961b5a6055ec92b Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Thu, 20 Jun 2013 10:46:52 +0200
Subject: [PATCH 001/110] Bump version number post-3.14
---
lib/codemirror.js | 2 +-
package.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/codemirror.js b/lib/codemirror.js
index 92fd188728..5da1f17fea 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -5707,7 +5707,7 @@ window.CodeMirror = (function() {
// THE END
- CodeMirror.version = "3.14.0";
+ CodeMirror.version = "3.14.1";
return CodeMirror;
})();
diff --git a/package.json b/package.json
index 9cc8a6962b..17e5487f25 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "codemirror",
- "version":"3.14.0",
+ "version":"3.14.1",
"main": "lib/codemirror.js",
"description": "In-browser code editing made bearable",
"licenses": [{"type": "MIT",
From 59e5b5cbda7267dd10505e92493cc671b3002abe Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Thu, 20 Jun 2013 11:23:53 +0200
Subject: [PATCH 002/110] [merge addon] Fix layout on Firefox
Work around percentage width rounding problems.
---
addon/merge/merge.css | 12 ++++++++++--
addon/merge/merge.js | 2 ++
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/addon/merge/merge.css b/addon/merge/merge.css
index 1f20f88a43..1302532091 100644
--- a/addon/merge/merge.css
+++ b/addon/merge/merge.css
@@ -1,6 +1,7 @@
.CodeMirror-diff {
position: relative;
border: 1px solid #ddd;
+ white-space: pre;
}
.CodeMirror-diff, .CodeMirror-diff .CodeMirror {
@@ -13,11 +14,18 @@
.CodeMirror-diff-3pane .CodeMirror-diff-gap { width: 3.5%; }
.CodeMirror-diff-pane {
- float: left;
+ display: inline-block;
+ white-space: normal;
+}
+.CodeMirror-diff-pane-rightmost {
+ position: absolute;
+ right: 0px;
+ z-index: 1;
}
.CodeMirror-diff-gap {
- float: left;
+ z-index: 2;
+ display: inline-block;
height: 100%;
box-sizing: border-box;
overflow: hidden;
diff --git a/addon/merge/merge.js b/addon/merge/merge.js
index b112518596..0e813f3e25 100644
--- a/addon/merge/merge.js
+++ b/addon/merge/merge.js
@@ -265,6 +265,8 @@
wrap.push(rightPane);
}
+ (hasRight ? rightPane : editPane).className += " CodeMirror-diff-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"));
this.edit = CodeMirror(editPane, copyObj(options));
From 607b32d857eac4315407d428a3245e971b212490 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Thu, 20 Jun 2013 13:35:13 +0200
Subject: [PATCH 003/110] [tern addon] Work-in-progress
---
addon/tern/tern.css | 84 +++++++++
addon/tern/tern.js | 531 ++++++++++++++++++++++++++++++++++++++++++++++++++++
demo/tern.html | 95 ++++++++++
3 files changed, 710 insertions(+)
create mode 100644 addon/tern/tern.css
create mode 100644 addon/tern/tern.js
create mode 100644 demo/tern.html
diff --git a/addon/tern/tern.css b/addon/tern/tern.css
new file mode 100644
index 0000000000..e8d5731861
--- /dev/null
+++ b/addon/tern/tern.css
@@ -0,0 +1,84 @@
+.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;
+ 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..662736637c
--- /dev/null
+++ b/addon/tern/tern.js
@@ -0,0 +1,531 @@
+// 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.
+// * 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.
+
+(function() {
+ "use strict";
+
+ CodeMirror.getURL = function(url, c) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("get", url, true);
+ xhr.send();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState != 4) return;
+ if (xhr.status < 400) return c(null, xhr.responseText);
+ var e = new Error(xhr.responseText || "No response");
+ e.status = xhr.status;
+ c(e);
+ };
+ };
+
+ CodeMirror.TernServer = function(options) {
+ var self = this;
+ this.options = options || {};
+ var plugins = this.options.plugins || {};
+ if (!plugins.doc_comment) plugins.doc_comment = true;
+ 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) {
+ this.server.addFile(name, doc.getValue());
+ CodeMirror.on(doc, "change", this.trackChange);
+ return this.docs[name] = {doc: doc, name: name, changed: null};
+ },
+
+ delDoc: function(name) {
+ var found = this.docs[name];
+ if (!found) return;
+ CodeMirror.on(found.doc, "change", this.trackChange);
+ delete this.docs[name];
+ this.server.delFile(name);
+ },
+
+ complete: function(cm) {
+ var self = this;
+ CodeMirror.showHint(cm, function(cm, c) { return hint(self, cm, c); }, {async: true});
+ },
+
+ 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); }
+ };
+
+ var Pos = CodeMirror.Pos;
+ var cls = "CodeMirror-Tern-";
+ var bigDoc = 250;
+
+ function getFile(ts, name, c) {
+ var buf = ts.docs[name];
+ if (buf)
+ c(buf.doc.getValue());
+ else if (ts.options.getFile)
+ ts.options.getFile(name, c);
+ else
+ c(null);
+ }
+
+ function findDoc(ts, doc, name) {
+ for (var name in ts.docs) {
+ var cur = ts.docs[name];
+ if (cur.doc == doc) return cur;
+ }
+ if (!name) for (var i = 0;; ++i) {
+ var n = "[doc" + (i || "") + "]";
+ if (!ts.docs[n]) { name = n; break; }
+ }
+ return ts.addDoc(n, 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: doc.doc.getValue()}]}, function(error) {
+ if (error) console.error(error);
+ else doc.changed = null;
+ });
+ }
+
+ // Completion
+
+ function hint(ts, cm, c) {
+ var doc = findDoc(ts, cm.getDoc());
+ var req = buildRequest(ts, doc, {type: "completions", types: true, docs: true});
+
+ ts.server.request(req, 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,
+ doc: completion.doc});
+ }
+
+ var obj = {from: from, to: to, list: completions};
+ var tooltip = null;
+ CodeMirror.on(obj, "close", function() { remove(tooltip); });
+ CodeMirror.on(obj, "select", function(cur, node) {
+ remove(tooltip);
+ if (cur.doc) {
+ tooltip = makeTooltip(node.parentNode.getBoundingClientRect().right + window.pageXOffset,
+ node.getBoundingClientRect().top + window.pageYOffset, cur.doc);
+ 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) {
+ var doc = findDoc(ts, cm.getDoc());
+ ts.server.request(buildRequest(ts, doc, "type"), function(error, data) {
+ if (error) return showError(ts, cm, error);
+ 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) {
+ if (ts.activeArgHints) { remove(ts.activeArgHints); ts.activeArgHints = null; }
+
+ 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);
+
+ var query = {type: "type", preferFunction: true, end: start};
+ ts.server.request(buildRequest(ts, findDoc(ts, cm.getDoc()), query), 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) {
+ if (ts.activeArgHints) { remove(ts.activeArgHints); ts.activeArgHints = null; }
+
+ 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)
+ 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) {
+ var req = {type: "rename", newName: newName}, doc = findDoc(ts, cm.getDoc());
+ ts.server.request(buildRequest(ts, doc, req, false), 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, allowFragments) {
+ var files = [], offsetLines = 0;
+ 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 = incLine(-offsetLines, query.start);
+ query.end = incLine(-offsetLines, query.end);
+ } else {
+ files.push({type: "full",
+ name: doc.name,
+ text: doc.doc.getValue()});
+ 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: cur.doc.getValue()});
+ cur.changed = null;
+ }
+ }
+
+ return {query: query, files: files};
+ }
+
+ function getFragmentAround(data, start, end) {
+ var doc = data.doc;
+ var minIndent = null, minLine = null, endLine, tabSize = doc.getOption("tabSize");
+ 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));
+ }
+})();
diff --git a/demo/tern.html b/demo/tern.html
new file mode 100644
index 0000000000..22464f8a60
--- /dev/null
+++ b/demo/tern.html
@@ -0,0 +1,95 @@
+
+
+
+
+ CodeMirror: Tern Demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ CodeMirror: Tern Demo
+
+
+
+
+
+
+
From 2677aba253ef5c0a1ed6332e961cef5712f334d3 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Thu, 20 Jun 2013 17:37:44 +0200
Subject: [PATCH 004/110] [xml-fold addon] Prevent infinite recursion from
weird lastIndexOf behavior
Issue #1625
---
addon/fold/xml-fold.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/addon/fold/xml-fold.js b/addon/fold/xml-fold.js
index b764bc0190..29d730eb71 100644
--- a/addon/fold/xml-fold.js
+++ b/addon/fold/xml-fold.js
@@ -43,7 +43,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 +65,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);
From f674dd148c27c882d3a85885caa9c27bb5638ee5 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Fri, 21 Jun 2013 09:53:32 +0200
Subject: [PATCH 005/110] Support insertAt option to addLineWidget
Issue #1627
---
doc/manual.html | 10 ++++++++--
lib/codemirror.js | 4 +++-
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/doc/manual.html b/doc/manual.html
index 655e64db4c..68692e2956 100644
--- a/doc/manual.html
+++ b/doc/manual.html
@@ -1253,7 +1253,7 @@ Widget, gutter, and decoration methods
widget again, simply use DOM methods (move it somewhere else, or
call removeChild on its parent).
- cm.addLineWidget (line: integer|LineHandle, node: Element, ?options: object) → LineWidget
+ cm.addLineWidget (line: integer|LineHandle, node: Element, ?options: object, ?pos: integer) → LineWidget
Adds a line widget, an element shown below a line, spanning
the whole of the editor's width, and moving the lines below it
downwards. line should be either an integer or a
@@ -1261,7 +1261,7 @@ Widget, gutter, and decoration methods
will be displayed below the given line. options,
when given, should be an object that configures the behavior of
the widget. The following options are supported (all default to
- false) →
+ false):
coverGutter : boolean
Whether the widget should cover the gutter.
@@ -1279,6 +1279,12 @@ Widget, gutter, and decoration methods
drag events occurring in this widget. Default is false—the
events will be left alone for the default browser handler,
or specific handlers on the widget, to capture.
+ insertAt : integer
+ By default, the widget is added below other widgets for
+ the line. This option can be used to place it at a different
+ position (zero for the top, N to put it after the Nth other
+ widget). Note that this only has effect once, when the
+ widget is created.
Note that the widget node will become a descendant of nodes with
CodeMirror-specific CSS classes, and those classes might in some
diff --git a/lib/codemirror.js b/lib/codemirror.js
index 5da1f17fea..ea3f8d9328 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -4078,7 +4078,9 @@ window.CodeMirror = (function() {
var widget = new LineWidget(cm, node, options);
if (widget.noHScroll) cm.display.alignWidgets = true;
changeLine(cm, handle, function(line) {
- (line.widgets || (line.widgets = [])).push(widget);
+ var widgets = line.widgets || (line.widgets = []);
+ if (options.insertAt == null) widgets.push(widget);
+ else widgets.splice(Math.max(widgets.length - 1, options.insertAt), 0, widget);
widget.line = line;
if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) {
var aboveVisible = heightAtLine(cm, line) < cm.display.scroller.scrollTop;
From 93a850a52b98ddf5a633b793df33edb0b3794ab1 Mon Sep 17 00:00:00 2001
From: James Campos
Date: Tue, 18 Jun 2013 17:56:48 -0700
Subject: [PATCH 006/110] [vim keymap] Fix bugs in line handling
Closes #1612
---
keymap/vim.js | 49 ++++++++++++++++++++++++-------------------------
test/vim_test.js | 25 ++++++++++++++++++-------
2 files changed, 42 insertions(+), 32 deletions(-)
diff --git a/keymap/vim.js b/keymap/vim.js
index e05be03176..8c942a3ff5 100644
--- a/keymap/vim.js
+++ b/keymap/vim.js
@@ -554,19 +554,16 @@
// Clear input state and get back to normal mode.
vim.inputState = new InputState();
if (vim.visualMode) {
- exitVisualMode(cm, vim);
+ exitVisualMode(cm);
}
return;
}
- if (vim.visualMode &&
- cursorEqual(cm.getCursor('head'), cm.getCursor('anchor'))) {
- // The selection was cleared. Exit visual mode.
- exitVisualMode(cm, vim);
- }
+ // Enter visual mode when the mouse selects text.
if (!vim.visualMode &&
!cursorEqual(cm.getCursor('head'), cm.getCursor('anchor'))) {
vim.visualMode = true;
vim.visualLine = false;
+ cm.on('mousedown', exitVisualMode);
}
if (key != '0' || (key == '0' && vim.inputState.getRepeat() === 0)) {
// Have to special case 0 since it's both a motion and a number.
@@ -1146,7 +1143,7 @@
operators[operator](cm, operatorArgs, vim, curStart,
curEnd, curOriginal);
if (vim.visualMode) {
- exitVisualMode(cm, vim);
+ exitVisualMode(cm);
}
if (operatorArgs.enterInsertMode) {
actions.enterInsertMode(cm, {}, vim);
@@ -1272,8 +1269,13 @@
}
var repeat = motionArgs.repeat+(motionArgs.repeatOffset||0);
var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat;
- if (line < cm.firstLine() || line > cm.lastLine() ) {
- return null;
+ var first = cm.firstLine();
+ var last = cm.lastLine();
+ // Vim cancels linewise motions that start on an edge and move beyond
+ // that edge. It does not cancel motions that do not start on an edge.
+ if ((line < first && cur.line == first) ||
+ (line > last && cur.line == last)) {
+ return;
}
if(motionArgs.toFirstChar){
endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line));
@@ -1478,18 +1480,12 @@
operatorArgs.registerName, 'change', cm.getRange(curStart, curEnd),
operatorArgs.linewise);
if (operatorArgs.linewise) {
- // Delete starting at the first nonwhitespace character of the first
- // line, instead of from the start of the first line. This way we get
- // an indent when we get into insert mode. This behavior isn't quite
- // correct because we should treat this as a completely new line, and
- // indent should be whatever codemirror thinks is the right indent.
- // But cm.indentLine doesn't seem work on empty lines.
- // TODO: Fix the above.
- curStart.ch =
- findFirstNonWhiteSpaceCharacter(cm.getLine(curStart.line));
- // Insert an additional newline so that insert mode can start there.
- // curEnd should be on the first character of the new line.
- cm.replaceRange('\n', curStart, curEnd);
+ // Push the next line back down, if there is a next line.
+ var replacement = curEnd.line > cm.lastLine() ? '' : '\n';
+ cm.replaceRange(replacement, curStart, curEnd);
+ cm.indentLine(curStart.line, 'smart');
+ // null ch so setCursor moves to end of line.
+ curStart.ch = null;
} else {
// Exclude trailing whitespace if the range is not all whitespace.
var text = cm.getRange(curStart, curEnd);
@@ -1651,6 +1647,7 @@
// equal to the repeat times the size of the previous visual
// operation.
if (!vim.visualMode) {
+ cm.on('mousedown', exitVisualMode);
vim.visualMode = true;
vim.visualLine = !!actionArgs.linewise;
if (vim.visualLine) {
@@ -1692,7 +1689,7 @@
// mode instead of exiting visual mode.
vim.visualLine = false;
} else {
- exitVisualMode(cm, vim);
+ exitVisualMode(cm);
}
}
updateMark(cm, vim, '<', cursorIsBefore(curStart, curEnd) ? curStart
@@ -1832,7 +1829,7 @@
cm.replaceRange(replaceWithStr, curStart, curEnd);
if(vim.visualMode){
cm.setCursor(curStart);
- exitVisualMode(cm,vim);
+ exitVisualMode(cm);
}else{
cm.setCursor(offsetCursor(curEnd, 0, -1));
}
@@ -1989,7 +1986,9 @@
return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1');
}
- function exitVisualMode(cm, vim) {
+ function exitVisualMode(cm) {
+ cm.off('mousedown', exitVisualMode);
+ var vim = cm.vimState;
vim.visualMode = false;
vim.visualLine = false;
var selectionStart = cm.getCursor('anchor');
@@ -2842,7 +2841,7 @@
processCommand: function(cm, input) {
var vim = getVimState(cm);
if (vim.visualMode) {
- exitVisualMode(cm, vim);
+ exitVisualMode(cm);
}
var inputStream = new CodeMirror.StringStream(input);
var params = {};
diff --git a/test/vim_test.js b/test/vim_test.js
index 47fc6cd14f..44639c72c8 100644
--- a/test/vim_test.js
+++ b/test/vim_test.js
@@ -296,8 +296,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));
@@ -818,12 +820,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 +850,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 +879,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
@@ -1471,6 +1478,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('/');
From 44cc08d6610783f9be8c98310b221ed684f721a9 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Fri, 21 Jun 2013 10:05:41 +0200
Subject: [PATCH 007/110] Fix bug in addLineWidget / insertAt implementation
(f674dd148c27c882d3a85885caa9c27bb5638ee5)
---
lib/codemirror.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lib/codemirror.js b/lib/codemirror.js
index ea3f8d9328..986f1640c8 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -4035,7 +4035,7 @@ window.CodeMirror = (function() {
// LINE WIDGETS
var LineWidget = CodeMirror.LineWidget = function(cm, node, options) {
- for (var opt in options) if (options.hasOwnProperty(opt))
+ if (options) for (var opt in options) if (options.hasOwnProperty(opt))
this[opt] = options[opt];
this.cm = cm;
this.node = node;
@@ -4079,8 +4079,8 @@ window.CodeMirror = (function() {
if (widget.noHScroll) cm.display.alignWidgets = true;
changeLine(cm, handle, function(line) {
var widgets = line.widgets || (line.widgets = []);
- if (options.insertAt == null) widgets.push(widget);
- else widgets.splice(Math.max(widgets.length - 1, options.insertAt), 0, widget);
+ if (widget.insertAt == null) widgets.push(widget);
+ else widgets.splice(Math.max(widgets.length - 1, widget.insertAt), 0, widget);
widget.line = line;
if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) {
var aboveVisible = heightAtLine(cm, line) < cm.display.scroller.scrollTop;
From 41deb2c055ba2efee8b5ee62b740df4582390827 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Mon, 24 Jun 2013 10:24:02 +0200
Subject: [PATCH 008/110] [matchtags addon] Add
Issue #1625
---
addon/edit/matchtags.js | 31 +++++++++++++++++++++++++++++++
addon/fold/xml-fold.js | 15 ++++++++++-----
demo/matchtags.html | 37 +++++++++++++++++++++++++++++++++++++
doc/manual.html | 11 +++++++++++
4 files changed, 89 insertions(+), 5 deletions(-)
create mode 100644 addon/edit/matchtags.js
create mode 100644 demo/matchtags.html
diff --git a/addon/edit/matchtags.js b/addon/edit/matchtags.js
new file mode 100644
index 0000000000..8cdc8b20c8
--- /dev/null
+++ b/addon/edit/matchtags.js
@@ -0,0 +1,31 @@
+(function() {
+ "use strict";
+
+ CodeMirror.defineOption("matchTags", false, function(cm, val, old) {
+ if (old && old != CodeMirror.Init)
+ cm.off("cursorActivity", doMatchTags);
+ if (val)
+ cm.on("cursorActivity", doMatchTags);
+ });
+
+ function doMatchTags(cm) {
+ cm.operation(function() {
+ if (cm.state.matchedTags) { cm.state.matchedTags(); cm.state.matchedTags = null; }
+
+ var cur = cm.getCursor();
+ var match = CodeMirror.findMatchingTag(cm, cur) || CodeMirror.findEnclosingTag(cm, cur);
+ if (!match) return;
+ var one = cm.markText(match.open.from, match.open.to, {className: "CodeMirror-matchingbracket"});
+ var two = cm.markText(match.close.from, match.close.to, {className: "CodeMirror-matchingbracket"});
+ cm.state.matchedTags = function() { one.clear(); two.clear(); };
+ });
+ }
+
+ CodeMirror.commands.toMatchingTag = function(cm) {
+ var found = CodeMirror.findMatchingTag(cm, cm.getCursor());
+ if (found) {
+ var other = found.at == "close" ? found.open : found.close;
+ cm.setSelection(other.to, other.from);
+ }
+ };
+})();
diff --git a/addon/fold/xml-fold.js b/addon/fold/xml-fold.js
index 29d730eb71..572feaa244 100644
--- a/addon/fold/xml-fold.js
+++ b/addon/fold/xml-fold.js
@@ -2,6 +2,7 @@
"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";
@@ -136,14 +137,18 @@
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;
+ 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]);
+ var open = findMatchingOpen(iter, start[2]);
+ return open && {open: open, close: here, at: "close"};
} else { // opening tag
- toTagEnd(iter);
- return findMatchingClose(iter, start[2]);
+ iter = new Iter(cm, to.line, to.ch);
+ var close = findMatchingClose(iter, start[2]);
+ return close && {open: here, close: close, at: "open"};
}
};
diff --git a/demo/matchtags.html b/demo/matchtags.html
new file mode 100644
index 0000000000..053c0ed667
--- /dev/null
+++ b/demo/matchtags.html
@@ -0,0 +1,37 @@
+
+
+
+
+ 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/doc/manual.html b/doc/manual.html
index 68692e2956..4284535f3f 100644
--- a/doc/manual.html
+++ b/doc/manual.html
@@ -1659,6 +1659,17 @@ Add-ons
them, should have the second character also moved to its own
line. Demo here .
+ edit/matchtags.js
+ Defines an option matchTags that, when enabled,
+ will cause the tags around the cursor to be highlighted (using
+ the CodeMirror-matchingbrackets class). Also
+ defines
+ a command toMatchingTag,
+ which you can bind a key to in order to jump to the tag mathing
+ the one under the cursor. Depends on
+ the addon/fold/xml-fold.js
+ addon. Demo here.
+
edit/trailingspace.js
Adds an option showTrailingSpace which, when
enabled, adds the CSS class cm-trailingspace to
From 493bd95ca309958ac3e1335a704893decc77a92d Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Mon, 24 Jun 2013 10:37:24 +0200
Subject: [PATCH 009/110] [real-world uses] Add Echoplexus and Shadertoy
---
doc/realworld.html | 2 ++
1 file changed, 2 insertions(+)
diff --git a/doc/realworld.html b/doc/realworld.html
index 98aaff989b..6e76980b4a 100644
--- a/doc/realworld.html
+++ b/doc/realworld.html
@@ -47,6 +47,7 @@
CSSDeck (CSS showcase)
Deck.js integration (slides with editors)
DbNinja (MySQL access interface)
+ Echoplexus (chat and collaborative coding)
Elm language examples
Eloquent JavaScript (book)
Emmet (fast XML editing)
@@ -89,6 +90,7 @@
Quivive File Manager
Rascal (tiny computer)
RealTime.io (Internet-of-Things infrastructure)
+ Shadertoy (shader sharing)
sketchPatch Livecodelab
Skulpt (in-browser Python environment)
Snippets.pro (code snippet sharing)
From a48327868c691c94841f635442330603a9ca4bf4 Mon Sep 17 00:00:00 2001
From: Drew Bratcher
Date: Sun, 23 Jun 2013 22:49:37 -0700
Subject: [PATCH 010/110] [jade mode] Add
---
mode/jade/index.html | 54 ++++++++++++++++++++++++++++++++
mode/jade/jade.js | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 142 insertions(+)
create mode 100644 mode/jade/index.html
create mode 100644 mode/jade/jade.js
diff --git a/mode/jade/index.html b/mode/jade/index.html
new file mode 100644
index 0000000000..0e68ba8ac4
--- /dev/null
+++ b/mode/jade/index.html
@@ -0,0 +1,54 @@
+
+
+
+
+ CodeMirror: Jade Templating Mode
+
+
+
+
+
+
+
+ CodeMirror: Jade Templating Mode
+
+doctype 5
+ html
+ head
+ title= "Jade Templating CodeMirror Mode Example"
+ link(rel='stylesheet', href='/css/bootstrap.min.css')
+ link(rel='stylesheet', href='/css/index.css')
+ script(type='text/javascript', src='/js/jquery-1.9.1.min.js')
+ script(type='text/javascript', src='/js/bootstrap.min.js')
+ body
+ div.header
+ h1 Welcome to this Example
+ div.spots
+ if locals.spots
+ each spot in spots
+ div.spot.well
+ div
+ if spot.logo
+ img.img-rounded.logo(src=spot.logo)
+ else
+ img.img-rounded.logo(src="img/placeholder.png")
+ h3
+ a(href=spot.hash) ##{spot.hash}
+ if spot.title
+ span.title #{spot.title}
+ if spot.desc
+ div #{spot.desc}
+ else
+ h3 There are no spots currently available.
+
+
+ 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..2c62632f1e
--- /dev/null
+++ b/mode/jade/jade.js
@@ -0,0 +1,88 @@
+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";
+ }
+ return null;
+ } else if(stream.eatSpace()) {
+ state.justMatchedKeyword = false;
+ if(stream.match(keyword_regex3) && stream.eatSpace()) {
+ state.justMatchedKeyword = true;
+ return "keyword";
+ }
+ return null;
+ } 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;
+ return null;
+ } else {
+ stream.next();
+ if(state.justMatchedKeyword){
+ return "property";
+ } else if(state.afterParen) {
+ return "property";
+ }
+ return null;
+ }
+ }
+ };
+});
+
+CodeMirror.defineMIME('text/x-jade', 'jade');
+
From 8942221a52622b750f1d1c52c857aef05c85effc Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Mon, 24 Jun 2013 10:41:22 +0200
Subject: [PATCH 011/110] [jade mode] Integrate
---
doc/compress.html | 1 +
doc/modes.html | 1 +
mode/meta.js | 1 +
3 files changed, 3 insertions(+)
diff --git a/doc/compress.html b/doc/compress.html
index fede6f43fa..8b7a11d670 100644
--- a/doc/compress.html
+++ b/doc/compress.html
@@ -93,6 +93,7 @@
htmlembedded.js
htmlmixed.js
http.js
+ jade.js
javascript.js
jinja2.js
less.js
diff --git a/doc/modes.html b/doc/modes.html
index af798aa774..69c7d5e9ad 100644
--- a/doc/modes.html
+++ b/doc/modes.html
@@ -45,6 +45,7 @@
HTML mixed-mode
HTTP
Java
+ Jade
JavaScript
Jinja2
LESS
diff --git a/mode/meta.js b/mode/meta.js
index e8cfc8fb63..cb1051e68f 100644
--- a/mode/meta.js
+++ b/mode/meta.js
@@ -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'},
From a4cbc645611017015594fe5e8f9738b27d1c571c Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Mon, 24 Jun 2013 12:45:42 +0200
Subject: [PATCH 012/110] [tern addon] Support custom type/completion tips
+ a few other fixes
---
addon/tern/tern.js | 83 +++++++++++++++++++++++++++++-------------------------
demo/tern.html | 15 +++++++++-
2 files changed, 58 insertions(+), 40 deletions(-)
diff --git a/addon/tern/tern.js b/addon/tern/tern.js
index 662736637c..5cff75782b 100644
--- a/addon/tern/tern.js
+++ b/addon/tern/tern.js
@@ -16,23 +16,16 @@
// 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.
(function() {
"use strict";
- CodeMirror.getURL = function(url, c) {
- var xhr = new XMLHttpRequest();
- xhr.open("get", url, true);
- xhr.send();
- xhr.onreadystatechange = function() {
- if (xhr.readyState != 4) return;
- if (xhr.status < 400) return c(null, xhr.responseText);
- var e = new Error(xhr.responseText || "No response");
- e.status = xhr.status;
- c(e);
- };
- };
-
CodeMirror.TernServer = function(options) {
var self = this;
this.options = options || {};
@@ -62,7 +55,7 @@
delDoc: function(name) {
var found = this.docs[name];
if (!found) return;
- CodeMirror.on(found.doc, "change", this.trackChange);
+ CodeMirror.off(found.doc, "change", this.trackChange);
delete this.docs[name];
this.server.delFile(name);
},
@@ -72,6 +65,8 @@
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); },
@@ -80,7 +75,11 @@
jumpBack: function(cm) { jumpBack(this, cm); },
- rename: function(cm) { rename(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;
@@ -139,10 +138,7 @@
// Completion
function hint(ts, cm, c) {
- var doc = findDoc(ts, cm.getDoc());
- var req = buildRequest(ts, doc, {type: "completions", types: true, docs: true});
-
- ts.server.request(req, function(error, data) {
+ ts.request(cm, {type: "completions", types: true, docs: true}, function(error, data) {
if (error) return showError(ts, cm, error);
var completions = [], after = "";
var from = data.start, to = data.end;
@@ -156,7 +152,7 @@
completions.push({text: completion.name + after,
displayText: completion.name,
className: className,
- doc: completion.doc});
+ data: completion});
}
var obj = {from: from, to: to, list: completions};
@@ -164,9 +160,10 @@
CodeMirror.on(obj, "close", function() { remove(tooltip); });
CodeMirror.on(obj, "select", function(cur, node) {
remove(tooltip);
- if (cur.doc) {
+ 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, cur.doc);
+ node.getBoundingClientRect().top + window.pageYOffset, content);
tooltip.className += " " + cls + "hint-doc";
}
});
@@ -187,15 +184,18 @@
// Type queries
function showType(ts, cm) {
- var doc = findDoc(ts, cm.getDoc());
- ts.server.request(buildRequest(ts, doc, "type"), function(error, data) {
+ ts.request(cm, "type", function(error, data) {
if (error) return showError(ts, cm, error);
- 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;
+ 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);
});
@@ -204,7 +204,7 @@
// Maintaining argument hints
function updateArgHints(ts, cm) {
- if (ts.activeArgHints) { remove(ts.activeArgHints); ts.activeArgHints = null; }
+ closeArgHints(ts);
if (cm.somethingSelected()) return;
var lex = cm.getTokenAt(cm.getCursor()).state.lexical;
@@ -220,8 +220,7 @@
if (cache && cache.doc == cm.getDoc() && cmpPos(start, cache.start) == 0)
return showArgHints(ts, cm, pos);
- var query = {type: "type", preferFunction: true, end: start};
- ts.server.request(buildRequest(ts, findDoc(ts, cm.getDoc()), query), function(error, data) {
+ 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,
@@ -235,7 +234,7 @@
}
function showArgHints(ts, cm, pos) {
- if (ts.activeArgHints) { remove(ts.activeArgHints); ts.activeArgHints = null; }
+ closeArgHints(ts);
var cache = ts.cachedArgHints, tp = cache.type;
var tip = elt("span", cache.guess ? cls + "fhint-guess" : null,
@@ -324,8 +323,10 @@
function moveTo(ts, curDoc, doc, start, end) {
doc.doc.setSelection(end, start);
- if (curDoc != doc && ts.options.switchToDoc)
+ if (curDoc != doc && ts.options.switchToDoc) {
+ closeArgHints(ts);
ts.options.switchToDoc(doc.name);
+ }
}
// The {line,ch} representation of positions makes this rather awkward.
@@ -371,8 +372,7 @@
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) {
- var req = {type: "rename", newName: newName}, doc = findDoc(ts, cm.getDoc());
- ts.server.request(buildRequest(ts, doc, req, false), function(error, data) {
+ ts.request(cm, {type: "rename", newName: newName, fullDocs: true}, function(error, data) {
if (error) return showError(ts, cm, error);
applyChanges(ts, data.changes);
});
@@ -400,8 +400,9 @@
// Generic request-building helper
- function buildRequest(ts, doc, query, allowFragments) {
- var files = [], offsetLines = 0;
+ 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) {
@@ -528,4 +529,8 @@
else
tempTooltip(cm, String(msg));
}
+
+ function closeArgHints(ts) {
+ if (ts.activeArgHints) { remove(ts.activeArgHints); ts.activeArgHints = null; }
+ }
})();
diff --git a/demo/tern.html b/demo/tern.html
index 22464f8a60..42c2b1a7bb 100644
--- a/demo/tern.html
+++ b/demo/tern.html
@@ -71,8 +71,21 @@
diff --git a/keymap/vim.js b/keymap/vim.js
index 8c942a3ff5..977642ea38 100644
--- a/keymap/vim.js
+++ b/keymap/vim.js
@@ -206,8 +206,7 @@
// Operators
{ keys: ['d'], type: 'operator', operator: 'delete' },
{ keys: ['y'], type: 'operator', operator: 'yank' },
- { keys: ['c'], type: 'operator', operator: 'change',
- operatorArgs: { enterInsertMode: true } },
+ { keys: ['c'], type: 'operator', operator: 'change' },
{ keys: ['>'], type: 'operator', operator: 'indent',
operatorArgs: { indentRight: true }},
{ keys: ['<'], type: 'operator', operator: 'indent',
@@ -231,7 +230,7 @@
motion: 'moveToEol', motionArgs: { inclusive: true },
operatorMotionArgs: { visualLine: true }},
{ keys: ['C'], type: 'operatorMotion',
- operator: 'change', operatorArgs: { enterInsertMode: true },
+ operator: 'change',
motion: 'moveToEol', motionArgs: { inclusive: true },
operatorMotionArgs: { visualLine: true }},
{ keys: ['~'], type: 'operatorMotion', operator: 'swapcase',
@@ -315,6 +314,27 @@
];
var Vim = function() {
+ CodeMirror.defineOption('vimMode', false, function(cm, val) {
+ if (val) {
+ cm.setOption('keyMap', 'vim');
+ cm.on('beforeSelectionChange', beforeSelectionChange);
+ maybeInitVimState(cm);
+ } else if (cm.state.vim) {
+ cm.setOption('keyMap', 'default');
+ cm.off('beforeSelectionChange', beforeSelectionChange);
+ cm.state.vim = null;
+ }
+ });
+ function beforeSelectionChange(cm, cur) {
+ var vim = cm.state.vim;
+ if (vim.insertMode || vim.exMode) return;
+
+ var head = cur.head;
+ if (head.ch && head.ch == cm.doc.getLine(head.line).length) {
+ head.ch--;
+ }
+ }
+
var numberRegex = /[\d]/;
var wordRegexp = [(/\w/), (/[^\w\s]/)], bigWordRegexp = [(/\S/)];
function makeKeyRange(start, size) {
@@ -450,28 +470,11 @@
};
};
- // Global Vim state. Call getVimGlobalState to get and initialize.
- var vimGlobalState;
- function getVimGlobalState() {
- if (!vimGlobalState) {
- vimGlobalState = {
- // The current search query.
- searchQuery: null,
- // Whether we are searching backwards.
- searchIsReversed: false,
- jumpList: createCircularJumpList(),
- macroModeState: createMacroState(),
- // Recording latest f, t, F or T motion command.
- lastChararacterSearch: {increment:0, forward:true, selectedCharacter:''},
- registerController: new RegisterController({})
- };
- }
- return vimGlobalState;
- }
- function getVimState(cm) {
- if (!cm.vimState) {
+
+ function maybeInitVimState(cm) {
+ if (!cm.state.vim) {
// Store instance state in the CodeMirror object.
- cm.vimState = {
+ cm.state.vim = {
inputState: new InputState(),
// Vim's input state that triggered the last edit, used to repeat
// motions and operators with '.'.
@@ -500,7 +503,21 @@
visualLine: false
};
}
- return cm.vimState;
+ return cm.state.vim;
+ }
+ var vimGlobalState;
+ function resetVimGlobalState() {
+ vimGlobalState = {
+ // The current search query.
+ searchQuery: null,
+ // Whether we are searching backwards.
+ searchIsReversed: false,
+ jumpList: createCircularJumpList(),
+ macroModeState: createMacroState(),
+ // Recording latest f, t, F or T motion command.
+ lastChararacterSearch: {increment:0, forward:true, selectedCharacter:''},
+ registerController: new RegisterController({})
+ };
}
var vimApi= {
@@ -510,16 +527,19 @@
// Testing hook, though it might be useful to expose the register
// controller anyways.
getRegisterController: function() {
- return getVimGlobalState().registerController;
+ return vimGlobalState.registerController;
},
// Testing hook.
- clearVimGlobalState_: function() {
- vimGlobalState = null;
- },
+ resetVimGlobalState_: resetVimGlobalState,
+
// Testing hook.
getVimGlobalState_: function() {
return vimGlobalState;
},
+
+ // Testing hook.
+ maybeInitVimState_: maybeInitVimState,
+
InsertModeKey: InsertModeKey,
map: function(lhs, rhs) {
// Add user defined key bindings.
@@ -532,17 +552,12 @@
exCommands[name]=func;
exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'};
},
- // Initializes vim state variable on the CodeMirror object. Should only be
- // called lazily by handleKey or for testing.
- maybeInitState: function(cm) {
- getVimState(cm);
- },
// This is the outermost function called by CodeMirror, after keys have
// been mapped to their Vim equivalents.
handleKey: function(cm, key) {
var command;
- var vim = getVimState(cm);
- var macroModeState = getVimGlobalState().macroModeState;
+ var vim = maybeInitVimState(cm);
+ var macroModeState = vimGlobalState.macroModeState;
if (macroModeState.enteredMacroMode) {
if (key == 'q') {
actions.exitMacroRecordMode();
@@ -967,7 +982,7 @@
// cachedCursor is used to save the old position of the cursor
// when * or # causes vim to seek for the nearest word and shift
// the cursor before entering the motion.
- getVimGlobalState().jumpList.cachedCursor = cm.getCursor();
+ vimGlobalState.jumpList.cachedCursor = cm.getCursor();
cm.setCursor(word.start);
handleQuery(query, true /** ignoreCase */, false /** smartCase */);
@@ -1049,7 +1064,7 @@
return;
}
if (motionArgs.toJumplist) {
- var jumpList = getVimGlobalState().jumpList;
+ var jumpList = vimGlobalState.jumpList;
// if the current motion is # or *, use cachedCursor
var cachedCursor = jumpList.cachedCursor;
if (cachedCursor) {
@@ -1145,13 +1160,10 @@
if (vim.visualMode) {
exitVisualMode(cm);
}
- if (operatorArgs.enterInsertMode) {
- actions.enterInsertMode(cm, {}, vim);
- }
}
},
recordLastEdit: function(vim, inputState, actionCommand) {
- var macroModeState = getVimGlobalState().macroModeState;
+ var macroModeState = vimGlobalState.macroModeState;
if (macroModeState.inReplay) { return; }
vim.lastEditInputState = inputState;
vim.lastEditActionCommand = actionCommand;
@@ -1458,7 +1470,7 @@
return [start, end];
},
repeatLastCharacterSearch: function(cm, motionArgs) {
- var lastSearch = getVimGlobalState().lastChararacterSearch;
+ var lastSearch = vimGlobalState.lastChararacterSearch;
var repeat = motionArgs.repeat;
var forward = motionArgs.forward === lastSearch.forward;
var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1);
@@ -1476,7 +1488,7 @@
var operators = {
change: function(cm, operatorArgs, _vim, curStart, curEnd) {
- getVimGlobalState().registerController.pushText(
+ vimGlobalState.registerController.pushText(
operatorArgs.registerName, 'change', cm.getRange(curStart, curEnd),
operatorArgs.linewise);
if (operatorArgs.linewise) {
@@ -1497,6 +1509,7 @@
}
cm.replaceRange('', curStart, curEnd);
}
+ actions.enterInsertMode(cm, {}, cm.state.vim);
cm.setCursor(curStart);
},
// delete is a javascript keyword.
@@ -1508,7 +1521,7 @@
curStart.line--;
curStart.ch = lineLength(cm, curStart.line);
}
- getVimGlobalState().registerController.pushText(
+ vimGlobalState.registerController.pushText(
operatorArgs.registerName, 'delete', cm.getRange(curStart, curEnd),
operatorArgs.linewise);
cm.replaceRange('', curStart, curEnd);
@@ -1550,7 +1563,7 @@
cm.setCursor(curOriginal);
},
yank: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) {
- getVimGlobalState().registerController.pushText(
+ vimGlobalState.registerController.pushText(
operatorArgs.registerName, 'yank',
cm.getRange(curStart, curEnd), operatorArgs.linewise);
cm.setCursor(curOriginal);
@@ -1564,7 +1577,7 @@
}
var repeat = actionArgs.repeat;
var forward = actionArgs.forward;
- var jumpList = getVimGlobalState().jumpList;
+ var jumpList = vimGlobalState.jumpList;
var mark = jumpList.move(cm, forward ? repeat : -repeat);
var markPos = mark ? mark.find() : undefined;
@@ -1590,7 +1603,7 @@
replayMacro: function(cm, actionArgs) {
var registerName = actionArgs.selectedCharacter;
var repeat = actionArgs.repeat;
- var macroModeState = getVimGlobalState().macroModeState;
+ var macroModeState = vimGlobalState.macroModeState;
if (registerName == '@') {
registerName = macroModeState.latestRegister;
}
@@ -1600,13 +1613,13 @@
}
},
exitMacroRecordMode: function() {
- var macroModeState = getVimGlobalState().macroModeState;
+ var macroModeState = vimGlobalState.macroModeState;
macroModeState.toggle();
parseKeyBufferToRegister(macroModeState.latestRegister,
macroModeState.macroKeyBuffer);
},
enterMacroRecordMode: function(cm, actionArgs) {
- var macroModeState = getVimGlobalState().macroModeState;
+ var macroModeState = vimGlobalState.macroModeState;
var registerName = actionArgs.selectedCharacter;
macroModeState.toggle(cm, registerName);
emptyMacroKeyBuffer(macroModeState);
@@ -1632,7 +1645,7 @@
} else {
cm.setOption('keyMap', 'vim-insert');
}
- if (!getVimGlobalState().macroModeState.inReplay) {
+ if (!vimGlobalState.macroModeState.inReplay) {
// Only record if not replaying.
cm.on('change', onChange);
cm.on('cursorActivity', onCursorActivity);
@@ -1725,6 +1738,7 @@
});
},
newLineAndEnterInsertMode: function(cm, actionArgs, vim) {
+ vim.insertMode = true;
var insertAt = cm.getCursor();
if (insertAt.line === cm.firstLine() && !actionArgs.after) {
// Special case for inserting newline before start of document.
@@ -1743,7 +1757,7 @@
},
paste: function(cm, actionArgs) {
var cur = cm.getCursor();
- var register = getVimGlobalState().registerController.getRegister(
+ var register = vimGlobalState.registerController.getRegister(
actionArgs.registerName);
if (!register.text) {
return;
@@ -1988,7 +2002,7 @@
function exitVisualMode(cm) {
cm.off('mousedown', exitVisualMode);
- var vim = cm.vimState;
+ var vim = cm.state.vim;
vim.visualMode = false;
vim.visualLine = false;
var selectionStart = cm.getCursor('anchor');
@@ -2112,12 +2126,11 @@
function recordJumpPosition(cm, oldCur, newCur) {
if(!cursorEqual(oldCur, newCur)) {
- getVimGlobalState().jumpList.add(cm, oldCur, newCur);
+ vimGlobalState.jumpList.add(cm, oldCur, newCur);
}
}
function recordLastCharacterSearch(increment, args) {
- var vimGlobalState = getVimGlobalState();
vimGlobalState.lastChararacterSearch.increment = increment;
vimGlobalState.lastChararacterSearch.forward = args.forward;
vimGlobalState.lastChararacterSearch.selectedCharacter = args.selectedCharacter;
@@ -2571,10 +2584,10 @@
function SearchState() {}
SearchState.prototype = {
getQuery: function() {
- return getVimGlobalState().query;
+ return vimGlobalState.query;
},
setQuery: function(query) {
- getVimGlobalState().query = query;
+ vimGlobalState.query = query;
},
getOverlay: function() {
return this.searchOverlay;
@@ -2583,14 +2596,14 @@
this.searchOverlay = overlay;
},
isReversed: function() {
- return getVimGlobalState().isReversed;
+ return vimGlobalState.isReversed;
},
setReversed: function(reversed) {
- getVimGlobalState().isReversed = reversed;
+ vimGlobalState.isReversed = reversed;
}
};
function getSearchState(cm) {
- var vim = getVimState(cm);
+ var vim = cm.state.vim;
return vim.searchState_ || (vim.searchState_ = new SearchState());
}
function dialog(cm, template, shortText, onClose, options) {
@@ -2839,7 +2852,7 @@
};
Vim.ExCommandDispatcher.prototype = {
processCommand: function(cm, input) {
- var vim = getVimState(cm);
+ var vim = cm.state.vim;
if (vim.visualMode) {
exitVisualMode(cm);
}
@@ -2920,7 +2933,7 @@
case '$':
return cm.lastLine();
case '\'':
- var mark = getVimState(cm).marks[inputStream.next()];
+ var mark = cm.state.vim.marks[inputStream.next()];
if (mark && mark.find()) {
return mark.find().line;
}
@@ -3030,7 +3043,7 @@
exCommandDispatcher.map(mapArgs[0], mapArgs[1], cm);
},
move: function(cm, params) {
- commandDispatcher.processCommand(cm, getVimState(cm), {
+ commandDispatcher.processCommand(cm, cm.state.vim, {
type: 'motion',
motion: 'moveToLineOrEdgeOfDocument',
motionArgs: { forward: false, explicitRepeat: true,
@@ -3185,7 +3198,7 @@
return;
}
- var state = getVimState(cm);
+ var state = cm.state.vim;
var stream = new CodeMirror.StringStream(params.argString.trim());
while (!stream.eol()) {
stream.eatSpace();
@@ -3256,6 +3269,7 @@
function doReplace(cm, confirm, lineStart, lineEnd, searchCursor, query,
replaceWith) {
// Set up all the functions.
+ cm.state.vim.exMode = true;
var done = false;
var lastPos = searchCursor.from();
function replaceAll() {
@@ -3290,7 +3304,8 @@
cm.focus();
if (lastPos) {
cm.setCursor(lastPos);
- var vim = getVimState(cm);
+ var vim = cm.state.vim;
+ vim.exMode = false;
vim.lastHPos = vim.lastHSPos = lastPos.ch;
}
}
@@ -3402,9 +3417,8 @@
CodeMirror.keyMap.vim = buildVimKeyMap();
function exitInsertMode(cm) {
- var vim = getVimState(cm);
- vim.insertMode = false;
- var inReplay = getVimGlobalState().macroModeState.inReplay;
+ var vim = cm.state.vim;
+ var inReplay = vimGlobalState.macroModeState.inReplay;
if (!inReplay) {
cm.off('change', onChange);
cm.off('cursorActivity', onCursorActivity);
@@ -3418,6 +3432,7 @@
}
delete vim.insertModeRepeat;
cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1, true);
+ vim.insertMode = false;
cm.setOption('keyMap', 'vim');
cm.toggleOverwrite(false); // exit replace mode if we were in it.
}
@@ -3445,7 +3460,7 @@
function parseRegisterToKeyBuffer(macroModeState, registerName) {
var match, key;
- var register = getVimGlobalState().registerController.getRegister(registerName);
+ var register = vimGlobalState.registerController.getRegister(registerName);
var text = register.toString();
var macroKeyBuffer = macroModeState.macroKeyBuffer;
emptyMacroKeyBuffer(macroModeState);
@@ -3461,7 +3476,7 @@
function parseKeyBufferToRegister(registerName, keyBuffer) {
var text = keyBuffer.join('');
- getVimGlobalState().registerController.setRegisterText(registerName, text);
+ vimGlobalState.registerController.setRegisterText(registerName, text);
}
function emptyMacroKeyBuffer(macroModeState) {
@@ -3489,7 +3504,7 @@
* Should only be active in insert mode.
*/
function onChange(_cm, changeObj) {
- var macroModeState = getVimGlobalState().macroModeState;
+ var macroModeState = vimGlobalState.macroModeState;
var lastChange = macroModeState.lastInsertModeChanges;
while (changeObj) {
lastChange.expectCursorActivityForChange = true;
@@ -3509,7 +3524,7 @@
* - Should only be active in insert mode.
*/
function onCursorActivity() {
- var macroModeState = getVimGlobalState().macroModeState;
+ var macroModeState = vimGlobalState.macroModeState;
var lastChange = macroModeState.lastInsertModeChanges;
if (lastChange.expectCursorActivityForChange) {
lastChange.expectCursorActivityForChange = false;
@@ -3530,7 +3545,7 @@
* - For recording deletes in insert mode.
*/
function onKeyEventTargetKeyDown(e) {
- var macroModeState = getVimGlobalState().macroModeState;
+ var macroModeState = vimGlobalState.macroModeState;
var lastChange = macroModeState.lastInsertModeChanges;
var keyName = CodeMirror.keyName(e);
function onKeyFound() {
@@ -3552,7 +3567,7 @@
* corresponding enterInsertMode call was made with a count.
*/
function repeatLastEdit(cm, vim, repeat, repeatForInsert) {
- var macroModeState = getVimGlobalState().macroModeState;
+ var macroModeState = vimGlobalState.macroModeState;
macroModeState.inReplay = true;
var isAction = !!vim.lastEditActionCommand;
var cachedInputState = vim.inputState;
@@ -3620,6 +3635,7 @@
}
}
+ resetVimGlobalState();
return vimApi;
};
// Initialize Vim and make it available as an API.
diff --git a/lib/codemirror.js b/lib/codemirror.js
index f3f0efb640..e4f5e19349 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -4976,7 +4976,8 @@ window.CodeMirror = (function() {
}
function historyChangeFromChange(doc, change) {
- var histChange = {from: change.from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)};
+ var from = { line: change.from.line, ch: change.from.ch };
+ var histChange = {from: from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)};
attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);
linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true);
return histChange;
diff --git a/test/vim_test.js b/test/vim_test.js
index 44639c72c8..0e88243215 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);
@@ -501,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);
@@ -594,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);
@@ -665,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);
@@ -1001,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);
@@ -1018,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) {
From ca6db50f5874b003f5d4daae23391c10dfa7d957 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Tue, 2 Jul 2013 13:58:49 +0200
Subject: [PATCH 032/110] Fix bug in findWordAt
Which could result in a selection at a negative character offset
when double-clicking.
Issue #1638
---
lib/codemirror.js | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/lib/codemirror.js b/lib/codemirror.js
index e4f5e19349..1aa350a2df 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -1064,7 +1064,6 @@ window.CodeMirror = (function() {
}
function finishRect(rect) {
rect.bottom = vranges[rect.top+1];
- if (isNaN(rect.bottom)) debugger;
rect.top = vranges[rect.top];
}
@@ -2757,7 +2756,7 @@ window.CodeMirror = (function() {
function findWordAt(line, pos) {
var start = pos.ch, end = pos.ch;
if (line) {
- if (pos.xRel < 0 || end == line.length) --start; else ++end;
+ if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end;
var startChar = line.charAt(start);
var check = isWordChar(startChar) ? isWordChar
: /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);}
From e0e2181cf0033ebb0f24c7909b0ce546d8c62edd Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Tue, 2 Jul 2013 14:04:20 +0200
Subject: [PATCH 033/110] [real-world uses] Add Fiddle Salad
---
doc/realworld.html | 1 +
1 file changed, 1 insertion(+)
diff --git a/doc/realworld.html b/doc/realworld.html
index 6e76980b4a..2150abe401 100644
--- a/doc/realworld.html
+++ b/doc/realworld.html
@@ -54,6 +54,7 @@
Fastfig (online computation/math tool)
Farabi (modern Perl IDE)
FathomJS integration (slides with editors, again)
+ Fiddle Salad (web development environment)
Firepad (collaborative text editor)
Go language tour
GitHub's Android app
From 836932ff202d860d86fa40c1788e238096394d15 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Tue, 2 Jul 2013 14:25:35 +0200
Subject: [PATCH 034/110] [compression helper] Sort and complete addon scripts
---
doc/compress.html | 45 +++++++++++++++++++++++++--------------------
1 file changed, 25 insertions(+), 20 deletions(-)
diff --git a/doc/compress.html b/doc/compress.html
index 8b7a11d670..c7eec47d5e 100644
--- a/doc/compress.html
+++ b/doc/compress.html
@@ -140,38 +140,43 @@
z80.js
- dialog.js
+ active-line.js
+ brace-fold.js
+ closebrackets.js
closetag.js
+ colorize.js
+ comment.js
continuecomment.js
continuelist.js
- matchbrackets.js
- closebrackets.js
+ dialog.js
foldcode.js
- xml-fold.js
- brace-fold.js
+ html-hint.js
indent-fold.js
- show-hint.js
javascript-hint.js
- xml-hint.js
- html-hint.js
- pig-hint.js
- python-hint.js
+ javascript-lint.js
+ json-lint.js
+ lint.js
loadmode.js
- overlay.js
+ mark-selection.js
+ match-highlighter.js
+ matchbrackets.js
+ matchtags.js
+ merge.js
multiplex.js
- colorize.js
+ overlay.js
+ pig-hint.js
+ placeholder.js
+ python-hint.js
runmode.js
- runmode-standalone.js
runmode.node.js
+ runmode-standalone.js
search.js
searchcursor.js
- match-highlighter.js
- mark-selection.js
- active-line.js
- lint.js
- javascript-lint.js
- json-lint.js
- merge.js
+ show-hint.js
+ trailingspace.js
+ tern.js
+ xml-fold.js
+ xml-hint.js
emacs.js
From 4d85080d2a031fecf7fe21f5fa3fe8e6376db4ea Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Tue, 2 Jul 2013 15:32:12 +0200
Subject: [PATCH 035/110] [merge addon] Make sure synchronized scrolling
scrolls other pane to top/bot
---
addon/merge/merge.js | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/addon/merge/merge.js b/addon/merge/merge.js
index 0e813f3e25..66721e3584 100644
--- a/addon/merge/merge.js
+++ b/addon/merge/merge.js
@@ -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(null, 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) {
From f3bc32b662b5b04d83e63dab71c523ee5edbe405 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Tue, 2 Jul 2013 15:33:59 +0200
Subject: [PATCH 036/110] [merge addon] Synchronize vertical scrolling
---
addon/merge/merge.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/addon/merge/merge.js b/addon/merge/merge.js
index 66721e3584..cae9ea7167 100644
--- a/addon/merge/merge.js
+++ b/addon/merge/merge.js
@@ -107,7 +107,7 @@
targetPos = targetPos * mix + (otherInfo.height - otherInfo.clientHeight - botDist) * (1 - mix);
}
- other.scrollTo(null, targetPos);
+ other.scrollTo(sInfo.left, targetPos);
other.state.scrollSetAt = now;
other.state.scrollSetBy = dv;
return true;
From b4cca0242a4570dae04cfa38024f29f196c896e8 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Tue, 2 Jul 2013 15:35:51 +0200
Subject: [PATCH 037/110] [merge addon] Use CodeMirror-merge-* CSS class names
Since the addon is called merge, not diff.
---
addon/merge/merge.css | 56 +++++++++++++++++++++++++--------------------------
addon/merge/merge.js | 44 ++++++++++++++++++++--------------------
2 files changed, 50 insertions(+), 50 deletions(-)
diff --git a/addon/merge/merge.css b/addon/merge/merge.css
index 3bd6bbb9a5..63237fc8e9 100644
--- a/addon/merge/merge.css
+++ b/addon/merge/merge.css
@@ -1,30 +1,30 @@
-.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 {
+.CodeMirror-merge-pane {
display: inline-block;
white-space: normal;
vertical-align: top;
}
-.CodeMirror-diff-pane-rightmost {
+.CodeMirror-merge-pane-rightmost {
position: absolute;
right: 0px;
z-index: 1;
}
-.CodeMirror-diff-gap {
+.CodeMirror-merge-gap {
z-index: 2;
display: inline-block;
height: 100%;
@@ -37,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;
@@ -49,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 cae9ea7167..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 = {
@@ -239,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";
@@ -264,25 +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-diff-pane-rightmost";
+ (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);
@@ -300,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);
@@ -315,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 = {
From bc85bdb354799ab2e1c8dd4a1dd2600db826e42c Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Wed, 3 Jul 2013 08:48:19 +0200
Subject: [PATCH 038/110] [matchtags addon] Only highlight matching tag, try to
be more efficient
---
addon/edit/matchtags.js | 36 ++++++++++++++++++++++++++----------
addon/fold/xml-fold.js | 20 +++++++++++---------
demo/matchtags.html | 1 +
3 files changed, 38 insertions(+), 19 deletions(-)
diff --git a/addon/edit/matchtags.js b/addon/edit/matchtags.js
index 8cdc8b20c8..66e57c8e9e 100644
--- a/addon/edit/matchtags.js
+++ b/addon/edit/matchtags.js
@@ -2,25 +2,41 @@
"use strict";
CodeMirror.defineOption("matchTags", false, function(cm, val, old) {
- if (old && old != CodeMirror.Init)
+ if (old && old != CodeMirror.Init) {
cm.off("cursorActivity", doMatchTags);
- if (val)
+ 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.operation(function() {
- if (cm.state.matchedTags) { cm.state.matchedTags(); cm.state.matchedTags = null; }
-
- var cur = cm.getCursor();
- var match = CodeMirror.findMatchingTag(cm, cur) || CodeMirror.findEnclosingTag(cm, cur);
- if (!match) return;
- var one = cm.markText(match.open.from, match.open.to, {className: "CodeMirror-matchingbracket"});
- var two = cm.markText(match.close.from, match.close.to, {className: "CodeMirror-matchingbracket"});
- cm.state.matchedTags = function() { one.clear(); two.clear(); };
+ 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 (cm.state.failedTagMatch = !match) return;
+ var other = match.at == "close" ? match.open : match.close;
+ cm.state.matchedTag = cm.markText(other.from, other.to, {className: "CodeMirror-matchingtag"});
});
}
+ function maybeUpdateMatch(cm) {
+ if (cm.state.failedTagMatch) doMatchTags(cm);
+ }
+
CodeMirror.commands.toMatchingTag = function(cm) {
var found = CodeMirror.findMatchingTag(cm, cm.getCursor());
if (found) {
diff --git a/addon/fold/xml-fold.js b/addon/fold/xml-fold.js
index 572feaa244..bf8ca9c2ac 100644
--- a/addon/fold/xml-fold.js
+++ b/addon/fold/xml-fold.js
@@ -8,9 +8,11 @@
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) {
@@ -19,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;
@@ -135,8 +137,8 @@
}
};
- CodeMirror.findMatchingTag = function(cm, pos) {
- var iter = new Iter(cm, pos.line, pos.ch);
+ CodeMirror.findMatchingTag = function(cm, pos, range) {
+ var iter = new Iter(cm, pos.line, pos.ch, range);
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;
@@ -146,18 +148,18 @@
var open = findMatchingOpen(iter, start[2]);
return open && {open: open, close: here, at: "close"};
} else { // opening tag
- iter = new Iter(cm, to.line, to.ch);
+ iter = new Iter(cm, to.line, to.ch, range);
var close = findMatchingClose(iter, start[2]);
return close && {open: here, close: close, 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/demo/matchtags.html b/demo/matchtags.html
index 053c0ed667..8f4217debe 100644
--- a/demo/matchtags.html
+++ b/demo/matchtags.html
@@ -12,6 +12,7 @@
From c969e83279331d1535096fb8925816630667760a Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Wed, 3 Jul 2013 09:43:27 +0200
Subject: [PATCH 039/110] Stabilize scroll position when removing widgets
(When they are above visible range, scroll up to correspond for
height reduction.)
Closes #1645
---
lib/codemirror.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/lib/codemirror.js b/lib/codemirror.js
index 1aa350a2df..c702260103 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -4057,7 +4057,9 @@ window.CodeMirror = (function() {
if (no == null || !ws) return;
for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);
if (!ws.length) this.line.widgets = null;
+ var aboveVisible = heightAtLine(this.cm, this.line) < this.cm.doc.scrollTop;
updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this)));
+ if (aboveVisible) addToScrollPos(this.cm, 0, -this.height);
regChange(this.cm, no, no + 1);
});
LineWidget.prototype.changed = widgetOperation(function() {
@@ -4086,7 +4088,7 @@ window.CodeMirror = (function() {
else widgets.splice(Math.max(widgets.length - 1, widget.insertAt), 0, widget);
widget.line = line;
if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) {
- var aboveVisible = heightAtLine(cm, line) < cm.display.scroller.scrollTop;
+ var aboveVisible = heightAtLine(cm, line) < cm.doc.scrollTop;
updateLineHeight(line, line.height + widgetHeight(widget));
if (aboveVisible) addToScrollPos(cm, 0, widget.height);
}
From a7dba9653f39b100879b82b6e6e5d6d542474488 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Wed, 3 Jul 2013 10:21:33 +0200
Subject: [PATCH 040/110] Fix problem in widget-preserving line redraws
Issue #1645
---
lib/codemirror.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/codemirror.js b/lib/codemirror.js
index c702260103..4721f67e2f 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -668,10 +668,10 @@ window.CodeMirror = (function() {
if (!/\bCodeMirror-linewidget\b/.test(n.className)) {
reuse.removeChild(n);
} else {
- for (var i = 0, first = true; i < line.widgets.length; ++i) {
+ for (var i = 0; i < line.widgets.length; ++i) {
var widget = line.widgets[i];
- if (!widget.above) { insertBefore = n; first = false; }
if (widget.node == n.firstChild) {
+ if (!widget.above && !insertBefore) insertBefore = n;
positionLineWidget(widget, n, reuse, dims);
++widgetsSeen;
break;
From ac14de0d671c0219c4c10c28ed2f63aa9b4e3993 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Wed, 3 Jul 2013 10:41:28 +0200
Subject: [PATCH 041/110] Fix bug in character measurement
Issue #1617
---
lib/codemirror.js | 5 +++--
test/test.js | 7 +++++--
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/lib/codemirror.js b/lib/codemirror.js
index 4721f67e2f..180594f198 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -964,8 +964,9 @@ window.CodeMirror = (function() {
if (r) break;
if (dir < 0 && pos == 0) dir = 1;
}
- if ((pos > ch || bias == "left") && r.leftSide) r = r.leftSide;
- else if ((pos < ch || bias == "right") && r.rightSide) r = r.rightSide;
+ bias = pos > ch ? "left" : pos < ch ? "right" : bias;
+ if (bias == "left" && r.leftSide) r = r.leftSide;
+ else if (bias == "right" && r.rightSide) r = r.rightSide;
return {left: pos < ch ? r.right : r.left,
right: pos > ch ? r.left : r.right,
top: r.top,
diff --git a/test/test.js b/test/test.js
index cc4b57a4b6..641818484d 100644
--- a/test/test.js
+++ b/test/test.js
@@ -501,7 +501,8 @@ testCM("bookmarkInsertLeft", function(cm) {
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));
+ 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("→")});
@@ -512,7 +513,9 @@ testCM("bookmarkCursor", function(cm) {
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");
-}, {value: "foo\nbar\n\n\nx"});
+ 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);
From ba3a62bde5305d4e02ec8555564aa25cb5743862 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Wed, 3 Jul 2013 11:37:13 +0200
Subject: [PATCH 042/110] [anyword-hint addon] Add
---
addon/hint/anyword-hint.js | 34 +++++++++++++++++++++++
demo/anywordhint.html | 69 ++++++++++++++++++++++++++++++++++++++++++++++
doc/manual.html | 9 ++++++
3 files changed, 112 insertions(+)
create mode 100644 addon/hint/anyword-hint.js
create mode 100644 demo/anywordhint.html
diff --git a/addon/hint/anyword-hint.js b/addon/hint/anyword-hint.js
new file mode 100644
index 0000000000..b3b2c1e1c0
--- /dev/null
+++ b/addon/hint/anyword-hint.js
@@ -0,0 +1,34 @@
+(function() {
+ "use strict";
+
+ var WORD = /[\w$]+/, RANGE = 500;
+
+ CodeMirror.anyWordHint = 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/demo/anywordhint.html b/demo/anywordhint.html
new file mode 100644
index 0000000000..820df39e90
--- /dev/null
+++ b/demo/anywordhint.html
@@ -0,0 +1,69 @@
+
+
+
+
+ CodeMirror: Any Word Completion Demo
+
+
+
+
+
+
+
+
+
+ CodeMirror: Any Word Completion Demo
+
+
+(function() {
+ "use strict";
+
+ var WORD = /[\w$]+/g, RANGE = 500;
+
+ CodeMirror.anyWordHint = 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;
+ word.lastIndex = 0;
+ while (m = word.exec(text)) {
+ 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)};
+ };
+})();
+
+
+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/doc/manual.html b/doc/manual.html
index 4284535f3f..74b2aab783 100644
--- a/doc/manual.html
+++ b/doc/manual.html
@@ -1915,6 +1915,15 @@ Add-ons
A very simple hinting function for Python code.
Defines CodeMirror.pythonHint.
+ hint/anyword-hint.js
+ A very simple hinting function
+ (CodeMirror.anyWordHint) that simply looks for
+ words in the nearby code and completes to those. Takes two
+ optional options, word, a regular expression that
+ matches words (sequences of one or more character),
+ and range, which defines how many lines the addon
+ should scan when completing (defaults to 500).
+
match-highlighter.js
Adds a highlightSelectionMatches option that
can be enabled to highlight all instances of a currently
From 022bc2862faa29970193fa10f8bbb3f469600e26 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Wed, 3 Jul 2013 13:45:22 +0200
Subject: [PATCH 043/110] Add registerHelper/getHelper mechanism
Move hinting functions into it.
---
addon/hint/anyword-hint.js | 4 ++--
addon/hint/html-hint.js | 8 ++++---
addon/hint/javascript-hint.js | 10 +++++---
addon/hint/pig-hint.js | 6 +++--
addon/hint/python-hint.js | 6 +++--
addon/hint/show-hint.js | 2 ++
addon/hint/xml-hint.js | 7 ++++--
demo/anywordhint.html | 6 ++---
demo/complete.html | 2 +-
demo/html5complete.html | 2 +-
demo/xmlcomplete.html | 4 ++--
doc/manual.html | 55 ++++++++++++++++++++++++++++++++-----------
lib/codemirror.js | 18 +++++++++++++-
mode/xml/xml.js | 3 ++-
14 files changed, 96 insertions(+), 37 deletions(-)
diff --git a/addon/hint/anyword-hint.js b/addon/hint/anyword-hint.js
index b3b2c1e1c0..36ff618e09 100644
--- a/addon/hint/anyword-hint.js
+++ b/addon/hint/anyword-hint.js
@@ -3,7 +3,7 @@
var WORD = /[\w$]+/, RANGE = 500;
- CodeMirror.anyWordHint = function(editor, options) {
+ 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);
@@ -30,5 +30,5 @@
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..5a34f552f7 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();
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/demo/anywordhint.html b/demo/anywordhint.html
index 820df39e90..5bb0a62a10 100644
--- a/demo/anywordhint.html
+++ b/demo/anywordhint.html
@@ -20,7 +20,7 @@
var WORD = /[\w$]+/g, RANGE = 500;
- CodeMirror.anyWordHint = function(editor, options) {
+ 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);
@@ -46,7 +46,7 @@
scan(-1);
scan(1);
return {list: list, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)};
- };
+ });
})();
@@ -58,7 +58,7 @@
diff --git a/doc/manual.html b/doc/manual.html
index c4e2ece461..c7466ce243 100644
--- a/doc/manual.html
+++ b/doc/manual.html
@@ -1745,24 +1745,28 @@ Addons
fails, line-comments it.
fold/foldcode.js
- Helps with code folding. Add a foldCode method
+ Helps with code folding. Adds a foldCode method
to editor instances, which will try to do a code fold starting
at the given line, or unfold the fold that is already present.
The method takes as first argument the position that should be
folded (may be a line number or
- a Pos ), and as second argument
- either a range-finder function, or an options object, supporting
- the following properties:
+ a Pos ), and as second optional
+ argument either a range-finder function, or an options object,
+ supporting the following properties:
rangeFinder : fn(CodeMirror, Pos)
- The function that is used to find foldable ranges. There
- are files in the addon/fold/
- directory providing CodeMirror.braceRangeFinder,
- which finds blocks in brace languages (JavaScript, C, Java,
- etc), CodeMirror.indentRangeFinder, for languages
- where indentation determines block structure (Python,
- Haskell), and CodeMirror.tagRangeFinder, for
- XML-style languages.
+ The function that is used to find foldable ranges. If this
+ is not directly passed, it will
+ call getHelper with
+ a "fold" type to find one that's appropriate for
+ the mode. There are files in
+ the addon/fold/
+ directory providing CodeMirror.fold.brace, which
+ finds blocks in brace languages (JavaScript, C, Java,
+ etc), CodeMirror.fold.indent, for languages where
+ indentation determines block structure (Python, Haskell),
+ and CodeMirror.fold.xml, for XML-style
+ languages.
widget : string|Element
The widget to show for folded ranges. Can be either a
string, in which case it'll become a span with
diff --git a/lib/codemirror.js b/lib/codemirror.js
index a43abb9724..659f8c96bc 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -2874,7 +2874,7 @@ window.CodeMirror = (function() {
if (!helpers.hasOwnProperty(type)) return;
var state = this.getTokenAt(pos).state, help = helpers[type];
var mode = CodeMirror.innerMode(this.getMode(), state).mode;
- return mode.type && help[mode[type]] ||
+ return mode[type] && help[mode[type]] ||
mode.helperType && help[mode.helperType] ||
help[mode.name];
},
diff --git a/mode/clike/clike.js b/mode/clike/clike.js
index 3fcc1a757b..f6626cd0ea 100644
--- a/mode/clike/clike.js
+++ b/mode/clike/clike.js
@@ -158,7 +158,8 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
electricChars: "{}",
blockCommentStart: "/*",
blockCommentEnd: "*/",
- lineComment: "//"
+ lineComment: "//",
+ fold: "brace"
};
});
diff --git a/mode/coffeescript/coffeescript.js b/mode/coffeescript/coffeescript.js
index 509d9207bb..4f54b0c690 100644
--- a/mode/coffeescript/coffeescript.js
+++ b/mode/coffeescript/coffeescript.js
@@ -339,7 +339,8 @@ CodeMirror.defineMode('coffeescript', function(conf) {
return state.scopes[0].offset;
},
- lineComment: "#"
+ lineComment: "#",
+ fold: "indent"
};
return external;
});
diff --git a/mode/css/css.js b/mode/css/css.js
index b38a968e5e..b52e787a70 100644
--- a/mode/css/css.js
+++ b/mode/css/css.js
@@ -289,7 +289,8 @@ CodeMirror.defineMode("css-base", function(config, parserConfig) {
electricChars: "}",
blockCommentStart: "/*",
- blockCommentEnd: "*/"
+ blockCommentEnd: "*/",
+ fold: "brace"
};
});
diff --git a/mode/groovy/groovy.js b/mode/groovy/groovy.js
index 92b948192e..6800e0aaf5 100644
--- a/mode/groovy/groovy.js
+++ b/mode/groovy/groovy.js
@@ -203,7 +203,8 @@ CodeMirror.defineMode("groovy", function(config) {
else return ctx.indented + (closing ? 0 : config.indentUnit);
},
- electricChars: "{}"
+ electricChars: "{}",
+ fold: "brace"
};
});
diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js
index 6435e138d6..f8c710f912 100644
--- a/mode/javascript/javascript.js
+++ b/mode/javascript/javascript.js
@@ -461,6 +461,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
blockCommentStart: jsonMode ? null : "/*",
blockCommentEnd: jsonMode ? null : "*/",
lineComment: jsonMode ? null : "//",
+ fold: "brace",
jsonMode: jsonMode
};
diff --git a/mode/python/python.js b/mode/python/python.js
index b623972b88..565d963fdf 100644
--- a/mode/python/python.js
+++ b/mode/python/python.js
@@ -333,7 +333,8 @@ CodeMirror.defineMode("python", function(conf, parserConf) {
return state.scopes[0].offset;
},
- lineComment: "#"
+ lineComment: "#",
+ fold: "indent"
};
return external;
});
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"
};
});
From e0e28c5dc02b54922f8853c1560d1d1fe74b9ef1 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Wed, 3 Jul 2013 14:26:22 +0200
Subject: [PATCH 045/110] [lint addon] Move over to registerHelper
---
addon/lint/coffeescript-lint.js | 5 +++--
addon/lint/javascript-lint.js | 9 +++------
addon/lint/json-lint.js | 5 +++--
addon/lint/lint.js | 18 ++++++++++++------
demo/lint.html | 6 +++---
doc/manual.html | 13 ++++++++-----
mode/javascript/javascript.js | 1 +
7 files changed, 33 insertions(+), 24 deletions(-)
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/demo/lint.html b/demo/lint.html
index ece8b1cef7..d379c31934 100644
--- a/demo/lint.html
+++ b/demo/lint.html
@@ -7,7 +7,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/doc/manual.html b/doc/manual.html
index c7466ce243..d028b9615c 100644
--- a/doc/manual.html
+++ b/doc/manual.html
@@ -1974,11 +1974,14 @@ Addons
with pluggable warning sources
(see json-lint.js
and javascript-lint.js
- in the same directory). Defines a lintWith option
- that can be set to a warning source (for
- example CodeMirror.javascriptValidator). Depends
- on addon/lint/lint.css. A demo can be
- found here .
+ in the same directory). Defines a lint option that
+ can be set to a warning source (for
+ example CodeMirror.lint.javascript), or
+ to true, in which
+ case getHelper with
+ type "lint" is used to determined a validator
+ function. Depends on addon/lint/lint.css. A demo
+ can be found here .
selection/mark-selection.js
Causes the selected text to be marked with the CSS class
diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js
index f8c710f912..f5507ce699 100644
--- a/mode/javascript/javascript.js
+++ b/mode/javascript/javascript.js
@@ -463,6 +463,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
lineComment: jsonMode ? null : "//",
fold: "brace",
+ helperType: jsonMode ? "json" : "javascript",
jsonMode: jsonMode
};
});
From 348db87b65b38d0b27f1a796797ee36f2a11195c Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Wed, 3 Jul 2013 14:40:45 +0200
Subject: [PATCH 046/110] [manual] Fix missing tag
---
doc/manual.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/manual.html b/doc/manual.html
index d028b9615c..68be9718f6 100644
--- a/doc/manual.html
+++ b/doc/manual.html
@@ -1584,7 +1584,7 @@ Static properties
(with the instance as argument) whenever a new CodeMirror instance
is initialized.
- CodeMirror.registerHelper (type: string, name: string, value: helper)
+ CodeMirror.registerHelper (type: string, name: string, value: helper)
Registers a helper value with the given name in
the given namespace (type). This is used to define
functionality that may be looked up by mode. Will create (if it
From 9d6f762ebec15dd5e00157c4baf5e3f5e73513dd Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Thu, 4 Jul 2013 08:32:26 +0200
Subject: [PATCH 047/110] [matchtags addon] Fix inefficiency
Issue #1653
---
addon/edit/matchtags.js | 10 +++++++---
addon/fold/xml-fold.js | 6 ++----
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/addon/edit/matchtags.js b/addon/edit/matchtags.js
index 66e57c8e9e..3eabfeb8d3 100644
--- a/addon/edit/matchtags.js
+++ b/addon/edit/matchtags.js
@@ -22,14 +22,18 @@
}
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 (cm.state.failedTagMatch = !match) return;
+ if (!match) return;
var other = match.at == "close" ? match.open : match.close;
- cm.state.matchedTag = cm.markText(other.from, other.to, {className: "CodeMirror-matchingtag"});
+ if (other)
+ cm.state.matchedTag = cm.markText(other.from, other.to, {className: "CodeMirror-matchingtag"});
+ else
+ cm.state.failedTagMatch = true;
});
}
@@ -41,7 +45,7 @@
var found = CodeMirror.findMatchingTag(cm, cm.getCursor());
if (found) {
var other = found.at == "close" ? found.open : found.close;
- cm.setSelection(other.to, other.from);
+ if (other) cm.setSelection(other.to, other.from);
}
};
})();
diff --git a/addon/fold/xml-fold.js b/addon/fold/xml-fold.js
index 08a87bd0a0..5707701aa5 100644
--- a/addon/fold/xml-fold.js
+++ b/addon/fold/xml-fold.js
@@ -146,12 +146,10 @@
var here = {from: Pos(iter.line, iter.ch), to: to, tag: start[2]};
if (start[1]) { // closing tag
- var open = findMatchingOpen(iter, start[2]);
- return open && {open: open, close: here, at: "close"};
+ return {open: findMatchingOpen(iter, start[2]), close: here, at: "close"};
} else { // opening tag
iter = new Iter(cm, to.line, to.ch, range);
- var close = findMatchingClose(iter, start[2]);
- return close && {open: here, close: close, at: "open"};
+ return {open: here, close: findMatchingClose(iter, start[2]), at: "open"};
}
};
From 5e706977b5f67d250c12f17ba71cbd1784783b29 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Thu, 4 Jul 2013 10:58:31 +0200
Subject: [PATCH 048/110] [foldgutter addon] Add
---
addon/fold/foldcode.js | 3 ++
addon/fold/foldgutter.js | 122 +++++++++++++++++++++++++++++++++++++++++++++++
demo/folding.html | 30 +++++++++---
doc/manual.html | 33 ++++++++++++-
4 files changed, 181 insertions(+), 7 deletions(-)
create mode 100644 addon/fold/foldgutter.js
diff --git a/addon/fold/foldcode.js b/addon/fold/foldcode.js
index d83a0f49dc..99b4c79cf5 100644
--- a/addon/fold/foldcode.js
+++ b/addon/fold/foldcode.js
@@ -16,7 +16,9 @@
if (marks[i].__isFold) {
if (!allowFolded) return null;
range.cleared = true;
+ var found = marks[i].find();
marks[i].clear();
+ CodeMirror.signal(cm, "unfold", cm, found.from, found.to);
}
}
return range;
@@ -36,6 +38,7 @@
clearOnEnter: true,
__isFold: true
});
+ CodeMirror.signal(cm, "fold", cm, range.from, range.to);
}
function makeWidget(options) {
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/demo/folding.html b/demo/folding.html
index d9823cf377..e6cecf26d0 100644
--- a/demo/folding.html
+++ b/demo/folding.html
@@ -6,6 +6,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:
@@ -47,18 +63,20 @@
mode: "javascript",
lineNumbers: true,
lineWrapping: true,
- extraKeys: {"Ctrl-Q": function(cm){ cm.foldCode(cm.getCursor()); }}
+ extraKeys: {"Ctrl-Q": function(cm){ cm.foldCode(cm.getCursor()); }},
+ foldGutter: true,
+ gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
});
- editor.on("gutterClick", function(cm, n) { cm.foldCode(CodeMirror.Pos(n, 0)); });
editor.foldCode(CodeMirror.Pos(8, 0));
window.editor_html = CodeMirror.fromTextArea(te_html, {
mode: "text/html",
lineNumbers: true,
lineWrapping: true,
- extraKeys: {"Ctrl-Q": function(cm){ cm.foldCode(cm.getCursor()); }}
+ extraKeys: {"Ctrl-Q": function(cm){ cm.foldCode(cm.getCursor()); }},
+ foldGutter: true,
+ gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
});
- editor_html.on("gutterClick", function(cm, n) { cm.foldCode(CodeMirror.Pos(n, 0)); });
editor_html.foldCode(CodeMirror.Pos(13, 0));
editor_html.foldCode(CodeMirror.Pos(1, 0));
};
diff --git a/doc/manual.html b/doc/manual.html
index 68be9718f6..12e2af7359 100644
--- a/doc/manual.html
+++ b/doc/manual.html
@@ -1432,7 +1432,7 @@ Mode, state, and token-related methods
unstyled tokens, and a string, potentially containing multiple
space-separated style names, otherwise.
- cm.getHelperAt (pos: {line, ch}, type: string) → helper
+ cm.getHelper (pos: {line, ch}, type: string) → helper
Fetch appropriate helper for the given position. Helpers
provide a way to look up functionality appropriate for a mode.
The type argument provides the helper namespace
@@ -1783,6 +1783,37 @@ Addons
See the demo for an
example.
+ fold/foldgutter.js
+ Provides an option foldGutter, which can be
+ used to create a gutter with markers indicating the blocks that
+ can be folded. Create a gutter using
+ the gutters option,
+ giving it the class CodeMirror-foldgutter or
+ something else if you configure the addon to use a different
+ class, and this addon will show markers next to folded and
+ foldable blocks, and handle clicks in this gutter. The option
+ can be either set to true, or an object containing
+ the following optional option fields:
+
+ gutter : string
+ The CSS class of the gutter. Defaults
+ to "CodeMirror-foldgutter". You will have to
+ style this yourself to give it a width (and possibly a
+ background).
+ indicatorOpen : string | Element
+ A CSS class or DOM element to be used as the marker for
+ open, foldable blocks. Defaults
+ to "CodeMirror-foldgutter-open".
+ indicatorFolded : string | Element
+ A CSS class or DOM element to be used as the marker for
+ folded blocks. Defaults to "CodeMirror-foldgutter-folded".
+ rangeFinder : fn(CodeMirror, Pos)
+ The range-finder function to use when determining whether
+ something can be folded. When not
+ given, getHelper will be
+ used to determine a default.
+
+
runmode/runmode.js
Can be used to run a CodeMirror mode over text without
actually opening an editor instance.
From 7499eace15c819055b9693daf375d9dcf4512aee Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Thu, 4 Jul 2013 11:00:56 +0200
Subject: [PATCH 049/110] Rename combineRangeFinders to CodeMirror.fold.combine
---
addon/fold/foldcode.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/addon/fold/foldcode.js b/addon/fold/foldcode.js
index 99b4c79cf5..51a7f5fd0e 100644
--- a/addon/fold/foldcode.js
+++ b/addon/fold/foldcode.js
@@ -60,7 +60,7 @@
// New-style interface
CodeMirror.defineExtension("foldCode", function(pos, options) { doFold(this, pos, options); });
- CodeMirror.combineRangeFinders = function() {
+ CodeMirror.fold.combine = function() {
var funcs = Array.prototype.slice.call(arguments, 0);
return function(cm, start) {
for (var i = 0; i < funcs.length; ++i) {
From d9a839fbe29a4ad186f1175458480f3557295587 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Thu, 4 Jul 2013 11:21:51 +0200
Subject: [PATCH 050/110] [match-highlighter addon] Improve showToken option
---
addon/search/match-highlighter.js | 20 +++++++++++---------
demo/matchhighlighter.html | 2 +-
doc/manual.html | 13 ++++++++-----
3 files changed, 20 insertions(+), 15 deletions(-)
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/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/doc/manual.html b/doc/manual.html
index 12e2af7359..5b08097a90 100644
--- a/doc/manual.html
+++ b/doc/manual.html
@@ -1812,7 +1812,8 @@ Addons
something can be folded. When not
given, getHelper will be
used to determine a default.
-
+
+ Demo here .
runmode/runmode.js
Can be used to run a CodeMirror mode over text without
@@ -1994,10 +1995,12 @@ Addons
minimum amount of selected characters that triggers a highlight
(default 2), style, for the style to be used to
highlight the matches (default "matchhighlight",
- which will correspond to CSS class cm-matchhighlight),
- and showToken which, when enabled, causes the
- current token to be highlighted when nothing is selected
- (defaults to off).
+ which will correspond to CSS
+ class cm-matchhighlight),
+ and showToken which can be set to true
+ or to a regexp matching the characters that make up a word. When
+ enabled, it causes the current word to be highlighted when
+ nothing is selected (defaults to off).
Demo here .
lint/lint.js
From b2b6b54b6e4617acc46a36cb1d924712c03f5237 Mon Sep 17 00:00:00 2001
From: Aurelian Oancea
Date: Thu, 4 Jul 2013 16:09:28 +0200
Subject: [PATCH 051/110] [midnight theme] fixed syntax error
---
theme/midnight.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/theme/midnight.css b/theme/midnight.css
index f10edf932a..bc2d49be1c 100644
--- a/theme/midnight.css
+++ b/theme/midnight.css
@@ -2,7 +2,7 @@
/**/
.cm-s-midnight span.CodeMirror-matchhighlight { background: #494949 }
-.cm-s-midnight.CodeMirror-focused span.CodeMirror-matchhighlight { background: #314D67; !important }
+.cm-s-midnight.CodeMirror-focused span.CodeMirror-matchhighlight { background: #314D67 !important; }
/**/
.cm-s-midnight .activeline {background: #253540 !important;}
From 8b8b7b061e441f8409b080ade327cacbc732272c Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Fri, 5 Jul 2013 08:43:04 +0200
Subject: [PATCH 052/110] [xml-fold addon] Add fast-case return to
findMatchingTag
---
addon/fold/xml-fold.js | 1 +
lib/codemirror.js | 1 +
2 files changed, 2 insertions(+)
diff --git a/addon/fold/xml-fold.js b/addon/fold/xml-fold.js
index 5707701aa5..b87da9088c 100644
--- a/addon/fold/xml-fold.js
+++ b/addon/fold/xml-fold.js
@@ -140,6 +140,7 @@
CodeMirror.findMatchingTag = function(cm, pos, range) {
var iter = new Iter(cm, pos.line, pos.ch, range);
+ if (!tagAt(iter, iter.ch)) 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;
diff --git a/lib/codemirror.js b/lib/codemirror.js
index 659f8c96bc..96e579e753 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -2862,6 +2862,7 @@ window.CodeMirror = (function() {
pos = clipPos(this.doc, pos);
var styles = getLineStyles(this, getLine(this.doc, pos.line));
var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;
+ if (ch == 0) return styles[2];
for (;;) {
var mid = (before + after) >> 1;
if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid;
From 4f43c8ce8780c531b6a1772e1e3eb34dcbfe037f Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Fri, 5 Jul 2013 16:11:26 +0200
Subject: [PATCH 053/110] Add getModeAt method
Use it to prevent some unneeded state computation.
---
addon/comment/comment.js | 6 +++---
addon/fold/foldcode.js | 4 ++--
doc/manual.html | 10 ++++++++--
lib/codemirror.js | 9 +++++++--
4 files changed, 20 insertions(+), 9 deletions(-)
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/fold/foldcode.js b/addon/fold/foldcode.js
index 51a7f5fd0e..69513cb8bb 100644
--- a/addon/fold/foldcode.js
+++ b/addon/fold/foldcode.js
@@ -60,7 +60,7 @@
// New-style interface
CodeMirror.defineExtension("foldCode", function(pos, options) { doFold(this, pos, options); });
- CodeMirror.fold.combine = 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) {
@@ -68,5 +68,5 @@
if (found) return found;
}
};
- };
+ });
})();
diff --git a/doc/manual.html b/doc/manual.html
index 5b08097a90..64c684b10b 100644
--- a/doc/manual.html
+++ b/doc/manual.html
@@ -1401,11 +1401,17 @@ Mode, state, and token-related methods
doc.getMode () → object
- Gets the mode object for the editor. Note that this is
- distinct from getOption("mode"), which gives you
+ Gets the (outer) mode object for the editor. Note that this
+ is distinct from getOption("mode"), which gives you
the mode specification, rather than the resolved, instantiated
mode object .
+ doc.getModeAt (pos: {line, ch}) → object
+ Gets the inner mode at a given position. This will return
+ the same as getMode for
+ simple modes, but will return an inner mode for nesting modes
+ (such as htmlmixed).
+
cm.getTokenAt (pos: {line, ch}, ?precise: boolean) → object
Retrieves information about the token the current mode found
before the given position (a {line, ch} object). The
diff --git a/lib/codemirror.js b/lib/codemirror.js
index 96e579e753..1caa59d2d2 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -2871,10 +2871,15 @@ window.CodeMirror = (function() {
}
},
+ getModeAt: function(pos) {
+ var mode = this.doc.mode;
+ if (!mode.innerMode) return mode;
+ return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode;
+ },
+
getHelper: function(pos, type) {
if (!helpers.hasOwnProperty(type)) return;
- var state = this.getTokenAt(pos).state, help = helpers[type];
- var mode = CodeMirror.innerMode(this.getMode(), state).mode;
+ var help = helpers[type], mode = this.getModeAt(pos);
return mode[type] && help[mode[type]] ||
mode.helperType && help[mode.helperType] ||
help[mode.name];
From 76894881e46571dc5bb8df953b32d71dc0113a0a Mon Sep 17 00:00:00 2001
From: Brandon Frohs
Date: Sun, 7 Jul 2013 21:11:40 -0400
Subject: [PATCH 054/110] [markdown] Highlight trailing spaces that affect line
breaks
---
mode/markdown/index.html | 7 ++++++-
mode/markdown/markdown.js | 27 ++++++++++++++++++++++++++-
mode/markdown/test.js | 14 ++++++++++++++
3 files changed, 46 insertions(+), 2 deletions(-)
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]");
From 25e4be044206c7b26b003df7cb8e2fcb0cbe71cf Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Mon, 8 Jul 2013 09:22:56 +0200
Subject: [PATCH 055/110] [javascript mode] Don't get confused by empty
brackets
---
mode/javascript/javascript.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js
index f5507ce699..d51745d837 100644
--- a/mode/javascript/javascript.js
+++ b/mode/javascript/javascript.js
@@ -311,7 +311,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
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);
From e99efe2543edea09ce6780e826288add7ddec48d Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Tue, 9 Jul 2013 10:36:05 +0200
Subject: [PATCH 056/110] [javascript mode] Fix bad indentation below if
Closes #1662
---
mode/javascript/javascript.js | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js
index d51745d837..3d04603c28 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) {
@@ -373,14 +373,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 +435,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;
From c921c10de61a733a55015b5607f1c360baf2e285 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Tue, 9 Jul 2013 11:09:30 +0200
Subject: [PATCH 057/110] Add another exception to the spanAffectsWrapping hack
And turn the hack off in recent Chrome builds.
Issue #1663
---
lib/codemirror.js | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/lib/codemirror.js b/lib/codemirror.js
index 1caa59d2d2..ab1cce5fb6 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -5391,10 +5391,12 @@ window.CodeMirror = (function() {
spanAffectsWrapping = function(str, i) {
return /\-[^ \-?]|\?[^ !\'\"\),.\-\/:;\?\]\}]/.test(str.slice(i - 1, i + 1));
};
- else if (webkit)
+ else if (webkit && !/Chrome\/(?:29|[3-9]\d|\d\d\d)\./.test(navigator.userAgent))
spanAffectsWrapping = function(str, i) {
- if (i > 1 && str.charCodeAt(i - 1) == 45 && /\w/.test(str.charAt(i - 2)) && /[^\-?\.]/.test(str.charAt(i)))
- return true;
+ if (i > 1 && str.charCodeAt(i - 1) == 45) {
+ if (/\w/.test(str.charAt(i - 2)) && /[^\-?\.]/.test(str.charAt(i))) return true;
+ if (i > 2 && /[\d\.,]/.test(str.charAt(i - 2)) && /[\d\.,]/.test(str.charAt(i))) return false;
+ }
return /[~!#%&*)=+}\]|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|…[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1));
};
From 8068b3d55d397ad14350e6b1593fa53095927f4b Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke
Date: Thu, 11 Jul 2013 08:09:06 +0200
Subject: [PATCH 058/110] Fix removeKeyMap for string keymap values
---
lib/codemirror.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/codemirror.js b/lib/codemirror.js
index ab1cce5fb6..330a09e385 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -2798,7 +2798,7 @@ window.CodeMirror = (function() {
removeKeyMap: function(map) {
var maps = this.state.keyMaps;
for (var i = 0; i < maps.length; ++i)
- if ((typeof map == "string" ? maps[i].name : maps[i]) == map) {
+ if (maps[i] == map || (typeof maps[i] != "string" && maps[i].name == map)) {
maps.splice(i, 1);
return true;
}
From 4651de869bcd7a1503bbe14540aebc3a8879705f Mon Sep 17 00:00:00 2001
From: Matthias BUSSONNIER
Date: Tue, 25 Jun 2013 17:12:09 +0200
Subject: [PATCH 059/110] [python mode] Add MIME for Cython dialect
---
doc/modes.html | 1 +
index.html | 1 +
mode/meta.js | 3 ++-
mode/python/index.html | 46 +++++++++++++++++++++++++++++++++++++++++++---
mode/python/python.js | 16 ++++++++++++++++
5 files changed, 63 insertions(+), 4 deletions(-)
diff --git a/doc/modes.html b/doc/modes.html
index 69c7d5e9ad..1c76f2fb1b 100644
--- a/doc/modes.html
+++ b/doc/modes.html
@@ -31,6 +31,7 @@
CoffeeScript
Common Lisp
CSS
+ Cython
D
diff
ECL
diff --git a/index.html b/index.html
index 6a9dbcb603..544aac1200 100644
--- a/index.html
+++ b/index.html
@@ -40,6 +40,7 @@ Supported modes:
CoffeeScript
Common Lisp
CSS
+ Cython
D
diff
ECL
diff --git a/mode/meta.js b/mode/meta.js
index cb1051e68f..1ea0f672b0 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'},
@@ -47,6 +47,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'},
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
# Literals
1234
@@ -102,6 +102,34 @@
self.mixin = mixin
+
+
+Cython mode
+
+
+
+import numpy as np
+cimport cython
+from libc.math cimport sqrt
+
+@cython.boundscheck(False)
+@cython.wraparound(False)
+def pairwise_cython(double[:, ::1] X):
+ cdef int M = X.shape[0]
+ cdef int N = X.shape[1]
+ cdef double tmp, d
+ cdef double[:, ::1] D = np.empty((M, M), dtype=np.float64)
+ for i in range(M):
+ for j in range(M):
+ d = 0.0
+ for k in range(N):
+ tmp = X[i, k] - X[j, k]
+ d += tmp * tmp
+ D[i, j] = sqrt(d)
+ return np.asarray(D)
+
+
+
- Configuration Options:
+ Configuration Options for Python mode:
- MIME types defined: text/x-python.
+ MIME types defined: text/x-python and text/x-cython.