diff --git a/AUTHORS b/AUTHORS index a4f2b94b79..b9bf72103c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -25,6 +25,7 @@ Alexandre Bique alexey-k Alex Piggott Aliaksei Chapyzhenka +Amin Shali Amsul amuntean Amy @@ -81,10 +82,13 @@ Cheah Chu Yeow Chris Coyier Chris Granger Chris Houseknecht +Chris Lohfink Chris Morgan Christian Oyarzun Christian Petrov Christopher Brown +Christopher Mitchell +Christopher Pfohl ciaranj CodeAnimal coderaiser @@ -106,6 +110,7 @@ Danny Yoo darealshinji Darius Roberts Dave Myers +David Barnett David Mignot David Pathakjee David Vázquez @@ -182,6 +187,7 @@ ilvalle Ingo Richter Irakli Gozalishvili Ivan Kurnosov +Ivoah Jacob Lee Jakob Miland Jakub Vrana @@ -213,6 +219,7 @@ John Connor John Lees-Miller John Snelson John Van Der Loo +Jonas Döbertin Jonathan Malmaud jongalloway Jon Malmaud @@ -223,6 +230,7 @@ Joshua Newman Josh Watzman jots jsoojeon +ju1ius Juan Benavides Romero Jucovschi Constantin Juho Vuori @@ -250,6 +258,8 @@ Leonid Khachaturov Leon Sorokin Leonya Khachaturov Liam Newman +Libo Cannici +LloydMilligan LM lochel Lorenzo Stoakes @@ -274,6 +284,7 @@ Marko Bonaci Martin Balek Martín Gaitán Martin Hasoň +Martin Hunt Mason Malone Mateusz Paprocki Mathias Bynens @@ -292,6 +303,7 @@ Max Xiantu mbarkhau Metatheos Micah Dubinko +Michael Grey Michael Lehenbauer Michael Zhou Mighty Guava @@ -308,6 +320,7 @@ misfo mloginov Moritz Schwörer mps +ms mtaran-google Narciso Jaramillo Nathan Williams @@ -319,6 +332,7 @@ nguillaumin Ng Zhi An Nicholas Bollweg Nicholas Bollweg (Nick) +Nick Kreeger Nick Small Niels van Groningen nightwing @@ -333,6 +347,7 @@ pablo Page Panupong Pasupat paris +Paris Patil Arpith Patrick Stoica Patrick Strawderman diff --git a/addon/display/panel.js b/addon/display/panel.js index 22c0453e8f..ba29484d6c 100644 --- a/addon/display/panel.js +++ b/addon/display/panel.js @@ -10,13 +10,31 @@ mod(CodeMirror); })(function(CodeMirror) { CodeMirror.defineExtension("addPanel", function(node, options) { + options = options || {}; + if (!this.state.panels) initPanels(this); var info = this.state.panels; - if (options && options.position == "bottom") - info.wrapper.appendChild(node); - else - info.wrapper.insertBefore(node, info.wrapper.firstChild); + var wrapper = info.wrapper; + var cmWrapper = this.getWrapperElement(); + + if (options.after instanceof Panel && !options.after.cleared) { + wrapper.insertBefore(node, options.before.node.nextSibling); + } else if (options.before instanceof Panel && !options.before.cleared) { + wrapper.insertBefore(node, options.before.node); + } else if (options.replace instanceof Panel && !options.replace.cleared) { + wrapper.insertBefore(node, options.replace.node); + options.replace.clear(); + } else if (options.position == "bottom") { + wrapper.appendChild(node); + } else if (options.position == "before-bottom") { + wrapper.insertBefore(node, cmWrapper.nextSibling); + } else if (options.position == "after-top") { + wrapper.insertBefore(node, cmWrapper); + } else { + wrapper.insertBefore(node, wrapper.firstChild); + } + var height = (options && options.height) || node.offsetHeight; this._setSize(null, info.heightLeft -= height); info.panels++; diff --git a/addon/edit/continuelist.js b/addon/edit/continuelist.js index ca8d26751a..86de49c538 100644 --- a/addon/edit/continuelist.js +++ b/addon/edit/continuelist.js @@ -19,23 +19,23 @@ if (cm.getOption("disableInput")) return CodeMirror.Pass; var ranges = cm.listSelections(), replacements = []; for (var i = 0; i < ranges.length; i++) { - var pos = ranges[i].head, match; + var pos = ranges[i].head; var eolState = cm.getStateAfter(pos.line); var inList = eolState.list !== false; - var inQuote = eolState.quote !== false; + var inQuote = eolState.quote !== 0; - if (!ranges[i].empty() || (!inList && !inQuote) || !(match = cm.getLine(pos.line).match(listRE))) { + var line = cm.getLine(pos.line), match = listRE.exec(line); + if (!ranges[i].empty() || (!inList && !inQuote) || !match) { cm.execCommand("newlineAndIndent"); return; } - if (cm.getLine(pos.line).match(emptyListRE)) { + if (emptyListRE.test(line)) { cm.replaceRange("", { line: pos.line, ch: 0 }, { line: pos.line, ch: pos.ch + 1 }); replacements[i] = "\n"; - } else { var indent = match[1], after = match[4]; var bullet = unorderedListRE.test(match[2]) || match[2].indexOf(">") >= 0 diff --git a/addon/hint/show-hint.js b/addon/hint/show-hint.js index f544619428..539181fdb7 100644 --- a/addon/hint/show-hint.js +++ b/addon/hint/show-hint.js @@ -24,44 +24,44 @@ return cm.showHint(newOpts); }; - var asyncRunID = 0; - function retrieveHints(getter, cm, options, then) { - if (getter.async) { - var id = ++asyncRunID; - getter(cm, function(hints) { - if (asyncRunID == id) then(hints); - }, options); - } else { - then(getter(cm, options)); - } - } - CodeMirror.defineExtension("showHint", function(options) { // We want a single cursor position. if (this.listSelections().length > 1 || this.somethingSelected()) return; if (this.state.completionActive) this.state.completionActive.close(); var completion = this.state.completionActive = new Completion(this, options); - var getHints = completion.options.hint; - if (!getHints) return; + if (!completion.options.hint) return; CodeMirror.signal(this, "startCompletion", this); - return retrieveHints(getHints, this, completion.options, function(hints) { completion.showHints(hints); }); + completion.update(); }); function Completion(cm, options) { this.cm = cm; this.options = this.buildOptions(options); - this.widget = this.onClose = null; + this.widget = null; + this.debounce = 0; + this.tick = 0; + this.startPos = this.cm.getCursor(); + this.startLen = this.cm.getLine(this.startPos.line).length; + + var self = this; + cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); }); } + var requestAnimationFrame = window.requestAnimationFrame || function(fn) { + return setTimeout(fn, 1000/60); + }; + var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout; + Completion.prototype = { close: function() { if (!this.active()) return; this.cm.state.completionActive = null; + this.tick = null; + this.cm.off("cursorActivity", this.activityFunc); if (this.widget) this.widget.close(); - if (this.onClose) this.onClose(); CodeMirror.signal(this.cm, "endCompletion", this.cm); }, @@ -87,61 +87,51 @@ this.showWidget(data); }, - showWidget: function(data) { - this.widget = new Widget(this, data); - CodeMirror.signal(data, "shown"); - - var debounce = 0, completion = this, finished; - var closeOn = this.options.closeCharacters; - var startPos = this.cm.getCursor(), startLen = this.cm.getLine(startPos.line).length; - - var requestAnimationFrame = window.requestAnimationFrame || function(fn) { - return setTimeout(fn, 1000/60); - }; - var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout; - - function done() { - if (finished) return; - finished = true; - completion.close(); - completion.cm.off("cursorActivity", activity); - if (data) CodeMirror.signal(data, "close"); + cursorActivity: function() { + if (this.debounce) { + cancelAnimationFrame(this.debounce); + this.debounce = 0; } - function update() { - if (finished) return; - CodeMirror.signal(data, "update"); - retrieveHints(completion.options.hint, completion.cm, completion.options, finishUpdate); - } - function finishUpdate(data_) { - data = data_; - if (finished) return; - if (!data || !data.list.length) return done(); - if (completion.widget) completion.widget.close(); - completion.widget = new Widget(completion, data); + var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line); + if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch || + pos.ch < this.startPos.ch || this.cm.somethingSelected() || + (pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) { + this.close(); + } else { + var self = this; + this.debounce = requestAnimationFrame(function() {self.update();}); + if (this.widget) this.widget.disable(); } + }, - function clearDebounce() { - if (debounce) { - cancelAnimationFrame(debounce); - debounce = 0; - } + update: function() { + if (this.tick == null) return; + if (this.data) CodeMirror.signal(this.data, "update"); + if (!this.options.hint.async) { + this.finishUpdate(this.options.hint(this.cm, this.options), myTick); + } else { + var myTick = ++this.tick, self = this; + this.options.hint(this.cm, function(data) { + if (self.tick == myTick) self.finishUpdate(data); + }, this.options); } + }, - function activity() { - clearDebounce(); - var pos = completion.cm.getCursor(), line = completion.cm.getLine(pos.line); - if (pos.line != startPos.line || line.length - pos.ch != startLen - startPos.ch || - pos.ch < startPos.ch || completion.cm.somethingSelected() || - (pos.ch && closeOn.test(line.charAt(pos.ch - 1)))) { - completion.close(); - } else { - debounce = requestAnimationFrame(update); - if (completion.widget) completion.widget.close(); - } + finishUpdate: function(data) { + this.data = data; + var picked = this.widget && this.widget.picked; + if (this.widget) this.widget.close(); + if (data && data.list.length) { + if (picked && data.list.length == 1) this.pick(data, 0); + else this.widget = new Widget(this, data); } - this.cm.on("cursorActivity", activity); - this.onClose = done; + }, + + showWidget: function(data) { + this.data = data; + this.widget = new Widget(this, data); + CodeMirror.signal(data, "shown"); }, buildOptions: function(options) { @@ -206,6 +196,7 @@ function Widget(completion, data) { this.completion = completion; this.data = data; + this.picked = false; var widget = this, cm = completion.cm; var hints = this.hints = document.createElement("ul"); @@ -320,6 +311,13 @@ cm.off("scroll", this.onScroll); }, + disable: function() { + this.completion.cm.removeKeyMap(this.keyMap); + var widget = this; + this.keyMap = {Enter: function() { widget.picked = true; }}; + this.completion.cm.addKeyMap(this.keyMap); + }, + pick: function() { this.completion.pick(this.data, this.selectedHint); }, diff --git a/addon/lint/javascript-lint.js b/addon/lint/javascript-lint.js index 3d65ba695b..d4f2ae9a1f 100644 --- a/addon/lint/javascript-lint.js +++ b/addon/lint/javascript-lint.js @@ -23,7 +23,7 @@ function validator(text, options) { if (!window.JSHINT) return []; - JSHINT(text, options); + JSHINT(text, options, options.globals); var errors = JSHINT.data().errors, result = []; if (errors) parseErrors(errors, result); return result; diff --git a/addon/mode/multiplex.js b/addon/mode/multiplex.js index 6a95b323c1..fe48c7fbd5 100644 --- a/addon/mode/multiplex.js +++ b/addon/mode/multiplex.js @@ -14,12 +14,14 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { // Others should be {open, close, mode [, delimStyle] [, innerStyle]} objects var others = Array.prototype.slice.call(arguments, 1); - var n_others = others.length; - function indexOf(string, pattern, from) { - if (typeof pattern == "string") return string.indexOf(pattern, from); + function indexOf(string, pattern, from, returnEnd) { + if (typeof pattern == "string") { + var found = string.indexOf(pattern, from); + return returnEnd && found > -1 ? found + pattern.length : found; + } var m = pattern.exec(from ? string.slice(from) : string); - return m ? m.index + from : -1; + return m ? m.index + from + (returnEnd ? m[0].length : 0) : -1; } return { @@ -42,11 +44,11 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { token: function(stream, state) { if (!state.innerActive) { var cutOff = Infinity, oldContent = stream.string; - for (var i = 0; i < n_others; ++i) { + for (var i = 0; i < others.length; ++i) { var other = others[i]; var found = indexOf(oldContent, other.open, stream.pos); if (found == stream.pos) { - stream.match(other.open); + if (!other.parseDelimiters) stream.match(other.open); state.innerActive = other; state.inner = CodeMirror.startState(other.mode, outer.indent ? outer.indent(state.outer, "") : 0); return other.delimStyle; @@ -64,8 +66,8 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { state.innerActive = state.inner = null; return this.token(stream, state); } - var found = curInner.close ? indexOf(oldContent, curInner.close, stream.pos) : -1; - if (found == stream.pos) { + var found = curInner.close ? indexOf(oldContent, curInner.close, stream.pos, curInner.parseDelimiters) : -1; + if (found == stream.pos && !curInner.parseDelimiters) { stream.match(curInner.close); state.innerActive = state.inner = null; return curInner.delimStyle; @@ -74,6 +76,9 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { var innerToken = curInner.mode.token(stream, state.inner); if (found > -1) stream.string = oldContent; + if (found == stream.pos && curInner.parseDelimiters) + state.innerActive = state.inner = null; + if (curInner.innerStyle) { if (innerToken) innerToken = innerToken + ' ' + curInner.innerStyle; else innerToken = curInner.innerStyle; @@ -95,7 +100,7 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { mode.blankLine(state.innerActive ? state.inner : state.outer); } if (!state.innerActive) { - for (var i = 0; i < n_others; ++i) { + for (var i = 0; i < others.length; ++i) { var other = others[i]; if (other.open === "\n") { state.innerActive = other; diff --git a/addon/scroll/annotatescrollbar.js b/addon/scroll/annotatescrollbar.js index 54aeacf271..e62a45acf2 100644 --- a/addon/scroll/annotatescrollbar.js +++ b/addon/scroll/annotatescrollbar.js @@ -68,15 +68,30 @@ var cm = this.cm, hScale = this.hScale; var frag = document.createDocumentFragment(), anns = this.annotations; + + var wrapping = cm.getOption("lineWrapping"); + var singleLineH = wrapping && cm.defaultTextHeight() * 1.5; + var curLine = null, curLineObj = null; + function getY(pos, top) { + if (curLine != pos.line) { + curLine = pos.line; + curLineObj = cm.getLineHandle(curLine); + } + if (wrapping && curLineObj.height > singleLineH) + return cm.charCoords(pos, "local")[top ? "top" : "bottom"]; + var topY = cm.heightAtLine(curLineObj, "local"); + return topY + (top ? 0 : curLineObj.height); + } + if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) { var ann = anns[i]; - var top = nextTop || cm.charCoords(ann.from, "local").top * hScale; - var bottom = cm.charCoords(ann.to, "local").bottom * hScale; + var top = nextTop || getY(ann.from, true) * hScale; + var bottom = getY(ann.to, false) * hScale; while (i < anns.length - 1) { - nextTop = cm.charCoords(anns[i + 1].from, "local").top * hScale; + nextTop = getY(anns[i + 1].from, true) * hScale; if (nextTop > bottom + .9) break; ann = anns[++i]; - bottom = cm.charCoords(ann.to, "local").bottom * hScale; + bottom = getY(ann.to, false) * hScale; } if (bottom == top) continue; var height = Math.max(bottom - top, 3); diff --git a/addon/search/matchesonscrollbar.js b/addon/search/matchesonscrollbar.js index dbd67a4a5d..8d19228971 100644 --- a/addon/search/matchesonscrollbar.js +++ b/addon/search/matchesonscrollbar.js @@ -19,6 +19,7 @@ function SearchAnnotation(cm, query, caseFold, options) { this.cm = cm; + this.options = options; var annotateOptions = {listenForChanges: false}; for (var prop in options) annotateOptions[prop] = options[prop]; if (!annotateOptions.className) annotateOptions.className = "CodeMirror-search-match"; @@ -46,11 +47,12 @@ if (match.to.line >= this.gap.from) this.matches.splice(i--, 1); } var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), this.caseFold); + var maxMatches = this.options && this.options.maxMatches || MAX_MATCHES; while (cursor.findNext()) { var match = {from: cursor.from(), to: cursor.to()}; if (match.from.line >= this.gap.to) break; this.matches.splice(i++, 0, match); - if (this.matches.length > MAX_MATCHES) break; + if (this.matches.length > maxMatches) break; } this.gap = null; }; diff --git a/addon/search/search.js b/addon/search/search.js index 0251067a73..1c8ca3c245 100644 --- a/addon/search/search.js +++ b/addon/search/search.js @@ -39,7 +39,7 @@ } function SearchState() { - this.posFrom = this.posTo = this.query = null; + this.posFrom = this.posTo = this.lastQuery = this.query = null; this.overlay = null; } function getSearchState(cm) { @@ -53,7 +53,7 @@ return cm.getSearchCursor(query, pos, queryCaseInsensitive(query)); } function dialog(cm, text, shortText, deflt, f) { - if (cm.openDialog) cm.openDialog(text, f, {value: deflt}); + if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true}); else f(prompt(shortText, deflt)); } function confirmDialog(cm, text, shortText, fs) { @@ -75,7 +75,8 @@ function doSearch(cm, rev) { var state = getSearchState(cm); if (state.query) return findNext(cm, rev); - dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) { + var q = cm.getSelection() || state.lastQuery; + dialog(cm, queryDialog, "Search for:", q, function(query) { cm.operation(function() { if (!query || state.query) return; state.query = parseQuery(query); @@ -104,6 +105,7 @@ });} function clearSearch(cm) {cm.operation(function() { var state = getSearchState(cm); + state.lastQuery = state.query; if (!state.query) return; state.query = null; cm.removeOverlay(state.overlay); @@ -116,7 +118,8 @@ var doReplaceConfirm = "Replace? "; function replace(cm, all) { if (cm.getOption("readOnly")) return; - dialog(cm, replaceQueryDialog, "Replace:", cm.getSelection(), function(query) { + var query = cm.getSelection() || getSearchState().lastQuery; + dialog(cm, replaceQueryDialog, "Replace:", query, function(query) { if (!query) return; query = parseQuery(query); dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) { diff --git a/addon/search/searchcursor.js b/addon/search/searchcursor.js index 55c108b5a3..97088bf1cf 100644 --- a/addon/search/searchcursor.js +++ b/addon/search/searchcursor.js @@ -148,10 +148,10 @@ from: function() {if (this.atOccurrence) return this.pos.from;}, to: function() {if (this.atOccurrence) return this.pos.to;}, - replace: function(newText) { + replace: function(newText, origin) { if (!this.atOccurrence) return; var lines = CodeMirror.splitLines(newText); - this.doc.replaceRange(lines, this.pos.from, this.pos.to); + this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin); this.pos.to = Pos(this.pos.from.line + lines.length - 1, lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0)); } diff --git a/bower.json b/bower.json index 334447e56d..84808c8bc3 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version":"5.1.0", + "version":"5.2.0", "main": ["lib/codemirror.js", "lib/codemirror.css"], "ignore": [ "**/.*", @@ -11,6 +11,8 @@ "doc", "test", "index.html", - "package.json" + "package.json", + "mode/*/*test.js", + "mode/*/*.html" ] } diff --git a/demo/panel.html b/demo/panel.html index 7f4bbefca6..b3b0b7ca6b 100644 --- a/demo/panel.html +++ b/demo/panel.html @@ -7,17 +7,36 @@ + +
+

Panel Demo

-
- - -

The panel -addon allows you to display panels above or below an editor. Click the -links in the previous paragraph to add panels to the editor.

+ close = node.appendChild(document.createElement("a")); + close.setAttribute("title", "Remove me!"); + close.setAttribute("class", "remove-panel"); + close.textContent = "✖"; + CodeMirror.on(close, "click", function() { + panels[node.id].clear(); + }); + label = node.appendChild(document.createElement("span")); + label.textContent = "I'm panel n°" + id; + return node; +} +function addPanel(where) { + var node = makePanel(where); + panels[node.id] = editor.addPanel(node, {position: where}); +} + +addPanel("top"); +addPanel("bottom"); + +function replacePanel(form) { + var id = form.elements.panel_id.value; + var panel = panels["panel-" + id]; + var node = makePanel(""); + + panels[node.id] = editor.addPanel(node, {replace: panel, position: "after-top"}); + return false; +} + + +
diff --git a/demo/tern.html b/demo/tern.html index 19135faf7f..d5badfef6e 100644 --- a/demo/tern.html +++ b/demo/tern.html @@ -13,9 +13,9 @@ - - - + + + diff --git a/demo/theme.html b/demo/theme.html index 22b59b7fdc..b3e31b5629 100644 --- a/demo/theme.html +++ b/demo/theme.html @@ -17,6 +17,7 @@ + @@ -87,6 +88,7 @@ + diff --git a/doc/compress.html b/doc/compress.html index 65df6483f5..581bacf07f 100644 --- a/doc/compress.html +++ b/doc/compress.html @@ -36,6 +36,7 @@

Version: @@ -52,8 +61,9 @@ var editor = CodeMirror.fromTextArea(document.getElementById("code"), { lineNumbers: true, mode: "django", - indentUnit: 4, - indentWithTabs: true + indentUnit: 2, + indentWithTabs: true, + theme: "mdn-like" }); diff --git a/mode/handlebars/handlebars.js b/mode/handlebars/handlebars.js new file mode 100644 index 0000000000..40dfea42af --- /dev/null +++ b/mode/handlebars/handlebars.js @@ -0,0 +1,53 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../../addon/mode/simple")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../../addon/mode/simple"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineSimpleMode("handlebars", { + start: [ + { regex: /\{\{!--/, push: "dash_comment", token: "comment" }, + { regex: /\{\{!/, push: "comment", token: "comment" }, + { regex: /\{\{/, push: "handlebars", token: "tag" } + ], + handlebars: [ + { regex: /\}\}/, pop: true, token: "tag" }, + + // Double and single quotes + { regex: /"(?:[^\\]|\\.)*?"/, token: "string" }, + { regex: /'(?:[^\\]|\\.)*?'/, token: "string" }, + + // Handlebars keywords + { regex: />|[#\/]([A-Za-z_]\w*)/, token: "keyword" }, + { regex: /(?:else|this)\b/, token: "keyword" }, + + // Numeral + { regex: /\d+/i, token: "number" }, + + // Atoms like = and . + { regex: /=|~|@|true|false/, token: "atom" }, + + // Paths + { regex: /(?:\.\.\/)*(?:[A-Za-z_][\w\.]*)+/, token: "variable-2" } + ], + dash_comment: [ + { regex: /--\}\}/, pop: true, token: "comment" }, + + // Commented code + { regex: /./, token: "comment"} + ], + comment: [ + { regex: /\}\}/, pop: true, token: "comment" }, + { regex: /./, token: "comment" } + ] + }); + + CodeMirror.defineMIME("text/x-handlebars-template", "handlebars"); +}); diff --git a/mode/handlebars/index.html b/mode/handlebars/index.html new file mode 100644 index 0000000000..beaef87a18 --- /dev/null +++ b/mode/handlebars/index.html @@ -0,0 +1,83 @@ + + +CodeMirror: Handlebars mode + + + + + + + + + + +

+ +
+

Handlebars

+
+ + + +

Handlebars syntax highlighting for CodeMirror.

+ +

MIME types defined: text/x-handlebars-template

+
diff --git a/mode/index.html b/mode/index.html index 7b62a33cf2..245595de4e 100644 --- a/mode/index.html +++ b/mode/index.html @@ -60,6 +60,7 @@
  • Go
  • Groovy
  • HAML
  • +
  • Handlebars
  • Haskell
  • Haxe
  • HTML mixed-mode
  • @@ -77,6 +78,7 @@
  • Markdown (GitHub-flavour)
  • mIRC
  • Modelica
  • +
  • MUMPS
  • Nginx
  • NTriples
  • Objective C
  • diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js index bee88997be..ef0184789a 100644 --- a/mode/javascript/javascript.js +++ b/mode/javascript/javascript.js @@ -585,7 +585,11 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { function importSpec(type, value) { if (type == "{") return contCommasep(importSpec, "}"); if (type == "variable") register(value); - return cont(); + if (value == "*") cx.marked = "keyword"; + return cont(maybeAs); + } + function maybeAs(_type, value) { + if (value == "as") { cx.marked = "keyword"; return cont(importSpec); } } function maybeFrom(_type, value) { if (value == "from") { cx.marked = "keyword"; return cont(expression); } diff --git a/mode/markdown/markdown.js b/mode/markdown/markdown.js index 3c80311068..bc5314fc35 100644 --- a/mode/markdown/markdown.js +++ b/mode/markdown/markdown.js @@ -72,7 +72,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { , ulRE = /^[*\-+]\s+/ , olRE = /^[0-9]+\.\s+/ , taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE - , atxHeaderRE = /^#+/ + , atxHeaderRE = /^#+ ?/ , setextHeaderRE = /^(?:\={1,}|-{1,})$/ , textRE = /^[^#!\[\]*_\\<>` "'(~]+/; @@ -116,18 +116,20 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { var sol = stream.sol(); - var prevLineIsList = (state.list !== false); - if (state.list !== false && state.indentationDiff >= 0) { // Continued list - if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block - state.indentation -= state.indentationDiff; + var prevLineIsList = state.list !== false; + if (prevLineIsList) { + if (state.indentationDiff >= 0) { // Continued list + if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block + state.indentation -= state.indentationDiff; + } + state.list = null; + } else if (state.indentation > 0) { + state.list = null; + state.listDepth = Math.floor(state.indentation / 4); + } else { // No longer a list + state.list = false; + state.listDepth = 0; } - state.list = null; - } else if (state.list !== false && state.indentation > 0) { - state.list = null; - state.listDepth = Math.floor(state.indentation / 4); - } else if (state.list !== false) { // No longer a list - state.list = false; - state.listDepth = 0; } var match = null; @@ -138,7 +140,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { } else if (stream.eatSpace()) { return null; } else if (match = stream.match(atxHeaderRE)) { - state.header = match[0].length <= 6 ? match[0].length : 6; + state.header = Math.min(6, match[0].indexOf(" ") !== -1 ? match[0].length - 1 : match[0].length); if (modeCfg.highlightFormatting) state.formatting = "header"; state.f = state.inline; return getType(state); diff --git a/mode/markdown/test.js b/mode/markdown/test.js index 96ca1aefc7..7bc687f0e6 100644 --- a/mode/markdown/test.js +++ b/mode/markdown/test.js @@ -26,7 +26,7 @@ "[comment&formatting&formatting-code ``][comment foo ` bar][comment&formatting&formatting-code ``]"); FT("formatting_atxHeader", - "[header&header-1&formatting&formatting-header&formatting-header-1 #][header&header-1 foo # bar ][header&header-1&formatting&formatting-header&formatting-header-1 #]"); + "[header&header-1&formatting&formatting-header&formatting-header-1 # ][header&header-1 foo # bar ][header&header-1&formatting&formatting-header&formatting-header-1 #]"); FT("formatting_setextHeader", "foo", diff --git a/mode/meta.js b/mode/meta.js index d138809179..6c988e226d 100644 --- a/mode/meta.js +++ b/mode/meta.js @@ -73,6 +73,7 @@ {name: "mIRC", mime: "text/mirc", mode: "mirc"}, {name: "MariaDB SQL", mime: "text/x-mariadb", mode: "sql"}, {name: "Modelica", mime: "text/x-modelica", mode: "modelica", ext: ["mo"]}, + {name: "MUMPS", mime: "text/x-mumps", mode: "mumps"}, {name: "MS SQL", mime: "text/x-mssql", mode: "sql"}, {name: "MySQL", mime: "text/x-mysql", mode: "sql"}, {name: "Nginx", mime: "text/x-nginx-conf", mode: "nginx", file: /nginx.*\.conf$/i}, diff --git a/mode/mumps/index.html b/mode/mumps/index.html new file mode 100644 index 0000000000..bd1f69aef5 --- /dev/null +++ b/mode/mumps/index.html @@ -0,0 +1,85 @@ + + +CodeMirror: MUMPS mode + + + + + + + + + +
    +

    MUMPS mode

    + + +
    + + +
    diff --git a/mode/mumps/mumps.js b/mode/mumps/mumps.js new file mode 100644 index 0000000000..469f8c3d1c --- /dev/null +++ b/mode/mumps/mumps.js @@ -0,0 +1,148 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +/* + This MUMPS Language script was constructed using vbscript.js as a template. +*/ + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineMode("mumps", function() { + function wordRegexp(words) { + return new RegExp("^((" + words.join(")|(") + "))\\b", "i"); + } + + var singleOperators = new RegExp("^[\\+\\-\\*/&#!_?\\\\<>=\\'\\[\\]]"); + var doubleOperators = new RegExp("^(('=)|(<=)|(>=)|('>)|('<)|([[)|(]])|(^$))"); + var singleDelimiters = new RegExp("^[\\.,:]"); + var brackets = new RegExp("[()]"); + var identifiers = new RegExp("^[%A-Za-z][A-Za-z0-9]*"); + var commandKeywords = ["break","close","do","else","for","goto", "halt", "hang", "if", "job","kill","lock","merge","new","open", "quit", "read", "set", "tcommit", "trollback", "tstart", "use", "view", "write", "xecute", "b","c","d","e","f","g", "h", "i", "j","k","l","m","n","o", "q", "r", "s", "tc", "tro", "ts", "u", "v", "w", "x"]; + // The following list includes instrinsic functions _and_ special variables + var intrinsicFuncsWords = ["\\$ascii", "\\$char", "\\$data", "\\$ecode", "\\$estack", "\\$etrap", "\\$extract", "\\$find", "\\$fnumber", "\\$get", "\\$horolog", "\\$io", "\\$increment", "\\$job", "\\$justify", "\\$length", "\\$name", "\\$next", "\\$order", "\\$piece", "\\$qlength", "\\$qsubscript", "\\$query", "\\$quit", "\\$random", "\\$reverse", "\\$select", "\\$stack", "\\$test", "\\$text", "\\$translate", "\\$view", "\\$x", "\\$y", "\\$a", "\\$c", "\\$d", "\\$e", "\\$ec", "\\$es", "\\$et", "\\$f", "\\$fn", "\\$g", "\\$h", "\\$i", "\\$j", "\\$l", "\\$n", "\\$na", "\\$o", "\\$p", "\\$q", "\\$ql", "\\$qs", "\\$r", "\\$re", "\\$s", "\\$st", "\\$t", "\\$tr", "\\$v", "\\$z"]; + var intrinsicFuncs = wordRegexp(intrinsicFuncsWords); + var command = wordRegexp(commandKeywords); + + function tokenBase(stream, state) { + if (stream.sol()) { + state.label = true; + state.commandMode = 0; + } + + // The character has meaning in MUMPS. Ignoring consecutive + // spaces would interfere with interpreting whether the next non-space + // character belongs to the command or argument context. + + // Examine each character and update a mode variable whose interpretation is: + // >0 => command 0 => argument <0 => command post-conditional + var ch = stream.peek(); + + if (ch == " " || ch == "\t") { // Pre-process + state.label = false; + if (state.commandMode == 0) + state.commandMode = 1; + else if ((state.commandMode < 0) || (state.commandMode == 2)) + state.commandMode = 0; + } else if ((ch != ".") && (state.commandMode > 0)) { + if (ch == ":") + state.commandMode = -1; // SIS - Command post-conditional + else + state.commandMode = 2; + } + + // Do not color parameter list as line tag + if ((ch === "(") || (ch === "\u0009")) + state.label = false; + + // MUMPS comment starts with ";" + if (ch === ";") { + stream.skipToEnd(); + return "comment"; + } + + // Number Literals // SIS/RLM - MUMPS permits canonic number followed by concatenate operator + if (stream.match(/^[-+]?\d+(\.\d+)?([eE][-+]?\d+)?/)) + return "number"; + + // Handle Strings + if (ch == '"') { + if (stream.skipTo('"')) { + stream.next(); + return "string"; + } else { + stream.skipToEnd(); + return "error"; + } + } + + // Handle operators and Delimiters + if (stream.match(doubleOperators) || stream.match(singleOperators)) + return "operator"; + + // Prevents leading "." in DO block from falling through to error + if (stream.match(singleDelimiters)) + return null; + + if (brackets.test(ch)) { + stream.next(); + return "bracket"; + } + + if (state.commandMode > 0 && stream.match(command)) + return "variable-2"; + + if (stream.match(intrinsicFuncs)) + return "builtin"; + + if (stream.match(identifiers)) + return "variable"; + + // Detect dollar-sign when not a documented intrinsic function + // "^" may introduce a GVN or SSVN - Color same as function + if (ch === "$" || ch === "^") { + stream.next(); + return "builtin"; + } + + // MUMPS Indirection + if (ch === "@") { + stream.next(); + return "string-2"; + } + + if (/[\w%]/.test(ch)) { + stream.eatWhile(/[\w%]/); + return "variable"; + } + + // Handle non-detected items + stream.next(); + return "error"; + } + + return { + startState: function() { + return { + label: false, + commandMode: 0 + }; + }, + + token: function(stream, state) { + var style = tokenBase(stream, state); + if (state.label) return "tag"; + return style; + } + }; + }); + + CodeMirror.defineMIME("text/x-mumps", "mumps"); +}); diff --git a/mode/python/python.js b/mode/python/python.js index 979e849822..cdb58bbbb1 100644 --- a/mode/python/python.js +++ b/mode/python/python.js @@ -162,15 +162,13 @@ if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) return null; - if (stream.match(doubleOperators) - || stream.match(singleOperators) - || stream.match(wordOperators)) + if (stream.match(doubleOperators) || stream.match(singleOperators)) return "operator"; if (stream.match(singleDelimiters)) return null; - if (stream.match(keywords)) + if (stream.match(keywords) || stream.match(wordOperators)) return "keyword"; if (stream.match(builtins)) diff --git a/mode/smarty/smarty.js b/mode/smarty/smarty.js index 7eada8223e..6e0fbed422 100644 --- a/mode/smarty/smarty.js +++ b/mode/smarty/smarty.js @@ -47,10 +47,17 @@ } function tokenTop(stream, state) { - if (stream.match(leftDelimiter, true)) { + var string = stream.string; + for (var scan = stream.pos;;) { + var nextMatch = string.indexOf(leftDelimiter, scan); + scan = nextMatch + leftDelimiter.length; + if (nextMatch == -1 || !doesNotCount(stream, nextMatch + leftDelimiter.length)) break; + } + if (nextMatch == stream.pos) { + stream.match(leftDelimiter); if (stream.eat("*")) { return chain(stream, state, tokenBlock("comment", "*" + rightDelimiter)); - } else if (!doesNotCount(stream)) { + } else { state.depth++; state.tokenize = tokenSmarty; last = "startTag"; @@ -58,11 +65,9 @@ } } + if (nextMatch > -1) stream.string = string.slice(0, nextMatch); var token = baseMode.token(stream, state.base); - var text = stream.current(); - var found = text.indexOf(leftDelimiter); - if (found > -1 && !doesNotCount(stream, stream.start + found + 1)) - stream.backUp(text.length - found); + if (nextMatch > -1) stream.string = string; return token; } @@ -205,6 +210,12 @@ state.last = last; return style; }, + indent: function(state, text) { + if (state.tokenize == tokenTop && baseMode.indent) + return baseMode.indent(state.base, text); + else + return CodeMirror.Pass; + }, blockCommentStart: leftDelimiter + "*", blockCommentEnd: "*" + rightDelimiter }; diff --git a/mode/soy/soy.js b/mode/soy/soy.js index 7e81e8dd56..79bfc24dfd 100644 --- a/mode/soy/soy.js +++ b/mode/soy/soy.js @@ -96,7 +96,7 @@ else state.indent -= (stream.current() == "/}" || indentingTags.indexOf(state.tag) == -1 ? 2 : 1) * config.indentUnit; state.soyState.pop(); return "keyword"; - } else if (stream.match(/^(\w+)(?==)/)) { + } else if (stream.match(/^([\w?]+)(?==)/)) { if (stream.current() == "kind" && (match = stream.match(/^="([^"]+)/, false))) { var kind = match[1]; state.kind.push(kind); @@ -134,7 +134,7 @@ return "comment"; } else if (stream.match(stream.sol() ? /^\s*\/\/.*/ : /^\s+\/\/.*/)) { return "comment"; - } else if (stream.match(/^\{\$\w*/)) { + } else if (stream.match(/^\{\$[\w?]*/)) { state.indent += 2 * config.indentUnit; state.soyState.push("variable"); return "variable-2"; @@ -142,7 +142,7 @@ state.indent += config.indentUnit; state.soyState.push("literal"); return "keyword"; - } else if (match = stream.match(/^\{([\/@\\]?\w*)/)) { + } else if (match = stream.match(/^\{([\/@\\]?[\w?]*)/)) { if (match[1] != "/switch") state.indent += (/^(\/|(else|elseif|case|default)$)/.test(match[1]) && state.tag != "switch" ? 1 : 2) * config.indentUnit; state.tag = match[1]; diff --git a/mode/sql/sql.js b/mode/sql/sql.js index ee6c194b0e..fb1c6cb5ce 100644 --- a/mode/sql/sql.js +++ b/mode/sql/sql.js @@ -280,7 +280,7 @@ CodeMirror.defineMode("sql", function(config, parserConfig) { CodeMirror.defineMIME("text/x-mssql", { name: "sql", client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"), - keywords: set(sqlKeywords + "begin trigger proc view index for add constraint key primary foreign collate clustered nonclustered"), + keywords: set(sqlKeywords + "begin trigger proc view index for add constraint key primary foreign collate clustered nonclustered declare"), builtin: set("bigint numeric bit smallint decimal smallmoney int tinyint money float real char varchar text nchar nvarchar ntext binary varbinary image cursor timestamp hierarchyid uniqueidentifier sql_variant xml table "), atoms: set("false true null unknown"), operatorChars: /^[*+\-%<>!=]/, @@ -327,9 +327,9 @@ CodeMirror.defineMode("sql", function(config, parserConfig) { CodeMirror.defineMIME("text/x-cassandra", { name: "sql", client: { }, - keywords: set("use select from using consistency where limit first reversed first and in insert into values using consistency ttl update set delete truncate begin batch apply create keyspace with columnfamily primary key index on drop alter type add any one quorum all local_quorum each_quorum"), - builtin: set("ascii bigint blob boolean counter decimal double float int text timestamp uuid varchar varint"), - atoms: set("false true"), + keywords: set("add all allow alter and any apply as asc authorize batch begin by clustering columnfamily compact consistency count create custom delete desc distinct drop each_quorum exists filtering from grant if in index insert into key keyspace keyspaces level limit local_one local_quorum modify nan norecursive nosuperuser not of on one order password permission permissions primary quorum rename revoke schema select set storage superuser table three to token truncate ttl two type unlogged update use user users using values where with writetime"), + builtin: set("ascii bigint blob boolean counter decimal double float frozen inet int list map static text timestamp timeuuid tuple uuid varchar varint"), + atoms: set("false true infinity NaN"), operatorChars: /^[<>=]/, dateSQL: { }, support: set("commentSlashSlash decimallessFloat"), diff --git a/mode/stylus/stylus.js b/mode/stylus/stylus.js index 60d9a737fb..fc46a0163d 100644 --- a/mode/stylus/stylus.js +++ b/mode/stylus/stylus.js @@ -323,7 +323,12 @@ return pushContext(state, stream, "block", 0); } if (type == "variable-name") { - return pushContext(state, stream, "variableName"); + if ((stream.indentation() == 0 && startOfLine(stream)) || wordIsBlock(firstWordOfLine(stream))) { + return pushContext(state, stream, "variableName"); + } + else { + return pushContext(state, stream, "variableName", 0); + } } if (type == "=") { if (!endOfLine(stream) && !wordIsBlock(firstWordOfLine(stream))) { @@ -445,10 +450,10 @@ wordIsTag(firstWordOfLine(stream)))) { return pushContext(state, stream, "block"); } - if (stream.string.match(/^-?[a-z][\w-\.\[\]\'\"]*\s*=/) || + if (stream.string.match(/^[\$-]?[a-z][\w-\.\[\]\'\"]*\s*=/) || stream.string.match(/^\s*(\(|\)|[0-9])/) || stream.string.match(/^\s+[a-z][\w-]*\(/i) || - stream.string.match(/^\s+-?[a-z]/i)) { + stream.string.match(/^\s+[\$-]?[a-z]/i)) { return pushContext(state, stream, "block", 0); } if (endOfLine(stream)) return pushContext(state, stream, "block"); diff --git a/mode/z80/index.html b/mode/z80/index.html index 1ad3ace046..a41b7473e2 100644 --- a/mode/z80/index.html +++ b/mode/z80/index.html @@ -29,17 +29,18 @@
    -

    MIME type defined: text/x-z80.

    +

    MIME types defined: text/x-z80, text/x-ez80.

    diff --git a/mode/z80/z80.js b/mode/z80/z80.js index ec41d050ac..aae70216f8 100644 --- a/mode/z80/z80.js +++ b/mode/z80/z80.js @@ -3,26 +3,35 @@ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); + mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); + define(["../../lib/codemirror"], mod); else // Plain browser env - mod(CodeMirror); + mod(CodeMirror); })(function(CodeMirror) { "use strict"; -CodeMirror.defineMode('z80', function() { - var keywords1 = /^(exx?|(ld|cp|in)([di]r?)?|pop|push|ad[cd]|cpl|daa|dec|inc|neg|sbc|sub|and|bit|[cs]cf|x?or|res|set|r[lr]c?a?|r[lr]d|s[lr]a|srl|djnz|nop|rst|[de]i|halt|im|ot[di]r|out[di]?)\b/i; - var keywords2 = /^(call|j[pr]|ret[in]?)\b/i; - var keywords3 = /^b_?(call|jump)\b/i; +CodeMirror.defineMode('z80', function(_config, parserConfig) { + var ez80 = parserConfig.ez80; + var keywords1, keywords2; + if (ez80) { + keywords1 = /^(exx?|(ld|cp)([di]r?)?|[lp]ea|pop|push|ad[cd]|cpl|daa|dec|inc|neg|sbc|sub|and|bit|[cs]cf|x?or|res|set|r[lr]c?a?|r[lr]d|s[lr]a|srl|djnz|nop|[de]i|halt|im|in([di]mr?|ir?|irx|2r?)|ot(dmr?|[id]rx|imr?)|out(0?|[di]r?|[di]2r?)|tst(io)?|slp)(\.([sl]?i)?[sl])?\b/i; + keywords2 = /^(((call|j[pr]|rst|ret[in]?)(\.([sl]?i)?[sl])?)|(rs|st)mix)\b/i; + } else { + keywords1 = /^(exx?|(ld|cp|in)([di]r?)?|pop|push|ad[cd]|cpl|daa|dec|inc|neg|sbc|sub|and|bit|[cs]cf|x?or|res|set|r[lr]c?a?|r[lr]d|s[lr]a|srl|djnz|nop|rst|[de]i|halt|im|ot[di]r|out[di]?)\b/i; + keywords2 = /^(call|j[pr]|ret[in]?|b_?(call|jump))\b/i; + } + var variables1 = /^(af?|bc?|c|de?|e|hl?|l|i[xy]?|r|sp)\b/i; var variables2 = /^(n?[zc]|p[oe]?|m)\b/i; var errors = /^([hl][xy]|i[xy][hl]|slia|sll)\b/i; - var numbers = /^([\da-f]+h|[0-7]+o|[01]+b|\d+)\b/i; + var numbers = /^([\da-f]+h|[0-7]+o|[01]+b|\d+d?)\b/i; return { startState: function() { - return {context: 0}; + return { + context: 0 + }; }, token: function(stream, state) { if (!stream.column()) @@ -34,14 +43,21 @@ CodeMirror.defineMode('z80', function() { var w; if (stream.eatWhile(/\w/)) { + if (ez80 && stream.eat('.')) { + stream.eatWhile(/\w/); + } w = stream.current(); if (stream.indentation()) { - if (state.context == 1 && variables1.test(w)) - return 'variable-2'; + if ((state.context == 1 || state.context == 4) && variables1.test(w)) { + state.context = 4; + return 'var2'; + } - if (state.context == 2 && variables2.test(w)) - return 'variable-3'; + if (state.context == 2 && variables2.test(w)) { + state.context = 4; + return 'var3'; + } if (keywords1.test(w)) { state.context = 1; @@ -49,14 +65,13 @@ CodeMirror.defineMode('z80', function() { } else if (keywords2.test(w)) { state.context = 2; return 'keyword'; - } else if (keywords3.test(w)) { - state.context = 3; - return 'keyword'; + } else if (state.context == 4 && numbers.test(w)) { + return 'number'; } if (errors.test(w)) return 'error'; - } else if (numbers.test(w)) { + } else if (stream.match(numbers)) { return 'number'; } else { return null; @@ -77,7 +92,7 @@ CodeMirror.defineMode('z80', function() { if (stream.match(/\\?.'/)) return 'number'; } else if (stream.eat('.') || stream.sol() && stream.eat('#')) { - state.context = 4; + state.context = 5; if (stream.eatWhile(/\w/)) return 'def'; @@ -96,5 +111,6 @@ CodeMirror.defineMode('z80', function() { }); CodeMirror.defineMIME("text/x-z80", "z80"); +CodeMirror.defineMIME("text/x-ez80", { name: "z80", ez80: true }); }); diff --git a/package.json b/package.json index a5c7cd093f..1d198cd801 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version":"5.1.0", + "version":"5.2.0", "main": "lib/codemirror.js", "description": "In-browser code editing made bearable", "licenses": [{"type": "MIT", diff --git a/test/test.js b/test/test.js index 4631672bdb..f3e9da7645 100644 --- a/test/test.js +++ b/test/test.js @@ -1004,6 +1004,14 @@ testCM("wrappingInlineWidget", function(cm) { eq(curR.bottom, cur1.bottom); }, {value: "1 2 3 xxx 4", lineWrapping: true}); +testCM("showEmptyWidgetSpan", function(cm) { + var marker = cm.markText(Pos(0, 2), Pos(0, 2), { + clearWhenEmpty: false, + replacedWith: document.createTextNode("X") + }); + eq(cm.display.view[0].text.textContent, "abXc"); +}, {value: "abc"}); + testCM("changedInlineWidget", function(cm) { cm.setSize("10em"); var w = document.createElement("span"); @@ -1324,8 +1332,8 @@ testCM("rtlMovement", function(cm) { if (cm.getOption("inputStyle") != "textarea") return; forEach(["خحج", "خحabcخحج", "abخحخحجcd", "abخde", "abخح2342خ1حج", "خ1ح2خح3حxج", "خحcd", "1خحcd", "abcdeح1ج", "خمرحبها مها!", "foobarر", "خ ة ق", - ""], function(line) { - var inv = line.charAt(0) == "خ"; + "", "يتم السحب في 05 فبراير 2014"], function(line) { + var inv = line.charCodeAt(0) > 128; cm.setValue(line + "\n"); cm.execCommand(inv ? "goLineEnd" : "goLineStart"); var cursors = byClassName(cm.getWrapperElement(), "CodeMirror-cursors")[0]; var cursor = cursors.firstChild; diff --git a/test/vim_test.js b/test/vim_test.js index dfce9a2ce5..b2b7e25211 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -504,7 +504,7 @@ testVim('{', function(cm, vim, helpers) { helpers.doKeys('6', '{'); helpers.assertCursorAt(0, 0); }, { value: 'a\n\nb\nc\n\nd' }); -testVim('paragraph motions', function(cm, vim, helpers) { +testVim('paragraph_motions', function(cm, vim, helpers) { cm.setCursor(10, 0); helpers.doKeys('{'); helpers.assertCursorAt(4, 0); @@ -984,6 +984,17 @@ testVim('cc_multiply_repeat', function(cm, vim, helpers) { is(register.linewise); eq('vim-insert', cm.getOption('keyMap')); }); +testVim('ct', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('c', 't', 'w'); + eq(' word1 word3', cm.getValue()); + helpers.doKeys('', 'c', '|'); + eq(' word3', cm.getValue()); + helpers.assertCursorAt(0, 0); + helpers.doKeys('', '2', 'u', 'w', 'h'); + helpers.doKeys('c', '2', 'g', 'e'); + eq(' wordword3', cm.getValue()); +}, { value: ' word1 word2 word3'}); testVim('cc_should_not_append_to_document', function(cm, vim, helpers) { var expectedLineCount = cm.lineCount(); cm.setCursor(cm.lastLine(), 0); @@ -1884,7 +1895,11 @@ testVim('visual_block_move_to_eol', function(cm, vim, helpers) { cm.setCursor(0, 0); helpers.doKeys('', 'G', '$'); var selections = cm.getSelections().join(); - eq("123,45,6", selections); + eq('123,45,6', selections); + // Checks that with cursor at Infinity, finding words backwards still works. + helpers.doKeys('2', 'k', 'b'); + selections = cm.getSelections().join(); + eq('1', selections); }, {value: '123\n45\n6'}); testVim('visual_block_different_line_lengths', function(cm, vim, helpers) { // test the block selection with lines of different length @@ -2712,6 +2727,44 @@ testVim('exCommand_history', function(cm, vim, helpers) { onKeyDown({keyCode: keyCodes.Up}, input, close); eq(input, 'sort'); }, {value: ''}); +testVim('search_clear', function(cm, vim, helpers) { + var onKeyDown; + var input = ''; + var keyCodes = { + Ctrl: 17, + u: 85 + }; + cm.openDialog = function(template, callback, options) { + onKeyDown = options.onKeyDown; + }; + var close = function(newVal) { + if (typeof newVal == 'string') input = newVal; + } + helpers.doKeys('/'); + input = 'foo'; + onKeyDown({keyCode: keyCodes.Ctrl}, input, close); + onKeyDown({keyCode: keyCodes.u, ctrlKey: true}, input, close); + eq(input, ''); +}); +testVim('exCommand_clear', function(cm, vim, helpers) { + var onKeyDown; + var input = ''; + var keyCodes = { + Ctrl: 17, + u: 85 + }; + cm.openDialog = function(template, callback, options) { + onKeyDown = options.onKeyDown; + }; + var close = function(newVal) { + if (typeof newVal == 'string') input = newVal; + } + helpers.doKeys(':'); + input = 'foo'; + onKeyDown({keyCode: keyCodes.Ctrl}, input, close); + onKeyDown({keyCode: keyCodes.u, ctrlKey: true}, input, close); + eq(input, ''); +}); testVim('.', function(cm, vim, helpers) { cm.setCursor(0, 0); helpers.doKeys('2', 'd', 'w'); @@ -3659,17 +3712,111 @@ testVim('set_string', function(cm, vim, helpers) { eq('c', CodeMirror.Vim.getOption('testoption')); }); testVim('ex_set_string', function(cm, vim, helpers) { - CodeMirror.Vim.defineOption('testoption', 'a', 'string'); + CodeMirror.Vim.defineOption('testopt', 'a', 'string'); // Test default value is set. - eq('a', CodeMirror.Vim.getOption('testoption')); + eq('a', CodeMirror.Vim.getOption('testopt')); try { - // Test fail to set 'notestoption' - helpers.doEx('set notestoption=b'); + // Test fail to set 'notestopt' + helpers.doEx('set notestopt=b'); fail(); } catch (expected) {}; // Test setOption - helpers.doEx('set testoption=c') - eq('c', CodeMirror.Vim.getOption('testoption')); + helpers.doEx('set testopt=c') + eq('c', CodeMirror.Vim.getOption('testopt')); + helpers.doEx('set testopt=c') + eq('c', CodeMirror.Vim.getOption('testopt', cm)); //local || global + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); // local + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); // global + eq('c', CodeMirror.Vim.getOption('testopt')); // global + // Test setOption global + helpers.doEx('setg testopt=d') + eq('c', CodeMirror.Vim.getOption('testopt', cm)); + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); + eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); + eq('d', CodeMirror.Vim.getOption('testopt')); + // Test setOption local + helpers.doEx('setl testopt=e') + eq('e', CodeMirror.Vim.getOption('testopt', cm)); + eq('e', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); + eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); + eq('d', CodeMirror.Vim.getOption('testopt')); +}); +testVim('ex_set_callback', function(cm, vim, helpers) { + var global; + + function cb(val, cm, cfg) { + if (val === undefined) { + // Getter + if (cm) { + return cm._local; + } else { + return global; + } + } else { + // Setter + if (cm) { + cm._local = val; + } else { + global = val; + } + } + } + + CodeMirror.Vim.defineOption('testopt', 'a', 'string', cb); + // Test default value is set. + eq('a', CodeMirror.Vim.getOption('testopt')); + try { + // Test fail to set 'notestopt' + helpers.doEx('set notestopt=b'); + fail(); + } catch (expected) {}; + // Test setOption (Identical to the string tests, but via callback instead) + helpers.doEx('set testopt=c') + eq('c', CodeMirror.Vim.getOption('testopt', cm)); //local || global + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); // local + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); // global + eq('c', CodeMirror.Vim.getOption('testopt')); // global + // Test setOption global + helpers.doEx('setg testopt=d') + eq('c', CodeMirror.Vim.getOption('testopt', cm)); + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); + eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); + eq('d', CodeMirror.Vim.getOption('testopt')); + // Test setOption local + helpers.doEx('setl testopt=e') + eq('e', CodeMirror.Vim.getOption('testopt', cm)); + eq('e', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); + eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); + eq('d', CodeMirror.Vim.getOption('testopt')); +}) +testVim('ex_set_filetype', function(cm, vim, helpers) { + CodeMirror.defineMode('test_mode', function() { + return {token: function(stream) { + stream.match(/^\s+|^\S+/); + }}; + }); + CodeMirror.defineMode('test_mode_2', function() { + return {token: function(stream) { + stream.match(/^\s+|^\S+/); + }}; + }); + // Test mode is set. + helpers.doEx('set filetype=test_mode'); + eq('test_mode', cm.getMode().name); + // Test 'ft' alias also sets mode. + helpers.doEx('set ft=test_mode_2'); + eq('test_mode_2', cm.getMode().name); +}); +testVim('ex_set_filetype_null', function(cm, vim, helpers) { + CodeMirror.defineMode('test_mode', function() { + return {token: function(stream) { + stream.match(/^\s+|^\S+/); + }}; + }); + cm.setOption('mode', 'test_mode'); + // Test mode is set to null. + helpers.doEx('set filetype='); + eq('null', cm.getMode().name); }); // TODO: Reset key maps after each test. testVim('ex_map_key2key', function(cm, vim, helpers) { diff --git a/theme/liquibyte.css b/theme/liquibyte.css new file mode 100644 index 0000000000..a6e070c6a8 --- /dev/null +++ b/theme/liquibyte.css @@ -0,0 +1,95 @@ +.cm-s-liquibyte.CodeMirror { + background-color: #000; + color: #fff; + line-height: 1.2em; + font-size: 1em; +} +.CodeMirror-focused .cm-matchhighlight { + text-decoration: underline; + text-decoration-color: #0f0; + text-decoration-style: wavy; +} +.cm-trailingspace { + text-decoration: line-through; + text-decoration-color: #f00; + text-decoration-style: dotted; +} +.cm-tab { + text-decoration: line-through; + text-decoration-color: #404040; + text-decoration-style: dotted; +} +.cm-s-liquibyte .CodeMirror-gutters { background-color: #262626; border-right: 1px solid #505050; padding-right: 0.8em; } +.cm-s-liquibyte .CodeMirror-gutter-elt div{ font-size: 1.2em; } +.cm-s-liquibyte .CodeMirror-guttermarker { } +.cm-s-liquibyte .CodeMirror-guttermarker-subtle { } +.cm-s-liquibyte .CodeMirror-linenumber { color: #606060; padding-left: 0;} +.cm-s-liquibyte .CodeMirror-cursor { border-left: 1px solid #eee !important; } + +.cm-s-liquibyte span.cm-comment { color: #008000; } +.cm-s-liquibyte span.cm-def { color: #ffaf40; font-weight: bold; } +.cm-s-liquibyte span.cm-keyword { color: #c080ff; font-weight: bold; } +.cm-s-liquibyte span.cm-builtin { color: #ffaf40; font-weight: bold; } +.cm-s-liquibyte span.cm-variable { color: #5967ff; font-weight: bold; } +.cm-s-liquibyte span.cm-string { color: #ff8000; } +.cm-s-liquibyte span.cm-number { color: #0f0; font-weight: bold; } +.cm-s-liquibyte span.cm-atom { color: #bf3030; font-weight: bold; } + +.cm-s-liquibyte span.cm-variable-2 { color: #007f7f; font-weight: bold; } +.cm-s-liquibyte span.cm-variable-3 { color: #c080ff; font-weight: bold; } +.cm-s-liquibyte span.cm-property { color: #999; font-weight: bold; } +.cm-s-liquibyte span.cm-operator { color: #fff; } + +.cm-s-liquibyte span.cm-meta { color: #0f0; } +.cm-s-liquibyte span.cm-qualifier { color: #fff700; font-weight: bold; } +.cm-s-liquibyte span.cm-bracket { color: #cc7; } +.cm-s-liquibyte span.cm-tag { color: #ff0; font-weight: bold; } +.cm-s-liquibyte span.cm-attribute { color: #c080ff; font-weight: bold; } +.cm-s-liquibyte span.cm-error { color: #f00; } + +.cm-s-liquibyte .CodeMirror-selected { background-color: rgba(255, 0, 0, 0.25) !important; } + +.cm-s-liquibyte span.cm-compilation { background-color: rgba(255, 255, 255, 0.12); } + +.cm-s-liquibyte .CodeMirror-activeline-background {background-color: rgba(0, 255, 0, 0.15) !important;} + +/* Default styles for common addons */ +div.CodeMirror span.CodeMirror-matchingbracket { color: #0f0; font-weight: bold; } +div.CodeMirror span.CodeMirror-nonmatchingbracket { color: #f00; font-weight: bold; } +.CodeMirror-matchingtag { background-color: rgba(150, 255, 0, .3); } +/* Scrollbars */ +/* Simple */ +div.CodeMirror-simplescroll-horizontal div:hover, div.CodeMirror-simplescroll-vertical div:hover { + background-color: rgba(80, 80, 80, .7); +} +div.CodeMirror-simplescroll-horizontal div, div.CodeMirror-simplescroll-vertical div { + background-color: rgba(80, 80, 80, .3); + border: 1px solid #404040; + border-radius: 5px; +} +div.CodeMirror-simplescroll-vertical div { + border-top: 1px solid #404040; + border-bottom: 1px solid #404040; +} +div.CodeMirror-simplescroll-horizontal div { + border-left: 1px solid #404040; + border-right: 1px solid #404040; +} +div.CodeMirror-simplescroll-vertical { + background-color: #262626; +} +div.CodeMirror-simplescroll-horizontal { + background-color: #262626; + border-top: 1px solid #404040; +} +/* Overlay */ +div.CodeMirror-overlayscroll-horizontal div, div.CodeMirror-overlayscroll-vertical div { + background-color: #404040; + border-radius: 5px; +} +div.CodeMirror-overlayscroll-vertical div { + border: 1px solid #404040; +} +div.CodeMirror-overlayscroll-horizontal div { + border: 1px solid #404040; +} diff --git a/theme/monokai.css b/theme/monokai.css index 6dfcc73ce4..d70bb86f29 100644 --- a/theme/monokai.css +++ b/theme/monokai.css @@ -18,7 +18,7 @@ .cm-s-monokai span.cm-keyword {color: #f92672;} .cm-s-monokai span.cm-string {color: #e6db74;} -.cm-s-monokai span.cm-variable {color: #a6e22e;} +.cm-s-monokai span.cm-variable {color: #f8f8f2;} .cm-s-monokai span.cm-variable-2 {color: #9effff;} .cm-s-monokai span.cm-def {color: #fd971f;} .cm-s-monokai span.cm-bracket {color: #f8f8f2;} diff --git a/theme/solarized.css b/theme/solarized.css index 4a10b7c059..9db3951579 100644 --- a/theme/solarized.css +++ b/theme/solarized.css @@ -53,7 +53,7 @@ http://ethanschoonover.com/solarized/img/solarized-palette.png .cm-s-solarized .cm-number { color: #d33682; } .cm-s-solarized .cm-def { color: #2aa198; } -.cm-s-solarized .cm-variable { color: #268bd2; } +.cm-s-solarized .cm-variable { color: #839496; } .cm-s-solarized .cm-variable-2 { color: #b58900; } .cm-s-solarized .cm-variable-3 { color: #6c71c4; }