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

    + + +

    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 @@ + 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 @@ - + + + + + - - + - - + - - - - - + + + - + + + + + - + + + + - + - - - - - - - + + + + + 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

    + +
    + +

    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

    + + +

    Cython mode

    + +
    + -

    Configuration Options:

    +

    Configuration Options for Python mode:

    • version - 2/3 - The version of Python to recognize. Default is 2.
    • singleLineStringErrors - true/false - If you have a single-line string that is not terminated at the end of the line, this will show subsequent lines as errors if true, otherwise it will consider the newline as the end of the string. Default is false.
    • @@ -127,9 +165,11 @@
    • doubleDelimiters - RegEx - Regular Expressoin for double delimiters matching, default :
      ^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))
    • tripleDelimiters - RegEx - Regular Expression for triple delimiters matching, default :
      ^((//=)|(>>=)|(<<=)|(\\*\\*=))
    • identifiers - RegEx - Regular Expression for identifier, default :
      ^[_A-Za-z][_A-Za-z0-9]*
    • +
    • extra_keywords - list of string - List of extra words ton consider as keywords
    • +
    • extra_builtins - list of string - List of extra words ton consider as builtins
    -

    MIME types defined: text/x-python.

    +

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

    diff --git a/mode/python/python.js b/mode/python/python.js index 565d963fdf..2c6d1d83c7 100644 --- a/mode/python/python.js +++ b/mode/python/python.js @@ -36,6 +36,12 @@ CodeMirror.defineMode("python", function(conf, parserConf) { var py3 = {'builtins': ['ascii', 'bytes', 'exec', 'print'], 'keywords': ['nonlocal', 'False', 'True', 'None']}; + if(parserConf.extra_keywords != undefined){ + commonkeywords = commonkeywords.concat(parserConf.extra_keywords); + } + if(parserConf.extra_builtins != undefined){ + commonBuiltins = commonBuiltins.concat(parserConf.extra_builtins); + } if (!!parserConf.version && parseInt(parserConf.version, 10) === 3) { commonkeywords = commonkeywords.concat(py3.keywords); commonBuiltins = commonBuiltins.concat(py3.builtins); @@ -340,3 +346,13 @@ CodeMirror.defineMode("python", function(conf, parserConf) { }); CodeMirror.defineMIME("text/x-python", "python"); + +var words = function(str){return str.split(' ');}; + + +CodeMirror.defineMIME("text/x-cython", { + name: "python", + extra_keywords: words("by cdef cimport cpdef ctypedef enum except"+ + "extern gil include nogil property public"+ + "readonly struct union DEF IF ELIF ELSE") +}); From 9e8199b3d604009f1a3b8df4691f9d3469c7aefe Mon Sep 17 00:00:00 2001 From: Michael Zhou Date: Tue, 9 Jul 2013 12:42:25 -0700 Subject: [PATCH 060/110] Fix bug in addLineWidget. Fixed clipping of the insertAt index. --- lib/codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 330a09e385..03933ff9eb 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -4108,7 +4108,7 @@ window.CodeMirror = (function() { changeLine(cm, handle, function(line) { var widgets = line.widgets || (line.widgets = []); if (widget.insertAt == null) widgets.push(widget); - else widgets.splice(Math.max(widgets.length - 1, widget.insertAt), 0, widget); + else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); widget.line = line; if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) { var aboveVisible = heightAtLine(cm, line) < cm.doc.scrollTop; From 486976e16d42ca9cf2b6b01bdd21912124fbc562 Mon Sep 17 00:00:00 2001 From: Michael Zhou Date: Tue, 9 Jul 2013 15:02:43 -0700 Subject: [PATCH 061/110] Fix addLineWidget documentation. Removed "pos" parameter. --- doc/manual.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual.html b/doc/manual.html index 64c684b10b..a4f8bc060e 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, ?pos: integer) → LineWidget
    +
    cm.addLineWidget(line: integer|LineHandle, node: Element, ?options: object) → 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 From ab2b604ed8bc64788c3590683e446f38043bb671 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Wed, 10 Jul 2013 21:16:48 +0200 Subject: [PATCH 062/110] Fix selecting until the start of a bidi line --- lib/codemirror.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 03933ff9eb..6bfd652fe5 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -5483,11 +5483,15 @@ window.CodeMirror = (function() { function iterateBidiSections(order, from, to, f) { if (!order) return f(from, to, "ltr"); + var found = false; for (var i = 0; i < order.length; ++i) { var part = order[i]; - if (part.from < to && part.to > from || from == to && part.to == from) + if (part.from < to && part.to > from || from == to && part.to == from) { f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); + found = true; + } } + if (!found) f(from, to, "ltr"); } function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } From 1a028d007a50c117775aabca1e5d932115333e9f Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 11 Jul 2013 10:18:01 +0200 Subject: [PATCH 063/110] Support 'title' option to markText Issue #1654 --- doc/manual.html | 4 ++++ lib/codemirror.js | 19 +++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index a4f8bc060e..cb3bc50509 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -1151,6 +1151,10 @@

    Text-marking methods

    is part of the marker.
    endStyle: string
    Equivalent to startStyle, but for the rightmost span.
    +
    title: + string
    When given, will give the nodes created + for this span a HTML title attribute with the + given value.
    shared: boolean
    When the target document is linked to other documents, you can set shared to true to make the diff --git a/lib/codemirror.js b/lib/codemirror.js index 6bfd652fe5..fe956fd81a 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3791,7 +3791,7 @@ window.CodeMirror = (function() { } if (cm) { if (updateMaxLine) cm.curOp.updateMaxLine = true; - if (marker.className || marker.startStyle || marker.endStyle || marker.collapsed) + if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.collapsed) regChange(cm, from.line, to.line + 1); if (marker.atomic) reCheckSelection(cm); } @@ -4284,7 +4284,7 @@ window.CodeMirror = (function() { } var tokenSpecialChars = /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\uFEFF]/g; - function buildToken(builder, text, style, startStyle, endStyle) { + function buildToken(builder, text, style, startStyle, endStyle, title) { if (!text) return; if (!tokenSpecialChars.test(text)) { builder.col += text.length; @@ -4317,7 +4317,9 @@ window.CodeMirror = (function() { var fullStyle = style || ""; if (startStyle) fullStyle += startStyle; if (endStyle) fullStyle += endStyle; - return builder.pre.appendChild(elt("span", [content], fullStyle)); + var token = elt("span", [content], fullStyle); + if (title) token.title = title; + return builder.pre.appendChild(token); } builder.pre.appendChild(content); } @@ -4355,8 +4357,8 @@ window.CodeMirror = (function() { out += " "; return out; } - return function(builder, text, style, startStyle, endStyle) { - return inner(builder, text.replace(/ {3,}/, split), style, startStyle, endStyle); + return function(builder, text, style, startStyle, endStyle, title) { + return inner(builder, text.replace(/ {3,}/, split), style, startStyle, endStyle, title); }; } @@ -4392,10 +4394,10 @@ window.CodeMirror = (function() { } var len = allText.length, pos = 0, i = 1, text = "", style; - var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed; + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed; for (;;) { if (nextChange == pos) { // Update current marker set - spanStyle = spanEndStyle = spanStartStyle = ""; + spanStyle = spanEndStyle = spanStartStyle = title = ""; collapsed = null; nextChange = Infinity; var foundBookmark = null; for (var j = 0; j < spans.length; ++j) { @@ -4405,6 +4407,7 @@ window.CodeMirror = (function() { if (m.className) spanStyle += " " + m.className; if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; + if (m.title && !title) title = m.title; if (m.collapsed && (!collapsed || collapsed.marker.size < m.size)) collapsed = sp; } else if (sp.from > pos && nextChange > sp.from) { @@ -4428,7 +4431,7 @@ window.CodeMirror = (function() { if (!collapsed) { var tokenText = end > upto ? text.slice(0, upto - pos) : text; builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, - spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : ""); + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title); } if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} pos = end; From 4f85a7ef97942a31b07609b11f762c785a71a34e Mon Sep 17 00:00:00 2001 From: John Snelson Date: Thu, 11 Jul 2013 09:50:43 +0100 Subject: [PATCH 064/110] Added SPARQL 1.1 keywords to the SPARQL mode. --- mode/sparql/sparql.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mode/sparql/sparql.js b/mode/sparql/sparql.js index f7237698d3..1168f23a52 100644 --- a/mode/sparql/sparql.js +++ b/mode/sparql/sparql.js @@ -9,7 +9,9 @@ CodeMirror.defineMode("sparql", function(config) { "isblank", "isliteral", "union", "a"]); var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe", "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional", - "graph", "by", "asc", "desc"]); + "graph", "by", "asc", "desc", "as", "having", "undef", "values", "group", + "minus", "in", "not", "service", "silent", "using", "insert", "delete", + "data", "copy", "to", "move", "add", "create", "drop", "clear", "load"]); var operatorChars = /[*+\-<>=&|]/; function tokenBase(stream, state) { From ef328ed46d2a7cc5e3d47735a9725230fadc4d59 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 12 Jul 2013 10:38:17 +0200 Subject: [PATCH 065/110] Add convenient event methods to all event-emitting objects i.e. linehandle.on, markedrange.off, etc. --- doc/manual.html | 22 ++++++++++------------ lib/codemirror.js | 34 +++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index cb3bc50509..d17eb79853 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -375,14 +375,15 @@

    Configuration

    Events

    -

    A CodeMirror instance emits a number of events, which allow - client code to react to various situations. These are registered - with the on method (and - removed with the off - method). These are the events that fire on the instance object. - The name of the event is followed by the arguments that will be - passed to the handler. The instance argument always - refers to the editor instance.

    +

    Various CodeMirror-related objects emit events, which allow + client code to react to various situations. Handlers for such + events can be registed with the on + and off methods on the objects + that the event fires on.

    + +

    An editor instance fires the following events. + The instance argument always refers to the editor + itself.

    "change" (instance: CodeMirror, changeObj: object)
    @@ -497,10 +498,7 @@

    Events

    CodeMirror should do no further handling.
    -

    It is also possible to register events on - other objects. Use CodeMirror.on(handle, "eventName", - func) to register handlers on objects that don't have their - own on method. Document objects (instances +

    Document objects (instances of CodeMirror.Doc) emit the following events:

    diff --git a/lib/codemirror.js b/lib/codemirror.js index fe956fd81a..35c2e30f18 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3123,9 +3123,6 @@ window.CodeMirror = (function() { this.refresh(); }, - on: function(type, f) {on(this, type, f);}, - off: function(type, f) {off(this, type, f);}, - operation: function(f){return runInOp(this, f);}, refresh: operation(null, function() { @@ -3149,6 +3146,7 @@ window.CodeMirror = (function() { getScrollerElement: function(){return this.display.scroller;}, getGutterElement: function(){return this.display.gutters;} }; + eventMixin(CodeMirror); // OPTION DEFAULTS @@ -3657,6 +3655,7 @@ window.CodeMirror = (function() { this.doc = doc; } CodeMirror.TextMarker = TextMarker; + eventMixin(TextMarker); TextMarker.prototype.clear = function() { if (this.explicitlyCleared) return; @@ -3809,6 +3808,7 @@ window.CodeMirror = (function() { } } CodeMirror.SharedTextMarker = SharedTextMarker; + eventMixin(SharedTextMarker); SharedTextMarker.prototype.clear = function() { if (this.explicitlyCleared) return; @@ -4066,6 +4066,7 @@ window.CodeMirror = (function() { this.cm = cm; this.node = node; }; + eventMixin(LineWidget); function widgetOperation(f) { return function() { var withOp = !this.cm.curOp; @@ -4124,12 +4125,12 @@ window.CodeMirror = (function() { // Line objects. These hold state related to a line, including // highlighting info (the styles array). - function makeLine(text, markedSpans, estimateHeight) { - var line = {text: text}; - attachMarkedSpans(line, markedSpans); - line.height = estimateHeight ? estimateHeight(line) : 1; - return line; + function Line(text, markedSpans, estimateHeight) { + this.text = text; + attachMarkedSpans(this, markedSpans); + this.height = estimateHeight ? estimateHeight(this) : 1; } + eventMixin(Line); function updateLine(line, text, markedSpans, estimateHeight) { line.text = text; @@ -4461,7 +4462,7 @@ window.CodeMirror = (function() { // This is a whole-line replace. Treated specially to make // sure line objects move the way they are supposed to. for (var i = 0, e = text.length - 1, added = []; i < e; ++i) - added.push(makeLine(text[i], spansFor(i), estimateHeight)); + added.push(new Line(text[i], spansFor(i), estimateHeight)); update(lastLine, lastLine.text, lastSpans); if (nlines) doc.remove(from.line, nlines); if (added.length) doc.insert(from.line, added); @@ -4470,8 +4471,8 @@ window.CodeMirror = (function() { update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); } else { for (var added = [], i = 1, e = text.length - 1; i < e; ++i) - added.push(makeLine(text[i], spansFor(i), estimateHeight)); - added.push(makeLine(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); + added.push(new Line(text[i], spansFor(i), estimateHeight)); + added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); doc.insert(from.line + 1, added); } @@ -4482,7 +4483,7 @@ window.CodeMirror = (function() { update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); for (var i = 1, e = text.length - 1, added = []; i < e; ++i) - added.push(makeLine(text[i], spansFor(i), estimateHeight)); + added.push(new Line(text[i], spansFor(i), estimateHeight)); if (nlines > 1) doc.remove(from.line + 1, nlines - 1); doc.insert(from.line + 1, added); } @@ -4625,7 +4626,7 @@ window.CodeMirror = (function() { if (!(this instanceof Doc)) return new Doc(text, mode, firstLine); if (firstLine == null) firstLine = 0; - BranchChunk.call(this, [new LeafChunk([makeLine("", null)])]); + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); this.first = firstLine; this.scrollTop = this.scrollLeft = 0; this.cantEdit = false; @@ -4860,6 +4861,8 @@ window.CodeMirror = (function() { return function() {return method.apply(this.doc, arguments);}; })(Doc.prototype[prop]); + eventMixin(Doc); + function linkedDocs(doc, f, sharedHistOnly) { function propagate(doc, skip, sharedHist) { if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) { @@ -5243,6 +5246,11 @@ window.CodeMirror = (function() { CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal; + function eventMixin(ctor) { + ctor.prototype.on = function(type, f) {on(this, type, f);}; + ctor.prototype.off = function(type, f) {off(this, type, f);}; + } + // MISC UTILITIES // Number of pixels added to scroller and sizer to hide scrollbar From 649b848b91d33471ad43708dc2af3abfeebc17ad Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 12 Jul 2013 10:45:55 +0200 Subject: [PATCH 066/110] [activeline addon] Don't get confused by collapsed spans Issue #1668 --- addon/selection/active-line.js | 2 +- lib/codemirror.js | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/addon/selection/active-line.js b/addon/selection/active-line.js index 65fab6f162..e50508653a 100644 --- a/addon/selection/active-line.js +++ b/addon/selection/active-line.js @@ -29,7 +29,7 @@ } function updateActiveLine(cm) { - var line = cm.getLineHandle(cm.getCursor().line); + var line = cm.getLineHandleVisualStart(cm.getCursor().line); if (cm.state.activeLine == line) return; clearActiveLine(cm); cm.addLineClass(line, "wrap", WRAP_CLASS); diff --git a/lib/codemirror.js b/lib/codemirror.js index 35c2e30f18..9a6a932010 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -4125,11 +4125,11 @@ window.CodeMirror = (function() { // Line objects. These hold state related to a line, including // highlighting info (the styles array). - function Line(text, markedSpans, estimateHeight) { + var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) { this.text = text; attachMarkedSpans(this, markedSpans); this.height = estimateHeight ? estimateHeight(this) : 1; - } + }; eventMixin(Line); function updateLine(line, text, markedSpans, estimateHeight) { @@ -4691,6 +4691,11 @@ window.CodeMirror = (function() { getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);}, getLineNumber: function(line) {return lineNo(line);}, + getLineHandleVisualStart: function(line) { + if (typeof line == "number") line = getLine(this, line); + return visualLine(this, line); + }, + lineCount: function() {return this.size;}, firstLine: function() {return this.first;}, lastLine: function() {return this.first + this.size - 1;}, From 47655e693d5d29fc549797aa0cd79800d7828427 Mon Sep 17 00:00:00 2001 From: Ruslan Osmanov Date: Sat, 29 Jun 2013 19:28:16 +0500 Subject: [PATCH 067/110] [smartymixed mode] Add --- .gitignore | 2 + mode/smartymixed/index.html | 107 +++++++++++++++++++++++++ mode/smartymixed/smartymixed.js | 170 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 279 insertions(+) create mode 100644 mode/smartymixed/index.html create mode 100644 mode/smartymixed/smartymixed.js diff --git a/.gitignore b/.gitignore index bc20ab58d5..b471fe6e63 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ /npm-debug.log test.html .tern-* +*~ +*.swp diff --git a/mode/smartymixed/index.html b/mode/smartymixed/index.html new file mode 100644 index 0000000000..6567b27d6b --- /dev/null +++ b/mode/smartymixed/index.html @@ -0,0 +1,107 @@ + + + + + CodeMirror: Smarty mixed mode + + + + + + + + + + + + + + + +

    CodeMirror: Smarty mixed mode

    + + + + +

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

    + +

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

    + +

    MIME types defined: text/x-smarty.

    + + + diff --git a/mode/smartymixed/smartymixed.js b/mode/smartymixed/smartymixed.js new file mode 100644 index 0000000000..c5d008885a --- /dev/null +++ b/mode/smartymixed/smartymixed.js @@ -0,0 +1,170 @@ +/** +* @file smartymixed.js +* @brief Smarty Mixed Codemirror mode (Smarty + Mixed HTML) +* @author Ruslan Osmanov +* @version 3.0 +* @date 05.07.2013 +*/ +CodeMirror.defineMode("smartymixed", function(config) { + var settings, regs, helpers, parsers, + htmlMixedMode = CodeMirror.getMode(config, "htmlmixed"), + smartyMode = CodeMirror.getMode(config, "smarty"), + + settings = { + rightDelimiter: '}', + leftDelimiter: '{' + }; + + if (config.hasOwnProperty("leftDelimiter")) { + settings.leftDelimiter = config.leftDelimiter; + } + if (config.hasOwnProperty("rightDelimiter")) { + settings.rightDelimiter = config.rightDelimiter; + } + + regs = { + smartyComment: new RegExp("^" + settings.leftDelimiter + "\\*"), + literalOpen: new RegExp(settings.leftDelimiter + "literal" + settings.rightDelimiter), + literalClose: new RegExp(settings.leftDelimiter + "\/literal" + settings.rightDelimiter), + hasLeftDelimeter: new RegExp(".*" + settings.leftDelimiter), + htmlHasLeftDelimeter: new RegExp("[^<>]*" + settings.leftDelimiter) + }; + + helpers = { + chain: function(stream, state, parser) { + state.tokenize = parser; + return parser(stream, state); + }, + + cleanChain: function(stream, state, parser) { + state.tokenize = null; + state.localState = null; + state.localMode = null; + return (typeof parser == "string") ? (parser ? parser : null) : parser(stream, state); + }, + + maybeBackup: function(stream, pat, style) { + var cur = stream.current(); + var close = cur.search(pat), + m; + if (close > - 1) stream.backUp(cur.length - close); + else if (m = cur.match(/<\/?$/)) { + stream.backUp(cur.length); + if (!stream.match(pat, false)) stream.match(cur[0]); + } + return style; + } + }; + + parsers = { + html: function(stream, state) { + if (!state.inLiteral && stream.match(regs.htmlHasLeftDelimeter, false)) { + state.tokenize = parsers.smarty; + state.localMode = smartyMode; + state.localState = smartyMode.startState(htmlMixedMode.indent(state.htmlMixedState, "")); + return helpers.maybeBackup(stream, settings.leftDelimiter, smartyMode.token(stream, state.localState)); + } + return htmlMixedMode.token(stream, state.htmlMixedState); + }, + + smarty: function(stream, state) { + if (stream.match(settings.leftDelimiter, false)) { + if (stream.match(regs.smartyComment, false)) { + return helpers.chain(stream, state, parsers.inBlock("comment", "*" + settings.rightDelimiter)); + } + } else if (stream.match(settings.rightDelimiter, false)) { + stream.eat(settings.rightDelimiter); + state.tokenize = parsers.html; + state.localMode = htmlMixedMode; + state.localState = state.htmlMixedState; + return "tag"; + } + + return helpers.maybeBackup(stream, settings.rightDelimiter, smartyMode.token(stream, state.localState)); + }, + + inBlock: function(style, terminator) { + return function(stream, state) { + while (!stream.eol()) { + if (stream.match(terminator)) { + helpers.cleanChain(stream, state, ""); + break; + } + stream.next(); + } + return style; + }; + } + }; + + return { + startState: function() { + var state = htmlMixedMode.startState(); + return { + token: parsers.html, + localMode: null, + localState: null, + htmlMixedState: state, + tokenize: null, + inLiteral: false + }; + }, + + copyState: function(state) { + var local = null, tok = (state.tokenize || state.token); + if (state.localState) { + local = CodeMirror.copyState((tok != parsers.html ? smartyMode : htmlMixedMode), state.localState); + } + return { + token: state.token, + tokenize: state.tokenize, + localMode: state.localMode, + localState: local, + htmlMixedState: CodeMirror.copyState(htmlMixedMode, state.htmlMixedState), + inLiteral: state.inLiteral + }; + }, + + token: function(stream, state) { + if (stream.match(settings.leftDelimiter, false)) { + if (!state.inLiteral && stream.match(regs.literalOpen, true)) { + state.inLiteral = true; + return "keyword"; + } else if (state.inLiteral && stream.match(regs.literalClose, true)) { + state.inLiteral = false; + return "keyword"; + } + } + if (state.inLiteral && state.localState != state.htmlMixedState) { + state.tokenize = parsers.html; + state.localMode = htmlMixedMode; + state.localState = state.htmlMixedState; + } + + var style = (state.tokenize || state.token)(stream, state); + return style; + }, + + indent: function(state, textAfter) { + if (state.localMode == smartyMode + || (state.inLiteral && !state.localMode) + || regs.hasLeftDelimeter.test(textAfter)) { + return CodeMirror.Pass; + } + return htmlMixedMode.indent(state.htmlMixedState, textAfter); + }, + + electricChars: "/{}:", + + innerMode: function(state) { + return { + state: state.localState || state.htmlMixedState, + mode: state.localMode || htmlMixedMode + }; + } + }; +}, +"htmlmixed"); + +CodeMirror.defineMIME("text/x-smarty", "smartymixed"); +// vim: et ts=2 sts=2 sw=2 From 7f560afa1ce8c6ce7a43c561e35bd43f4b9652c3 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 12 Jul 2013 10:50:43 +0200 Subject: [PATCH 068/110] [smartymixed 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 c7eec47d5e..9ccafba4ae 100644 --- a/doc/compress.html +++ b/doc/compress.html @@ -123,6 +123,7 @@ + diff --git a/doc/modes.html b/doc/modes.html index 1c76f2fb1b..cb72754eb7 100644 --- a/doc/modes.html +++ b/doc/modes.html @@ -76,6 +76,7 @@
  • Sieve
  • Smalltalk
  • Smarty
  • +
  • Smarty/HTML mixed
  • SQL (several dialects)
  • SPARQL
  • sTeX, LaTeX
  • diff --git a/mode/meta.js b/mode/meta.js index 1ea0f672b0..63123a0254 100644 --- a/mode/meta.js +++ b/mode/meta.js @@ -59,6 +59,7 @@ CodeMirror.modeInfo = [ {name: 'Sieve', mime: 'application/sieve', mode: 'sieve'}, {name: 'Smalltalk', mime: 'text/x-stsrc', mode: 'smalltalk'}, {name: 'Smarty', mime: 'text/x-smarty', mode: 'smarty'}, + {name: 'SmartyMixed', mime: 'text/x-smarty', mode: 'smartymixed'}, {name: 'SPARQL', mime: 'application/x-sparql-query', mode: 'sparql'}, {name: 'SQL', mime: 'text/x-sql', mode: 'sql'}, {name: 'MariaDB', mime: 'text/x-mariadb', mode: 'sql'}, From 47f1a861d4493dc5b582b0b04d48155311301c1e Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Fri, 12 Jul 2013 22:34:09 -0400 Subject: [PATCH 069/110] [vim] Disable insert mode if document is readonly --- keymap/vim.js | 1 + 1 file changed, 1 insertion(+) diff --git a/keymap/vim.js b/keymap/vim.js index 977642ea38..ab7f757b1e 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -1625,6 +1625,7 @@ emptyMacroKeyBuffer(macroModeState); }, enterInsertMode: function(cm, actionArgs, vim) { + if (cm.getOption('readOnly')) { return; } vim.insertMode = true; vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1; var insertAt = (actionArgs) ? actionArgs.insertAt : null; From 13dc1753c35768c4802dfc8749b26011bf18bd42 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sat, 13 Jul 2013 11:40:48 +0200 Subject: [PATCH 070/110] Fire DOM event from contextmenu handler Issue #1674 --- doc/manual.html | 2 +- lib/codemirror.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index d17eb79853..55f7196aaf 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -489,7 +489,7 @@

    Events

    should not try to change the state of the editor.
    "mousedown", - "dblclick", "keydown", "keypress", + "dblclick", "contextmenu", "keydown", "keypress", "keyup", "dragstart", "dragenter", "dragover", "drop" (instance: CodeMirror, event: Event)
    diff --git a/lib/codemirror.js b/lib/codemirror.js index 9a6a932010..4e3a07eccb 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2107,6 +2107,7 @@ window.CodeMirror = (function() { var detectingSelectAll; function onContextMenu(cm, e) { + if (signalDOMEvent(cm, e, "contextmenu")) return; var display = cm.display, sel = cm.doc.sel; if (eventInWidget(display, e)) return; @@ -5232,8 +5233,8 @@ window.CodeMirror = (function() { delayedCallbacks.push(bnd(arr[i])); } - function signalDOMEvent(cm, e) { - signal(cm, e.type, cm, e); + function signalDOMEvent(cm, e, override) { + signal(cm, override || e.type, cm, e); return e_defaultPrevented(e); } From 35ae796c194fa638dde8a97da00286012f41c5c4 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Sat, 13 Jul 2013 11:43:39 +0200 Subject: [PATCH 071/110] [real-world used] Add CodeZample --- doc/realworld.html | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/realworld.html b/doc/realworld.html index 2150abe401..9aca75a75d 100644 --- a/doc/realworld.html +++ b/doc/realworld.html @@ -41,6 +41,7 @@
  • Code Snippets (WordPress snippet management plugin)
  • Code together (collaborative editing)
  • Codev (collaborative IDE)
  • +
  • CodeZample (code snippet sharing)
  • Collaborative CodeMirror demo (CodeMirror + operational transforms)
  • Community Code Camp (code snippet sharing)
  • CKWNC (UML editor)
  • From b05b96e0306d3b33b8a7810884a0d1cfb7b42969 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 12 Jul 2013 12:47:00 +0400 Subject: [PATCH 072/110] [closebrackets addon] Do not insert pair of quotes right after the word --- addon/edit/closebrackets.js | 4 +++- lib/codemirror.js | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/addon/edit/closebrackets.js b/addon/edit/closebrackets.js index 6fbf38ae67..88718b7729 100644 --- a/addon/edit/closebrackets.js +++ b/addon/edit/closebrackets.js @@ -54,7 +54,9 @@ if (cm.somethingSelected()) return surround(cm); if (left == right && maybeOverwrite(cm) != CodeMirror.Pass) return; var cur = cm.getCursor(), ahead = CodeMirror.Pos(cur.line, cur.ch + 1); - var line = cm.getLine(cur.line), nextChar = line.charAt(cur.ch); + var line = cm.getLine(cur.line), nextChar = line.charAt(cur.ch), curChar = cur.ch > 0 ? line.charAt(cur.ch - 1) : ""; + if (left == right && CodeMirror.isWordChar(curChar)) + return CodeMirror.Pass; if (line.length == cur.ch || closingBrackets.indexOf(nextChar) >= 0 || SPACE_CHAR_REGEX.test(nextChar)) cm.replaceSelection(left + right, {head: ahead, anchor: ahead}); else diff --git a/lib/codemirror.js b/lib/codemirror.js index 4e3a07eccb..aed7e83b13 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3319,6 +3319,10 @@ window.CodeMirror = (function() { helpers[type][name] = value; }; + // UTILITIES + + CodeMirror.isWordChar = isWordChar; + // MODE STATE HANDLING // Utility functions for working with state. Exported because modes From 2e3cce0cd6aea4093ad72daa8e6b537f3cefe635 Mon Sep 17 00:00:00 2001 From: Leonya Khachaturov Date: Fri, 12 Jul 2013 12:18:19 +0200 Subject: [PATCH 073/110] [css mode] Add several non-standard but very common CSS properties --- mode/css/css.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mode/css/css.js b/mode/css/css.js index b52e787a70..70a55d30d4 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -386,15 +386,15 @@ CodeMirror.defineMode("css-base", function(config, parserConfig) { "text-decoration-color", "text-decoration-line", "text-decoration-skip", "text-decoration-style", "text-emphasis", "text-emphasis-color", "text-emphasis-position", "text-emphasis-style", "text-height", - "text-indent", "text-justify", "text-outline", "text-shadow", - "text-space-collapse", "text-transform", "text-underline-position", + "text-indent", "text-justify", "text-outline", "text-overflow", "text-shadow", + "text-size-adjust", "text-space-collapse", "text-transform", "text-underline-position", "text-wrap", "top", "transform", "transform-origin", "transform-style", "transition", "transition-delay", "transition-duration", "transition-property", "transition-timing-function", "unicode-bidi", "vertical-align", "visibility", "voice-balance", "voice-duration", "voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress", "voice-volume", "volume", "white-space", "widows", "width", "word-break", - "word-spacing", "word-wrap", "z-index", + "word-spacing", "word-wrap", "z-index", "zoom", // SVG-specific "clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color", "flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events", From 36c356cc6166775cbc89756165465172ee423228 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 25 Jul 2013 08:02:41 +0200 Subject: [PATCH 074/110] Remove trailing whitespace --- mode/css/css.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/css/css.js b/mode/css/css.js index 70a55d30d4..a81fa40408 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -386,7 +386,7 @@ CodeMirror.defineMode("css-base", function(config, parserConfig) { "text-decoration-color", "text-decoration-line", "text-decoration-skip", "text-decoration-style", "text-emphasis", "text-emphasis-color", "text-emphasis-position", "text-emphasis-style", "text-height", - "text-indent", "text-justify", "text-outline", "text-overflow", "text-shadow", + "text-indent", "text-justify", "text-outline", "text-overflow", "text-shadow", "text-size-adjust", "text-space-collapse", "text-transform", "text-underline-position", "text-wrap", "top", "transform", "transform-origin", "transform-style", "transition", "transition-delay", "transition-duration", From 0ca2c2546475945dca9dacfd93de8cee3a6e5492 Mon Sep 17 00:00:00 2001 From: "Jan T. Sott" Date: Mon, 15 Jul 2013 10:36:47 +0200 Subject: [PATCH 075/110] [base16 themes] Add --- theme/3024-day.css | 33 +++++++++++++++++++++++++++++++++ theme/3024-night.css | 33 +++++++++++++++++++++++++++++++++ theme/base16-dark.css | 33 +++++++++++++++++++++++++++++++++ theme/base16-light.css | 33 +++++++++++++++++++++++++++++++++ theme/tomorrow-night-eighties.css | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 165 insertions(+) create mode 100644 theme/3024-day.css create mode 100644 theme/3024-night.css create mode 100644 theme/base16-dark.css create mode 100644 theme/base16-light.css create mode 100644 theme/tomorrow-night-eighties.css diff --git a/theme/3024-day.css b/theme/3024-day.css new file mode 100644 index 0000000000..e931a49de4 --- /dev/null +++ b/theme/3024-day.css @@ -0,0 +1,33 @@ +/* + + Name: 3024 day + Author: Jan T. Sott (http://github.com/idleberg) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-3024-day.CodeMirror {background: #f7f7f7; color: #3a3432;} +.cm-s-3024-day div.CodeMirror-selected {background: #d6d5d4 !important;} +.cm-s-3024-day .CodeMirror-gutters {background: #f7f7f7; border-right: 0px;} +.cm-s-3024-day .CodeMirror-linenumber {color: #807d7c;} +.cm-s-3024-day .CodeMirror-cursor {border-left: 1px solid #5c5855 !important;} + +.cm-s-3024-day span.cm-comment {color: #cdab53;} +.cm-s-3024-day span.cm-atom {color: #a16a94;} +.cm-s-3024-day span.cm-number {color: #a16a94;} + +.cm-s-3024-day span.cm-property, .cm-s-3024-day span.cm-attribute {color: #01a252;} +.cm-s-3024-day span.cm-keyword {color: #db2d20;} +.cm-s-3024-day span.cm-string {color: #fded02;} + +.cm-s-3024-day span.cm-variable {color: #01a252;} +.cm-s-3024-day span.cm-variable-2 {color: #01a0e4;} +.cm-s-3024-day span.cm-def {color: #e8bbd0;} +.cm-s-3024-day span.cm-error {background: #db2d20; color: #5c5855;} +.cm-s-3024-day span.cm-bracket {color: #3a3432;} +.cm-s-3024-day span.cm-tag {color: #db2d20;} +.cm-s-3024-day span.cm-link {color: #a16a94;} + +.cm-s-3024-day .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} diff --git a/theme/3024-night.css b/theme/3024-night.css new file mode 100644 index 0000000000..87c7f8abe0 --- /dev/null +++ b/theme/3024-night.css @@ -0,0 +1,33 @@ +/* + + Name: 3024 night + Author: Jan T. Sott (http://github.com/idleberg) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-3024-night.CodeMirror {background: #090300; color: #d6d5d4;} +.cm-s-3024-night div.CodeMirror-selected {background: #3a3432 !important;} +.cm-s-3024-night .CodeMirror-gutters {background: #090300; border-right: 0px;} +.cm-s-3024-night .CodeMirror-linenumber {color: #5c5855;} +.cm-s-3024-night .CodeMirror-cursor {border-left: 1px solid #807d7c !important;} + +.cm-s-3024-night span.cm-comment {color: #cdab53;} +.cm-s-3024-night span.cm-atom {color: #a16a94;} +.cm-s-3024-night span.cm-number {color: #a16a94;} + +.cm-s-3024-night span.cm-property, .cm-s-3024-night span.cm-attribute {color: #01a252;} +.cm-s-3024-night span.cm-keyword {color: #db2d20;} +.cm-s-3024-night span.cm-string {color: #fded02;} + +.cm-s-3024-night span.cm-variable {color: #01a252;} +.cm-s-3024-night span.cm-variable-2 {color: #01a0e4;} +.cm-s-3024-night span.cm-def {color: #e8bbd0;} +.cm-s-3024-night span.cm-error {background: #db2d20; color: #807d7c;} +.cm-s-3024-night span.cm-bracket {color: #d6d5d4;} +.cm-s-3024-night span.cm-tag {color: #db2d20;} +.cm-s-3024-night span.cm-link {color: #a16a94;} + +.cm-s-3024-night .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} diff --git a/theme/base16-dark.css b/theme/base16-dark.css new file mode 100644 index 0000000000..7a818b75f5 --- /dev/null +++ b/theme/base16-dark.css @@ -0,0 +1,33 @@ +/* + + Name: Base16 Default Dark + Author: Chris Kempson (http://chriskempson.com) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-chrome-devtools) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-base16-dark.CodeMirror {background: #151515; color: #e0e0e0;} +.cm-s-base16-dark div.CodeMirror-selected {background: #202020 !important;} +.cm-s-base16-dark .CodeMirror-gutters {background: #151515; border-right: 0px;} +.cm-s-base16-dark .CodeMirror-linenumber {color: #505050;} +.cm-s-base16-dark .CodeMirror-cursor {border-left: 1px solid #b0b0b0 !important;} + +.cm-s-base16-dark span.cm-comment {color: #8f5536;} +.cm-s-base16-dark span.cm-atom {color: #aa759f;} +.cm-s-base16-dark span.cm-number {color: #aa759f;} + +.cm-s-base16-dark span.cm-property, .cm-s-base16-dark span.cm-attribute {color: #90a959;} +.cm-s-base16-dark span.cm-keyword {color: #ac4142;} +.cm-s-base16-dark span.cm-string {color: #f4bf75;} + +.cm-s-base16-dark span.cm-variable {color: #90a959;} +.cm-s-base16-dark span.cm-variable-2 {color: #6a9fb5;} +.cm-s-base16-dark span.cm-def {color: #d28445;} +.cm-s-base16-dark span.cm-error {background: #ac4142; color: #b0b0b0;} +.cm-s-base16-dark span.cm-bracket {color: #e0e0e0;} +.cm-s-base16-dark span.cm-tag {color: #ac4142;} +.cm-s-base16-dark span.cm-link {color: #aa759f;} + +.cm-s-base16-dark .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} diff --git a/theme/base16-light.css b/theme/base16-light.css new file mode 100644 index 0000000000..a3b2d4dcba --- /dev/null +++ b/theme/base16-light.css @@ -0,0 +1,33 @@ +/* + + Name: Base16 Default Light + Author: Chris Kempson (http://chriskempson.com) + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-chrome-devtools) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-base16-light.CodeMirror {background: #f5f5f5; color: #202020;} +.cm-s-base16-light div.CodeMirror-selected {background: #e0e0e0 !important;} +.cm-s-base16-light .CodeMirror-gutters {background: #f5f5f5; border-right: 0px;} +.cm-s-base16-light .CodeMirror-linenumber {color: #b0b0b0;} +.cm-s-base16-light .CodeMirror-cursor {border-left: 1px solid #505050 !important;} + +.cm-s-base16-light span.cm-comment {color: #8f5536;} +.cm-s-base16-light span.cm-atom {color: #aa759f;} +.cm-s-base16-light span.cm-number {color: #aa759f;} + +.cm-s-base16-light span.cm-property, .cm-s-base16-light span.cm-attribute {color: #90a959;} +.cm-s-base16-light span.cm-keyword {color: #ac4142;} +.cm-s-base16-light span.cm-string {color: #f4bf75;} + +.cm-s-base16-light span.cm-variable {color: #90a959;} +.cm-s-base16-light span.cm-variable-2 {color: #6a9fb5;} +.cm-s-base16-light span.cm-def {color: #d28445;} +.cm-s-base16-light span.cm-error {background: #ac4142; color: #505050;} +.cm-s-base16-light span.cm-bracket {color: #202020;} +.cm-s-base16-light span.cm-tag {color: #ac4142;} +.cm-s-base16-light span.cm-link {color: #aa759f;} + +.cm-s-base16-light .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} diff --git a/theme/tomorrow-night-eighties.css b/theme/tomorrow-night-eighties.css new file mode 100644 index 0000000000..3aa84cc829 --- /dev/null +++ b/theme/tomorrow-night-eighties.css @@ -0,0 +1,33 @@ +/* + + Name: Tomorrow Night - Eighties + Author: Chris Kempson + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-tomorrownight-eighties-night.CodeMirror {background: #000000; color: #CCCCCC;} +.cm-s-tomorrownight-eighties-night div.CodeMirror-selected {background: #2D2D2D !important;} +.cm-s-tomorrownight-eighties-night .CodeMirror-gutters {background: #000000; border-right: 0px;} +.cm-s-tomorrownight-eighties-night .CodeMirror-linenumber {color: #515151;} +.cm-s-tomorrownight-eighties-night .CodeMirror-cursor {border-left: 1px solid #6A6A6A !important;} + +.cm-s-tomorrownight-eighties-night span.cm-comment {color: #d27b53;} +.cm-s-tomorrownight-eighties-night span.cm-atom {color: #a16a94;} +.cm-s-tomorrownight-eighties-night span.cm-number {color: #a16a94;} + +.cm-s-tomorrownight-eighties-night span.cm-property, .cm-s-tomorrownight-eighties-night span.cm-attribute {color: #99cc99;} +.cm-s-tomorrownight-eighties-night span.cm-keyword {color: #f2777a;} +.cm-s-tomorrownight-eighties-night span.cm-string {color: #ffcc66;} + +.cm-s-tomorrownight-eighties-night span.cm-variable {color: #99cc99;} +.cm-s-tomorrownight-eighties-night span.cm-variable-2 {color: #6699cc;} +.cm-s-tomorrownight-eighties-night span.cm-def {color: #f99157;} +.cm-s-tomorrownight-eighties-night span.cm-error {background: #f2777a; color: #6A6A6A;} +.cm-s-tomorrownight-eighties-night span.cm-bracket {color: #CCCCCC;} +.cm-s-tomorrownight-eighties-night span.cm-tag {color: #f2777a;} +.cm-s-tomorrownight-eighties-night span.cm-link {color: #a16a94;} + +.cm-s-tomorrownight-eighties-night .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} From 23c845ce7cd3f13cf660caa934acd12b7e70b86c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 25 Jul 2013 08:13:13 +0200 Subject: [PATCH 076/110] [base16 themes] Integrate --- demo/theme.html | 10 +++++++++ theme/tomorrow-night-eighties.css | 46 +++++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/demo/theme.html b/demo/theme.html index 147a89ff14..62544be1a7 100644 --- a/demo/theme.html +++ b/demo/theme.html @@ -22,6 +22,11 @@ + + + + + @@ -49,7 +54,11 @@

    Select a theme: + + +

    MIME types defined: text/nginx.

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

    CodeMirror: NGINX mode