From f175e8d455717416cf3c1a54c831896bb37ef78c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 29 Aug 2012 13:37:25 +0200 Subject: [PATCH 001/135] Make StringStream.match return null when the match isn't at current position Closes #761 --- lib/codemirror.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/codemirror.js b/lib/codemirror.js index fa950dca8c..f2ca8a3019 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2340,6 +2340,7 @@ window.CodeMirror = (function() { } } else { var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) return null; if (match && consume !== false) this.pos += match[0].length; return match; } From a4effe378c3e5dead457af6655ba33adb4cedf39 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 29 Aug 2012 14:41:37 +0200 Subject: [PATCH 002/135] Make right-click select-all work on some browsers The hack is horrible, but it seems to do the trick. Also simplifies context-menu handling somewhat. Issue #755 --- lib/codemirror.js | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index f2ca8a3019..6ce88b2a70 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -78,7 +78,7 @@ window.CodeMirror = (function() { overwrite = false, suppressEdits = false; // Variables used by startOperation/endOperation to track what // happened during the operation. - var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone, + var updateInput, userSelChange, changes, textChanged, selectionChanged, gutterDirty, callbacks; // Current visible range (may be bigger than the view window). var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0; @@ -675,7 +675,6 @@ window.CodeMirror = (function() { focused = true; if (scroller.className.search(/\bCodeMirror-focused\b/) == -1) scroller.className += " CodeMirror-focused"; - if (!leaveInputAlone) resetInput(true); } slowPoll(); restartBlink(); @@ -938,7 +937,7 @@ window.CodeMirror = (function() { // supported or compatible enough yet to rely on.) var prevInput = ""; function readInput() { - if (leaveInputAlone || !focused || hasSelection(input) || options.readOnly) return false; + if (!focused || hasSelection(input) || options.readOnly) return false; var text = input.value; if (text == prevInput) return false; shiftSelecting = null; @@ -1757,6 +1756,7 @@ window.CodeMirror = (function() { var offL = eltOffset(lineSpace, true); return coordsChar(x - offL.left, y - offL.top); } + var detectingSelectAll; function onContextMenu(e) { var pos = posFromMouse(e), scrollPos = scrollbar.scrollTop; if (!pos || opera) return; // Opera is difficult. @@ -1768,19 +1768,28 @@ window.CodeMirror = (function() { input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " + "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; - leaveInputAlone = true; - var val = input.value = getSelection(); focusInput(); selectInput(input); + clearTimeout(detectingSelectAll); function rehide() { - var newVal = splitLines(input.value).join("\n"); - if (newVal != val && !options.readOnly) operation(replaceSelection)(newVal, "end"); inputDiv.style.position = "relative"; input.style.cssText = oldCSS; if (ie_lt9) scrollbar.scrollTop = scrollPos; - leaveInputAlone = false; resetInput(true); slowPoll(); + + // Try to detect the user choosing select-all + if (input.selectionStart != null) { + var extval = input.value = " " + input.value, i = 0; + prevInput = " "; + input.selectionStart = 1; input.selectionEnd = extval.length; + detectingSelectAll = setTimeout(function poll(){ + if (prevInput == " " && input.selectionStart == 0) + operation(commands.selectAll)(instance); + else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); + else resetInput(); + }, 200); + } } if (gecko) { @@ -1962,8 +1971,7 @@ window.CodeMirror = (function() { if (newScrollPos) scrollCursorIntoView(); if (selectionChanged) restartBlink(); - if (focused && !leaveInputAlone && - (updateInput === true || (updateInput !== false && selectionChanged))) + if (focused && (updateInput === true || (updateInput !== false && selectionChanged))) resetInput(userSelChange); if (selectionChanged && options.matchBrackets) From c565edcf01f326fd5c8013c9074696c27eea3c5c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 29 Aug 2012 16:59:32 +0200 Subject: [PATCH 003/135] Add a .INI mode --- doc/compress.html | 1 + index.html | 1 + mode/ini/index.html | 34 ++++++++++++++++++++++++++++++++++ mode/ini/ini.js | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+) create mode 100644 mode/ini/index.html create mode 100644 mode/ini/ini.js diff --git a/doc/compress.html b/doc/compress.html index 3e4abc1479..f0b60c0506 100644 --- a/doc/compress.html +++ b/doc/compress.html @@ -70,6 +70,7 @@

{ } CodeMi + diff --git a/index.html b/index.html index 6d074dd334..54f0287ff6 100644 --- a/index.html +++ b/index.html @@ -46,6 +46,7 @@

Supported modes:

  • HTML embedded scripts
  • HTML mixed-mode
  • Java
  • +
  • .INI
  • JavaScript
  • Jinja2
  • LESS
  • diff --git a/mode/ini/index.html b/mode/ini/index.html new file mode 100644 index 0000000000..ac1a72a7fe --- /dev/null +++ b/mode/ini/index.html @@ -0,0 +1,34 @@ + + + + + CodeMirror: INI mode + + + + + + + +

    CodeMirror: INI mode

    +
    + + +

    MIME type defined: text/ini.

    + + diff --git a/mode/ini/ini.js b/mode/ini/ini.js new file mode 100644 index 0000000000..7042c65081 --- /dev/null +++ b/mode/ini/ini.js @@ -0,0 +1,38 @@ +CodeMirror.defineMode("ini", function() { + return { + startState: function() { + return {context: ""}; + }, + + token: function(stream, state) { + + if (stream.eatSpace()) return null; + var ch = stream.next(); + if (ch == ";") {stream.skipToEnd(); return "comment";} + else if (ch == "[") {state.context = "section"; return "bracket";} + else if (ch == "]") {state.context = "key"; return "bracket";} + else if (ch == "=") {state.context = "value"; return "operator";} + else + { + if (state.context == "section") + { + if (!stream.skipTo("]")) stream.skipToEnd(); + return "keyword"; + } + else if(state.context == "key") + { + if (!stream.skipTo("=")) stream.skipToEnd(); + return "attribute"; + } + else if(state.context == "value") + { + stream.skipToEnd(); + state.context = "key"; + return "variable"; + } + } + } + }; +}); + +CodeMirror.defineMIME("text/ini", "ini"); From bf3a6f445c94ba07af628d66be7b751636dcffb5 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 30 Aug 2012 10:21:23 +0200 Subject: [PATCH 004/135] Revert "Add a .INI mode" It turns out the properties mode already handles .ini files just fine. This reverts commit 9afd96d94d65e27329887d16e8e2cd7f6ffed1d6. --- doc/compress.html | 1 - index.html | 1 - mode/ini/index.html | 34 ---------------------------------- mode/ini/ini.js | 38 -------------------------------------- 4 files changed, 74 deletions(-) delete mode 100644 mode/ini/index.html delete mode 100644 mode/ini/ini.js diff --git a/doc/compress.html b/doc/compress.html index f0b60c0506..3e4abc1479 100644 --- a/doc/compress.html +++ b/doc/compress.html @@ -70,7 +70,6 @@

    { } CodeMi - diff --git a/index.html b/index.html index 54f0287ff6..6d074dd334 100644 --- a/index.html +++ b/index.html @@ -46,7 +46,6 @@

    Supported modes:

  • HTML embedded scripts
  • HTML mixed-mode
  • Java
  • -
  • .INI
  • JavaScript
  • Jinja2
  • LESS
  • diff --git a/mode/ini/index.html b/mode/ini/index.html deleted file mode 100644 index ac1a72a7fe..0000000000 --- a/mode/ini/index.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - CodeMirror: INI mode - - - - - - - -

    CodeMirror: INI mode

    -
    - - -

    MIME type defined: text/ini.

    - - diff --git a/mode/ini/ini.js b/mode/ini/ini.js deleted file mode 100644 index 7042c65081..0000000000 --- a/mode/ini/ini.js +++ /dev/null @@ -1,38 +0,0 @@ -CodeMirror.defineMode("ini", function() { - return { - startState: function() { - return {context: ""}; - }, - - token: function(stream, state) { - - if (stream.eatSpace()) return null; - var ch = stream.next(); - if (ch == ";") {stream.skipToEnd(); return "comment";} - else if (ch == "[") {state.context = "section"; return "bracket";} - else if (ch == "]") {state.context = "key"; return "bracket";} - else if (ch == "=") {state.context = "value"; return "operator";} - else - { - if (state.context == "section") - { - if (!stream.skipTo("]")) stream.skipToEnd(); - return "keyword"; - } - else if(state.context == "key") - { - if (!stream.skipTo("=")) stream.skipToEnd(); - return "attribute"; - } - else if(state.context == "value") - { - stream.skipToEnd(); - state.context = "key"; - return "variable"; - } - } - } - }; -}); - -CodeMirror.defineMIME("text/ini", "ini"); From 1b07cc3f515baf6ce5d68c995a616d80c4c9e346 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 31 Aug 2012 10:21:15 +0200 Subject: [PATCH 005/135] Fix assumption that StringStream.peek returns a string Closes #776 --- mode/clojure/clojure.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mode/clojure/clojure.js b/mode/clojure/clojure.js index d0268a78d6..ce389927db 100644 --- a/mode/clojure/clojure.js +++ b/mode/clojure/clojure.js @@ -40,9 +40,9 @@ CodeMirror.defineMode("clojure", function (config, mode) { var tests = { digit: /\d/, digit_or_colon: /[\d:]/, - hex: /[0-9a-fA-F]/, + hex: /[0-9a-f]/i, sign: /[+-]/, - exponent: /[eE]/, + exponent: /e/i, keyword_char: /[^\s\(\[\;\)\]]/, basic: /[\w\$_\-]/, lang_keyword: /[\w*+!\-_?:\/]/ @@ -64,8 +64,7 @@ CodeMirror.defineMode("clojure", function (config, mode) { function isNumber(ch, stream){ // hex - if ( ch === '0' && 'x' == stream.peek().toLowerCase() ) { - stream.eat('x'); + if ( ch === '0' && stream.eat(/x/i) ) { stream.eatWhile(tests.hex); return true; } @@ -85,8 +84,7 @@ CodeMirror.defineMode("clojure", function (config, mode) { stream.eatWhile(tests.digit); } - if ( 'e' == stream.peek().toLowerCase() ) { - stream.eat(tests.exponent); + if ( stream.eat(tests.exponent) ) { stream.eat(tests.sign); stream.eatWhile(tests.digit); } From 6ba24813c96687dac2f23db100780bc42e6ed808 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 31 Aug 2012 15:30:05 +0200 Subject: [PATCH 006/135] Save overhead of operation when polling doesn't find a change --- lib/codemirror.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 6ce88b2a70..be782c74ea 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -911,21 +911,17 @@ window.CodeMirror = (function() { function slowPoll() { if (pollingFast) return; poll.set(options.pollInterval, function() { - startOperation(); readInput(); if (focused) slowPoll(); - endOperation(); }); } function fastPoll() { var missed = false; pollingFast = true; function p() { - startOperation(); var changed = readInput(); if (!changed && !missed) {missed = true; poll.set(60, p);} else {pollingFast = false; slowPoll();} - endOperation(); } poll.set(20, p); } @@ -940,6 +936,7 @@ window.CodeMirror = (function() { if (!focused || hasSelection(input) || options.readOnly) return false; var text = input.value; if (text == prevInput) return false; + startOperation(); shiftSelecting = null; var same = 0, l = Math.min(prevInput.length, text.length); while (same < l && prevInput[same] == text[same]) ++same; @@ -950,6 +947,7 @@ window.CodeMirror = (function() { replaceSelection(text.slice(same), "end"); if (text.length > 1000) { input.value = prevInput = ""; } else prevInput = text; + endOperation(); return true; } function resetInput(user) { From a2257ba4ce89b704e10a67e29808e56bcd5ce8fa Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 31 Aug 2012 12:48:49 +0200 Subject: [PATCH 007/135] Simplify line content-node API, get rid of makeTab indirection --- lib/codemirror.js | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index be782c74ea..624f71b19e 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -88,7 +88,6 @@ window.CodeMirror = (function() { // Tracks the maximum line length so that the horizontal scrollbar // can be kept static when scrolling. var maxLine = getLine(0), updateMaxLine = false, maxLineChanged = true; - var tabCache = {}; var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll var goalColumn = null; @@ -363,6 +362,10 @@ window.CodeMirror = (function() { for (var n = line; n; n = n.parent) n.height += diff; } + function lineContent(line, wrapAt) { + return line.getContent(options.tabSize, wrapAt, options.lineWrapping); + } + function setValue(code) { var top = {line: 0, ch: 0}; updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length}, @@ -1159,7 +1162,7 @@ window.CodeMirror = (function() { if (!nextIntact || nextIntact.from > j) { if (line.hidden) var lineElement = elt("pre"); else { - var lineElement = line.getElement(makeTab); + var lineElement = lineContent(line); if (line.className) lineElement.className = line.className; // Kludge to make sure the styled element lies behind the selection (by z-index) if (line.bgClassName) { @@ -1443,7 +1446,7 @@ window.CodeMirror = (function() { var indentString = "", pos = 0; if (options.indentWithTabs) for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";} - while (pos < indentation) {++pos; indentString += " ";} + if (pos < indentation) indentString += spaceStr(indentation - pos); if (indentString != curSpaceString) replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}); @@ -1480,13 +1483,6 @@ window.CodeMirror = (function() { } changes.push({from: 0, to: doc.size}); } - function makeTab(col) { - var w = options.tabSize - col % options.tabSize, cached = tabCache[w]; - if (cached) return cached; - for (var str = "", i = 0; i < w; ++i) str += " "; - var span = elt("span", str, "cm-tab"); - return (tabCache[w] = {element: span, width: w}); - } function themeChanged() { scroller.className = scroller.className.replace(/\s*cm-s-\S+/g, "") + options.theme.replace(/(^|\s)\s*/g, " cm-s-"); @@ -1643,7 +1639,7 @@ window.CodeMirror = (function() { if (ch == 0) return {top: 0, left: 0}; var wbr = options.lineWrapping && ch < line.text.length && spanAffectsWrapping.test(line.text.slice(ch - 1, ch + 1)); - var pre = line.getElement(makeTab, ch, wbr); + var pre = lineContent(line, ch); removeChildrenAndAdd(measure, pre); var anchor = pre.anchor; var top = anchor.offsetTop, left = anchor.offsetLeft; @@ -2598,7 +2594,7 @@ window.CodeMirror = (function() { indentation: function(tabSize) {return countColumn(this.text, null, tabSize);}, // Produces an HTML fragment for the line, taking selection, // marking, and highlighting into account. - getElement: function(makeTab, wrapAt, wrapWBR) { + getContent: function(tabSize, wrapAt, compensateForWrapping) { var first = true, col = 0, specials = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g; var pre = elt("pre"); function span_(html, text, style) { @@ -2622,9 +2618,9 @@ window.CodeMirror = (function() { if (!m) break; pos += skipped + 1; if (m[0] == "\t") { - var tab = makeTab(col); - content.appendChild(tab.element.cloneNode(true)); - col += tab.width; + var tabWidth = tabSize - col % tabSize; + content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); + col += tabWidth; } else { var token = elt("span", "\u2022", "cm-invalidchar"); token.title = "\\u" + m[0].charCodeAt(0).toString(16); @@ -2645,7 +2641,7 @@ window.CodeMirror = (function() { if (wrapAt > outPos) { span_(html, text.slice(0, wrapAt - outPos), style); // See comment at the definition of spanAffectsWrapping - if (wrapWBR) html.appendChild(elt("wbr")); + if (compensateForWrapping) html.appendChild(elt("wbr")); } html.appendChild(anchor); var cut = wrapAt - outPos; @@ -3085,10 +3081,17 @@ window.CodeMirror = (function() { return box; } - // Get a node's text content. function eltText(node) { return node.textContent || node.innerText || node.nodeValue || ""; } + + var spaceStrs = [""]; + function spaceStr(n) { + while (spaceStrs.length <= n) + spaceStrs.push(spaceStrs[spaceStrs.length - 1] + " "); + return spaceStrs[n]; + } + function selectInput(node) { if (ios) { // Mobile Safari apparently has a bug where select() is broken. node.selectionStart = 0; From ff6bc0c6d8815d779be713ddffd3425f70e42ea4 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 31 Aug 2012 13:57:34 +0200 Subject: [PATCH 008/135] Change the way highlighting information is updated - Background parsing now never goes past the visible part of the document. - We only store style information for lines that are actually visible (and then keep it cached). - When the document changes, background highlighting always re-highlights from the change to the end of the visible part. - Gets rid of compareState mode methods and the hairy heuristic that tried to simulate it when absent. - The onHighlightComplete callback was removed, since it no longer really applies -- the document is only fully parsed when scrolled to its end. This should help preserve memory (a huge document will no longer immediately have parser state and highlighting information built up for all lines, but only for the parts that you look at), and removes the pathological case when you, for example, are typing at the top of a huge XML document, and opening or closing a tag causes a whole re-highlight to cascade all the way to the bottom because the compareState will detect a change. Issue #688 --- doc/manual.html | 17 ---- lib/codemirror.js | 160 +++++++++++++----------------------- lib/util/multiplex.js | 8 -- mode/haxe/haxe.js | 3 - mode/htmlmixed/htmlmixed.js | 6 -- mode/tiki/tiki.js | 7 -- mode/xml/xml.js | 8 -- 7 files changed, 58 insertions(+), 151 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index 6b4dc8c7b5..21e6cbdcbd 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -249,11 +249,6 @@

    Configuration

    When given, will be called whenever the editor is scrolled.
    -
    onHighlightComplete (function)
    -
    Whenever the editor's content has been fully highlighted, - this function (if given) will be called. It'll be given a single - argument, the editor instance.
    -
    onUpdate (function)
    Will be called whenever CodeMirror updates its DOM display.
    @@ -1085,18 +1080,6 @@

    Writing CodeMirror Modes

    which is given a state and should return a safe copy of that state.

    -

    By default, CodeMirror will stop re-parsing - a document as soon as it encounters a few lines that were - highlighted the same in the old parse as in the new one. It is - possible to provide an explicit way to test whether a state is - equivalent to another one, which CodeMirror will use (instead of - the unchanged-lines heuristic) to decide when to stop - highlighting. You do this by providing - a compareStates method on your mode object, which - takes two state arguments and returns a boolean indicating whether - they are equivalent. See the XML mode, which uses this to provide - reliable highlighting of bad closing tags, as an example.

    -

    If you want your mode to provide smart indentation (though the indentLine method and the indentAuto diff --git a/lib/codemirror.js b/lib/codemirror.js index 624f71b19e..68694effd1 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -64,9 +64,9 @@ window.CodeMirror = (function() { var poll = new Delayed(), highlight = new Delayed(), blinker; // mode holds a mode API object. doc is the tree of Line objects, - // work an array of lines that should be parsed, and history the - // undo history (instance of History constructor). - var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused; + // frontier is the point up to which the content has been parsed, + // and history the undo history (instance of History constructor). + var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), frontier = 0, focused; loadMode(); // The selection. These are always maintained to point at valid // positions. Inverted is used to remember that the user is @@ -363,6 +363,8 @@ window.CodeMirror = (function() { } function lineContent(line, wrapAt) { + if (!line.styles) + line.highlight(mode, line.stateAfter = getStateBefore(lineNo(line)), options.tabSize); return line.getContent(options.tabSize, wrapAt, options.lineWrapping); } @@ -795,19 +797,11 @@ window.CodeMirror = (function() { if (recomputeMaxLength) updateMaxLine = true; } - // Add these lines to the work array, so that they will be - // highlighted. Adjust work lines if lines were added/removed. - var newWork = [], lendiff = newText.length - nlines - 1; - for (var i = 0, l = work.length; i < l; ++i) { - var task = work[i]; - if (task < from.line) newWork.push(task); - else if (task > to.line) newWork.push(task + lendiff); - } - var hlEnd = from.line + Math.min(newText.length, 500); - highlightLines(from.line, hlEnd); - newWork.push(hlEnd); - work = newWork; - startWorker(100); + // Adjust frontier, schedule worker + frontier = Math.min(frontier, from.line); + startWorker(400); + + var lendiff = newText.length - nlines - 1; // Remember that these lines changed, for updating the display changes.push({from: from.line, to: to.line + 1, diff: lendiff}); var changeObj = {from: from, to: to, text: newText}; @@ -1076,6 +1070,7 @@ window.CodeMirror = (function() { }); showingFrom = from; showingTo = to; displayOffset = heightAtLine(doc, from); + startWorker(100); // Since this is all rather error prone, it is honoured with the // only assertion in the whole file. @@ -1455,8 +1450,8 @@ window.CodeMirror = (function() { function loadMode() { mode = CodeMirror.getMode(options, options.mode); doc.iter(0, doc.size, function(line) { line.stateAfter = null; }); - work = [0]; - startWorker(); + frontier = 0; + startWorker(100); } function gutterChanged() { var visible = options.gutter || options.lineNumbers; @@ -1867,70 +1862,39 @@ window.CodeMirror = (function() { return minline; } function getStateBefore(n) { - var start = findStartLine(n), state = start && getLine(start-1).stateAfter; + var pos = findStartLine(n), state = pos && getLine(pos-1).stateAfter; if (!state) state = startState(mode); else state = copyState(mode, state); - doc.iter(start, n, function(line) { - line.highlight(mode, state, options.tabSize); - line.stateAfter = copyState(mode, state); + doc.iter(pos, n, function(line) { + line.process(mode, state, options.tabSize); + line.stateAfter = (pos == n - 1 || pos % 5 == 0) ? copyState(mode, state) : null; }); - if (start < n) changes.push({from: start, to: n}); - if (n < doc.size && !getLine(n).stateAfter) work.push(n); return state; } - function highlightLines(start, end) { - var state = getStateBefore(start); - doc.iter(start, end, function(line) { - line.highlight(mode, state, options.tabSize); - line.stateAfter = copyState(mode, state); - }); - } function highlightWorker() { - var end = +new Date + options.workTime; - var foundWork = work.length; - while (work.length) { - if (!getLine(showingFrom).stateAfter) var task = showingFrom; - else var task = work.pop(); - if (task >= doc.size) continue; - var start = findStartLine(task), state = start && getLine(start-1).stateAfter; - if (state) state = copyState(mode, state); - else state = startState(mode); - - var unchanged = 0, compare = mode.compareStates, realChange = false, - i = start, bail = false; - doc.iter(i, doc.size, function(line) { - var hadState = line.stateAfter; - if (+new Date > end) { - work.push(i); - startWorker(options.workDelay); - if (realChange) changes.push({from: task, to: i + 1}); - return (bail = true); - } - var changed = line.highlight(mode, state, options.tabSize); - if (changed) realChange = true; + if (frontier >= showingTo) return; + var end = +new Date + options.workTime, state = copyState(mode, getStateBefore(frontier)); + var startFrontier = frontier; + doc.iter(frontier, showingTo, function(line) { + if (frontier >= showingFrom) { // Visible + line.highlight(mode, state, options.tabSize); line.stateAfter = copyState(mode, state); - var done = null; - if (compare) { - var same = hadState && compare(hadState, state); - if (same != Pass) done = !!same; - } - if (done == null) { - if (changed !== false || !hadState) unchanged = 0; - else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, ""))) - done = true; - } - if (done) return true; - ++i; - }); - if (bail) return; - if (realChange) changes.push({from: task, to: i + 1}); - } - if (foundWork && options.onHighlightComplete) - options.onHighlightComplete(instance); + } else { + line.process(mode, state, options.tabSize); + line.stateAfter = frontier % 5 == 0 ? copyState(mode, state) : null; + } + ++frontier; + if (+new Date > end) { + startWorker(options.workDelay); + return true; + } + }); + if (showingTo > startFrontier && frontier >= showingFrom) + operation(function() {changes.push({from: startFrontier, to: frontier});})(); } function startWorker(time) { - if (!work.length) return; - highlight.set(time, operation(highlightWorker)); + if (frontier < showingTo) + highlight.set(time, highlightWorker); } // Operations are used to wrap changes in such a way that each @@ -2029,7 +1993,6 @@ window.CodeMirror = (function() { onCursorActivity: null, onViewportChange: null, onGutterClick: null, - onHighlightComplete: null, onUpdate: null, onFocus: null, onBlur: null, onScroll: null, matchBrackets: false, @@ -2427,8 +2390,7 @@ window.CodeMirror = (function() { // Line objects. These hold state related to a line, including // highlighting info (the styles array). - function Line(text, styles) { - this.styles = styles || [text, null]; + function Line(text) { this.text = text; this.height = 1; } @@ -2445,15 +2407,11 @@ window.CodeMirror = (function() { return ln; }; Line.prototype = { - // Replace a piece of a line, keeping the styles around it intact. + // Replace a piece of a line, keeping the markers intact. replace: function(from, to_, text) { - var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_; - copyStyles(0, from, this.styles, st); - if (text) st.push(text, null); - copyStyles(to, this.text.length, this.styles, st); - this.styles = st; + var mk = this.marked, to = to_ == null ? this.text.length : to_; this.text = this.text.slice(0, from) + text + this.text.slice(to); - this.stateAfter = null; + this.stateAfter = this.styles = null; if (mk) { var diff = text.length - (to - from); for (var i = 0; i < mk.length; ++i) { @@ -2463,11 +2421,10 @@ window.CodeMirror = (function() { } } }, - // Split a part off a line, keeping styles and markers intact. + // Split a part off a line, keeping markers intact. split: function(pos, textBefore) { - var st = [textBefore, null], mk = this.marked; - copyStyles(pos, this.text.length, this.styles, st); - var taken = new Line(textBefore + this.text.slice(pos), st); + var mk = this.marked; + var taken = new Line(textBefore + this.text.slice(pos)); if (mk) { for (var i = 0; i < mk.length; ++i) { var mark = mk[i]; @@ -2484,7 +2441,7 @@ window.CodeMirror = (function() { append: function(line) { var mylen = this.text.length, mk = line.marked, mymk = this.marked; this.text += line.text; - copyStyles(0, line.text.length, line.styles, this.styles); + this.styles = this.stateAfter = null; if (mymk) { for (var i = 0; i < mymk.length; ++i) if (mymk[i].to == null) mymk[i].to = mylen; @@ -2550,19 +2507,16 @@ window.CodeMirror = (function() { // array, which contains alternating fragments of text and CSS // classes. highlight: function(mode, state, tabSize) { - var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0; - var changed = false, curWord = st[0], prevWord; + var stream = new StringStream(this.text, tabSize), st = this.styles || (this.styles = []); + var pos = st.length = 0; if (this.text == "" && mode.blankLine) mode.blankLine(state); while (!stream.eol()) { - var style = mode.token(stream, state); - var substr = this.text.slice(stream.start, stream.pos); + var style = mode.token(stream, state), substr = stream.current(); stream.start = stream.pos; - if (pos && st[pos-1] == style) + if (pos && st[pos-1] == style) { st[pos-2] += substr; - else if (substr) { - if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true; + } else if (substr) { st[pos++] = substr; st[pos++] = style; - prevWord = curWord; curWord = st[pos]; } // Give up when line is ridiculously long if (stream.pos > 5000) { @@ -2570,12 +2524,14 @@ window.CodeMirror = (function() { break; } } - if (st.length != pos) {st.length = pos; changed = true;} - if (pos && st[pos-2] != prevWord) changed = true; - // Short lines with simple highlights return null, and are - // counted as changed by the driver because they are likely to - // highlight the same way in various contexts. - return changed || (st.length < 5 && this.text.length < 10 ? null : false); + }, + process: function(mode, state, tabSize) { + var stream = new StringStream(this.text, tabSize); + if (this.text == "" && mode.blankLine) mode.blankLine(state); + while (!stream.eol() && stream.pos <= 5000) { + mode.token(stream, state); + stream.start = stream.pos; + } }, // Fetch the parser token for a given character. Useful for hacks // that want to inspect the mode state (say, for completion). diff --git a/lib/util/multiplex.js b/lib/util/multiplex.js index 755588b9c4..b7c1838f62 100644 --- a/lib/util/multiplex.js +++ b/lib/util/multiplex.js @@ -68,14 +68,6 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { return mode.indent(state.innerActive ? state.inner : state.outer, textAfter); }, - compareStates: function(a, b) { - if (a.innerActive != b.innerActive) return false; - var mode = a.innerActive || outer; - if (!mode.compareStates) return CodeMirror.Pass; - return mode.compareStates(a.innerActive ? a.inner : a.outer, - b.innerActive ? b.inner : b.outer); - }, - electricChars: outer.electricChars }; }; diff --git a/mode/haxe/haxe.js b/mode/haxe/haxe.js index ea8bd834e6..64f4eb3ff8 100644 --- a/mode/haxe/haxe.js +++ b/mode/haxe/haxe.js @@ -421,9 +421,6 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) { else if (lexical.align) return lexical.column + (closing ? 0 : 1); else return lexical.indented + (closing ? 0 : indentUnit); }, - compareStates: function(state1, state2) { - return (state1.localVars == state2.localVars) && (state1.context == state2.context); - }, electricChars: "{}" }; diff --git a/mode/htmlmixed/htmlmixed.js b/mode/htmlmixed/htmlmixed.js index 260a6d0dfb..5f2fc238c9 100644 --- a/mode/htmlmixed/htmlmixed.js +++ b/mode/htmlmixed/htmlmixed.js @@ -76,12 +76,6 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) { return cssMode.indent(state.localState, textAfter); }, - compareStates: function(a, b) { - if (a.mode != b.mode) return false; - if (a.localState) return CodeMirror.Pass; - return htmlMode.compareStates(a.htmlState, b.htmlState); - }, - electricChars: "/{}:" }; }, "xml", "javascript", "css"); diff --git a/mode/tiki/tiki.js b/mode/tiki/tiki.js index 24bf0fbfe5..af83dc1b5b 100644 --- a/mode/tiki/tiki.js +++ b/mode/tiki/tiki.js @@ -301,13 +301,6 @@ CodeMirror.defineMode('tiki', function(config, parserConfig) { if (context) return context.indent + indentUnit; else return 0; }, - compareStates: function(a, b) { - if (a.indented != b.indented || a.pluginName != b.pluginName) return false; - for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) { - if (!ca || !cb) return ca == cb; - if (ca.pluginName != cb.pluginName) return false; - } - }, electricChars: "/" }; }); diff --git a/mode/xml/xml.js b/mode/xml/xml.js index cd69f62fd5..860e368f5b 100644 --- a/mode/xml/xml.js +++ b/mode/xml/xml.js @@ -308,14 +308,6 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { else return 0; }, - compareStates: function(a, b) { - if (a.indented != b.indented || a.tokenize != b.tokenize) return false; - for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) { - if (!ca || !cb) return ca == cb; - if (ca.tagName != cb.tagName || ca.indent != cb.indent) return false; - } - }, - electricChars: "/" }; }); From ee16009813062592104ef07fefdc55c9c4f13342 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 31 Aug 2012 15:58:15 +0200 Subject: [PATCH 009/135] Remove unused function copyStyles --- lib/codemirror.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 68694effd1..d020d6de78 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2677,20 +2677,6 @@ window.CodeMirror = (function() { for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this); } }; - // Utility used by replace and split above - function copyStyles(from, to, source, dest) { - for (var i = 0, pos = 0, state = 0; pos < to; i+=2) { - var part = source[i], end = pos + part.length; - if (state == 0) { - if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]); - if (end >= from) state = 1; - } else if (state == 1) { - if (end > to) dest.push(part.slice(0, to - pos), source[i+1]); - else dest.push(part, source[i+1]); - } - pos = end; - } - } // Data structure that holds the sequence of lines. function LeafChunk(lines) { From 0cae19e8cc5f9dccf55194d934c32bb971938bc9 Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Mon, 3 Sep 2012 02:42:36 -0400 Subject: [PATCH 010/135] Add highlighting tests for Markdown mode. --- mode/markdown/test.html | 419 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 419 insertions(+) create mode 100644 mode/markdown/test.html diff --git a/mode/markdown/test.html b/mode/markdown/test.html new file mode 100644 index 0000000000..3f4e9e0c90 --- /dev/null +++ b/mode/markdown/test.html @@ -0,0 +1,419 @@ + + + + + CodeMirror: Markdown mode + + + + + + + + + + +

    Tests for the Markdown Mode

    +

    Basics

    + + +

    Code

    + + +

    Headers

    + + +

    Blockquotes

    + + +

    Horizontal rules

    + + +

    Links

    + + +

    Emphasis

    + + +

    Escaping

    + + +

    Summary

    + + + + + From 59d6bbef9fff62ae57276deab7f6940b39896434 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 3 Sep 2012 09:48:59 +0200 Subject: [PATCH 011/135] [vim keymap] Reset keymap after YY is pressed Closes #774 --- keymap/vim.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/keymap/vim.js b/keymap/vim.js index 73a4a0a771..b6a41850ef 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -509,7 +509,10 @@ setupPrefixBindingForKey("Space"); CodeMirror.keyMap["vim-prefix-y"] = { - "Y": countTimes(function(cm) { pushInBuffer("\n"+cm.getLine(cm.getCursor().line+yank)); yank++; }), + "Y": countTimes(function(cm) { + pushInBuffer("\n"+cm.getLine(cm.getCursor().line+yank)); yank++; + cm.setOption("keyMap", "vim"); + }), "'": function(cm) {cm.setOption("keyMap", "vim-prefix-y'"); emptyBuffer();}, nofallthrough: true, style: "fat-cursor" }; From 5bc1e8983ff636df09b205aad73988554f13994c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 3 Sep 2012 10:15:16 +0200 Subject: [PATCH 012/135] Remove obsolete before_script section in .travis.yml --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9bdccd0e3e..baa0031d50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,3 @@ language: node_js node_js: - 0.8 -before_script: - - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start" From a562bed086d59104f3691ef47b6947882083550b Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Sun, 2 Sep 2012 22:53:30 -0400 Subject: [PATCH 013/135] [clojure mode] Check if +/- is immediately followed by digit before marking as leading sign Closes #782 Correct highlighting for the following code snippet should highlight the + as an operator, and each 2 as a number (taken from http://clojuredocs.org/clojure_core/1.2.0/clojure.test/deftest) (is (= 4 (+ 2 2))) --- mode/clojure/clojure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/clojure/clojure.js b/mode/clojure/clojure.js index ce389927db..84f6073fd5 100644 --- a/mode/clojure/clojure.js +++ b/mode/clojure/clojure.js @@ -70,7 +70,7 @@ CodeMirror.defineMode("clojure", function (config, mode) { } // leading sign - if ( ch == '+' || ch == '-' ) { + if ( ( ch == '+' || ch == '-' ) && ( tests.digit.test(stream.peek()) ) ) { stream.eat(tests.sign); ch = stream.next(); } From 3e4a4a4455e08e12ab5b84f0c503044564987eb5 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 3 Sep 2012 10:38:23 +0200 Subject: [PATCH 014/135] Tweak context menu hack to make select-all reliable on FF Issue #755 --- lib/codemirror.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index d020d6de78..56c29936d2 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1758,18 +1758,20 @@ window.CodeMirror = (function() { "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " + "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; focusInput(); - selectInput(input); - clearTimeout(detectingSelectAll); + resetInput(true); + // Adds "Select all" to context menu in FF + if (posEq(sel.from, sel.to)) input.value = prevInput = " "; + function rehide() { inputDiv.style.position = "relative"; input.style.cssText = oldCSS; if (ie_lt9) scrollbar.scrollTop = scrollPos; - resetInput(true); slowPoll(); // Try to detect the user choosing select-all if (input.selectionStart != null) { - var extval = input.value = " " + input.value, i = 0; + clearTimeout(detectingSelectAll); + var extval = input.value = " " + (posEq(sel.from, sel.to) ? "" : input.value), i = 0; prevInput = " "; input.selectionStart = 1; input.selectionEnd = extval.length; detectingSelectAll = setTimeout(function poll(){ From 4ac5b7aec8c998e21612796e18bfeb9a8aa7f393 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 3 Sep 2012 13:09:25 +0200 Subject: [PATCH 015/135] Fix corruption of operation data introduced by 6ba24813c96687dac2f23db100780bc42e6ed808 --- lib/codemirror.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 56c29936d2..8eae1226e6 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -933,7 +933,7 @@ window.CodeMirror = (function() { if (!focused || hasSelection(input) || options.readOnly) return false; var text = input.value; if (text == prevInput) return false; - startOperation(); + if (!nestedOperation) startOperation(); shiftSelecting = null; var same = 0, l = Math.min(prevInput.length, text.length); while (same < l && prevInput[same] == text[same]) ++same; @@ -944,7 +944,7 @@ window.CodeMirror = (function() { replaceSelection(text.slice(same), "end"); if (text.length > 1000) { input.value = prevInput = ""; } else prevInput = text; - endOperation(); + if (!nestedOperation) endOperation(); return true; } function resetInput(user) { From aeccaf59af5a893de333a32e482674d64fc1a699 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 3 Sep 2012 14:41:57 +0200 Subject: [PATCH 016/135] Add CodeMirror.version to API --- doc/manual.html | 16 +++++++-- lib/codemirror.js | 87 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 4 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index 21e6cbdcbd..05ef22030e 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -777,13 +777,23 @@

    Programming API

    contextual information for a line. -

    Finally, the CodeMirror object - itself has a method fromTextArea. This takes a +

    The CodeMirror object itself provides + several useful properties. Firstly, its version + property contains a string that indicates the version of the + library. For releases, this simply + contains "major.minor" (for + example "2.33". For beta versions, " B" + (space, capital B) is added at the end of the string, for + development snapshots, " +" (space, plus) is + added.

    + +

    The CodeMirror.fromTextArea + method provides another way to initialize an editor. It takes a textarea DOM node as first argument and an optional configuration object as second. It will replace the textarea with a CodeMirror instance, and wire up the form of that textarea (if any) to make sure the editor contents are put into the textarea when the form - is submitted. A CodeMirror instance created this way has two + is submitted. A CodeMirror instance created this way has three additional methods:

    diff --git a/lib/codemirror.js b/lib/codemirror.js index 8eae1226e6..8c2641b5d7 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3069,7 +3069,6 @@ window.CodeMirror = (function() { e.appendChild(document.createTextNode(str)); } else e.textContent = str; } - CodeMirror.setTextContent = setTextContent; // Used to position the cursor after an undo/redo by finding the // last edited character. @@ -3144,5 +3143,91 @@ window.CodeMirror = (function() { for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; })(); +<<<<<<< HEAD +======= + function iterateBidiSections(order, from, to, f) { + if (!order) return f(from, to, "ltr"); + for (var i = 0; i < order.length; ++i) { + var part = order[i]; + if (part.from < to && part.to > from) + f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); + } + } + + function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } + function bidiRight(part) { return part.level % 2 ? part.from : part.to; } + + function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; } + function lineRight(line) { + var order = getOrder(line); + if (!order) return line.text.length; + var last = order[order.length-1]; + return order[0].level != last.level ? line.text.length : bidiRight(last); + } + function lineStart(line) { + var order = getOrder(line); + if (!order) return 0; + return order[0].level % 2 ? lineRight(line) : lineLeft(line); + } + function lineEnd(line) { + var order = getOrder(line); + if (!order) return line.text.length; + return order[0].level % 2 ? lineLeft(line) : lineRight(line); + } + + var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F]/; + + // This is somewhat involved. It is needed in order to move + // 'visually' through bi-directional text -- i.e., pressing left + // should make the cursor go left, even when in RTL text. The + // tricky part is the 'jumps', where RTL and LTR text touch each + // other. This often requires the cursor offset to move more than + // one unit, in order to visually move one unit. + function moveVisually(line, start, dir, byUnit) { + var bidi = getOrder(line); + if (!bidi) return moveLogically(line, start, dir, byUnit); + var moveOneUnit = byUnit ? function(pos, dir) { + do pos += dir; + while (pos > 0 && isExtendingChar.test(line.text.charAt(pos))); + return pos; + } : function(pos, dir) { return pos + dir; }; + var linedir = bidi[0].level, last = bidi[bidi.length-1]; + if (linedir == last.level % 2) last = null; + for (var i = 0; i < bidi.length; ++i) { + var part = bidi[i], sticky = part.level % 2 == linedir; + if ((part.from < start && part.to > start) || + (sticky && (part.from == start || part.to == start))) break; + } + var target = moveOneUnit(start, part.level % 2 ? -dir : dir); + if (i == bidi.length) { + if (dir > 0) return null; // Moving right from EOL + target = last.level % 1 ? moveOneUnit(last.from, 1) : moveOneUnit(last.to, -1); + if (moveOneUnit(last.from, 1) == last.to) return bidiRight(bidi[i-2]); + else return target; + } + + while (target != null) { + if (part.level % 2 == linedir) { + if (target < part.from || target > part.to) { + part = bidi[i += dir]; + target = part && (dir > 0 == part.level % 2 ? moveOneUnit(part.to, -1) : moveOneUnit(part.from, 1)); + } else break; + } else { + if (target == bidiLeft(part)) { + part = bidi[--i]; + target = part && bidiRight(part); + } else if (target == bidiRight(part)) { + if (part == last) return line.text.length; + part = bidi[++i]; + target = part && bidiLeft(part); + } else break; + } + } + + return target < 0 || target > line.text.length ? null : target; + } + + CodeMirror.version = "2.33 +"; + return CodeMirror; })(); From 79fa0bdbca40d9b275e3a805e9c3d0a033169a6d Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 3 Sep 2012 14:46:55 +0200 Subject: [PATCH 017/135] Remove accidentally committed conflict junk --- lib/codemirror.js | 84 ----------------------------------------------- 1 file changed, 84 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 8c2641b5d7..eba48afca7 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3143,90 +3143,6 @@ window.CodeMirror = (function() { for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; })(); -<<<<<<< HEAD -======= - function iterateBidiSections(order, from, to, f) { - if (!order) return f(from, to, "ltr"); - for (var i = 0; i < order.length; ++i) { - var part = order[i]; - if (part.from < to && part.to > from) - f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); - } - } - - function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } - function bidiRight(part) { return part.level % 2 ? part.from : part.to; } - - function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; } - function lineRight(line) { - var order = getOrder(line); - if (!order) return line.text.length; - var last = order[order.length-1]; - return order[0].level != last.level ? line.text.length : bidiRight(last); - } - function lineStart(line) { - var order = getOrder(line); - if (!order) return 0; - return order[0].level % 2 ? lineRight(line) : lineLeft(line); - } - function lineEnd(line) { - var order = getOrder(line); - if (!order) return line.text.length; - return order[0].level % 2 ? lineLeft(line) : lineRight(line); - } - - var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F]/; - - // This is somewhat involved. It is needed in order to move - // 'visually' through bi-directional text -- i.e., pressing left - // should make the cursor go left, even when in RTL text. The - // tricky part is the 'jumps', where RTL and LTR text touch each - // other. This often requires the cursor offset to move more than - // one unit, in order to visually move one unit. - function moveVisually(line, start, dir, byUnit) { - var bidi = getOrder(line); - if (!bidi) return moveLogically(line, start, dir, byUnit); - var moveOneUnit = byUnit ? function(pos, dir) { - do pos += dir; - while (pos > 0 && isExtendingChar.test(line.text.charAt(pos))); - return pos; - } : function(pos, dir) { return pos + dir; }; - var linedir = bidi[0].level, last = bidi[bidi.length-1]; - if (linedir == last.level % 2) last = null; - for (var i = 0; i < bidi.length; ++i) { - var part = bidi[i], sticky = part.level % 2 == linedir; - if ((part.from < start && part.to > start) || - (sticky && (part.from == start || part.to == start))) break; - } - var target = moveOneUnit(start, part.level % 2 ? -dir : dir); - if (i == bidi.length) { - if (dir > 0) return null; // Moving right from EOL - target = last.level % 1 ? moveOneUnit(last.from, 1) : moveOneUnit(last.to, -1); - if (moveOneUnit(last.from, 1) == last.to) return bidiRight(bidi[i-2]); - else return target; - } - - while (target != null) { - if (part.level % 2 == linedir) { - if (target < part.from || target > part.to) { - part = bidi[i += dir]; - target = part && (dir > 0 == part.level % 2 ? moveOneUnit(part.to, -1) : moveOneUnit(part.from, 1)); - } else break; - } else { - if (target == bidiLeft(part)) { - part = bidi[--i]; - target = part && bidiRight(part); - } else if (target == bidiRight(part)) { - if (part == last) return line.text.length; - part = bidi[++i]; - target = part && bidiLeft(part); - } else break; - } - } - - return target < 0 || target > line.text.length ? null : target; - } - CodeMirror.version = "2.33 +"; return CodeMirror; From 9adaafac79e948d00eeda7f4c34cb4cb1194bed8 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 4 Sep 2012 09:41:04 +0200 Subject: [PATCH 018/135] [shell mode] Fix unsafe use of object Parsing the word constructor would look up words["constructor"], and return Object.prototype.constructor instead of a style string. Closes #793 --- mode/shell/shell.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/shell/shell.js b/mode/shell/shell.js index 1ad898303e..d4eba852ba 100644 --- a/mode/shell/shell.js +++ b/mode/shell/shell.js @@ -60,7 +60,7 @@ CodeMirror.defineMode('shell', function(config) { stream.eatWhile(/\w/); var cur = stream.current(); if (stream.peek() === '=' && /\w+/.test(cur)) return 'def'; - return words[cur] || null; + return words.hasOwnProperty(cur) ? words[cur] : null; } function tokenString(quote) { From 2189810311969f270c8e53179f6e8464c1aa63ad Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Mon, 3 Sep 2012 05:54:29 -0400 Subject: [PATCH 019/135] [markdown mode] Improvements to parsing and tester MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (Squashed commit. Below are the original commit messages:) - Add highlighting test for Markdown for consecutive backticks. - Make it possible to wrap inline code in multiple backticks. - Add highlighting test for Markdown mode to only allow a single space to separate brackets. - Make it possible to separate bracket groups with a space. - Allow for horizontal rules to use dashes. - Allow for *any* number of -'s or ='s for setex headers. - Remove requirement that the text for setext headers have to be highlighted. - Might revisit someday to work on this, but in the meantime, it's better to prevent regressions than to be picky about what would be highlighted in an ideal world. - Correctly match single line footnotes with title. - Match inline links in Markdown mode. - Don't match unclosed code blocks. - Get mode test highlighter in line with CodeMirror.highlight(). - It would be nice to use all native functions, to avoid having to update this manually each time. - Fix tests per fixes made to mode test highlighter. - Properly handle multiple classes for mode tests. - More correct handling of EM and STRONG in Markdown mode. Per Markdown documentation: "You can use whichever style you prefer; the lone restriction is that the same character must be used to open and close an emphasis span." # This is the 15th commit message: - Allow escaping of * and _ by surrounding with spaces. Per Markdown documentation: "But if you surround an * or _ with spaces, it’ll be treated as a literal asterisk or underscore." - More correct support for inline code blocks. - Adjust Markdown highlighting tests to fit readability, rather than 100% conformance with Markdown documentation. Although unclosed italics, bold, and inline code would not be formatted by a Markdown parser, it is extremely helpful to have the styles added even when it is not yet closed. It may be a good idea to add an 'incomplete' flag for each at some point, in order to style it (and mark it) as different. Perhaps piggyback off of "error". --- mode/markdown/markdown.js | 101 ++++++++++++++++--- mode/markdown/test.html | 203 +++++++++++++++++++------------------- mode/stex/test.html | 77 +++++---------- test/mode_test.js | 23 ++++- 4 files changed, 231 insertions(+), 173 deletions(-) diff --git a/mode/markdown/markdown.js b/mode/markdown/markdown.js index 9eab617573..97ca1b17c4 100644 --- a/mode/markdown/markdown.js +++ b/mode/markdown/markdown.js @@ -2,12 +2,18 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { var htmlFound = CodeMirror.mimeModes.hasOwnProperty("text/html"); var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? "text/html" : "text/plain"); + + var codeDepth = 0; + var prevLineHasContent = false + , thisLineHasContent = false; var header = 'header' , code = 'comment' , quote = 'quote' , list = 'string' , hr = 'hr' + , linkinline = 'link' + , linkemail = 'link' , linktext = 'link' , linkhref = 'string' , em = 'em' @@ -17,8 +23,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { var hrRE = /^([*\-=_])(?:\s*\1){2,}\s*$/ , ulRE = /^[*\-+]\s+/ , olRE = /^[0-9]+\.\s+/ - , headerRE = /^(?:\={3,}|-{3,})$/ - , textRE = /^[^\[*_\\<>`]+/; + , headerRE = /^(?:\={1,}|-{1,})$/ + , textRE = /^[^\[*_\\<>` ]+/; function switchInline(stream, state, f) { state.f = state.inline = f; @@ -34,6 +40,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { // Blocks function blankLine(state) { + // Reset CODE state + state.code = false; // Reset EM state state.em = false; // Reset STRONG state @@ -55,7 +63,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { return code; } else if (stream.eatSpace()) { return null; - } else if (stream.peek() === '#' || stream.match(headerRE)) { + } else if (stream.peek() === '#' || (prevLineHasContent && stream.match(headerRE)) ) { state.header = true; } else if (stream.eat('>')) { state.indentation++; @@ -94,6 +102,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { if (state.strong) { styles.push(state.em ? emstrong : strong); } else if (state.em) { styles.push(em); } + if (state.code) { styles.push(code); } + if (state.header) { styles.push(header); } if (state.quote) { styles.push(quote); } @@ -118,12 +128,39 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { stream.next(); return getType(state); } + if (ch === '`') { - return switchInline(stream, state, inlineElement(code, '`')); + var t = getType(state); + var before = stream.pos; + stream.eatWhile('`'); + var difference = 1 + stream.pos - before; + if (!state.code) { + codeDepth = difference; + state.code = true; + return getType(state); + } else { + if (difference === codeDepth) { // Must be exact + state.code = false; + return t; + } + return getType(state); + } + } else if (state.code) { + return getType(state); } - if (ch === '[' && stream.match(/.*\](?:\(|\[)/, false)) { + + if (ch === '[' && stream.match(/.*\] ?(?:\(|\[)/, false)) { return switchInline(stream, state, linkText); } + + if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, true)) { + return switchInline(stream, state, inlineElement(linkinline, '>')); + } + + if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, true)) { + return switchInline(stream, state, inlineElement(linkemail, '>')); + } + if (ch === '<' && stream.match(/^\w/, false)) { var md_inside = false; if (stream.string.indexOf(">")!=-1) { @@ -143,10 +180,27 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { var t = getType(state); if (ch === '*' || ch === '_') { - if (stream.eat(ch)) { - return (state.strong = !state.strong) ? getType(state) : t; + if (state.strong === ch && stream.eat(ch)) { // Remove STRONG + state.strong = false; + return t; + } else if (!state.strong && stream.eat(ch)) { // Add STRONG + state.strong = ch; + return getType(state); + } else if (state.em === ch) { // Remove EM + state.em = false; + return t; + } else if (!state.em) { // Add EM + state.em = ch; + return getType(state); + } + } else if (ch === ' ') { + if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces + if (stream.peek() === ' ') { // Surrounded by spaces, ignore + return getType(state); + } else { // Not surrounded by spaces, back up pointer + stream.backUp(1); + } } - return (state.em = !state.em) ? getType(state) : t; } return getType(state); @@ -165,7 +219,10 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { } function linkHref(stream, state) { - stream.eatSpace(); + // Check if space, and return NULL if so (to avoid marking the space) + if(stream.eatSpace()){ + return null; + } var ch = stream.next(); if (ch === '(' || ch === '[') { return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']')); @@ -182,17 +239,22 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { } function footnoteUrl(stream, state) { - stream.eatSpace(); - stream.match(/^[^\s]+/, true); + // Check if space, and return NULL if so (to avoid marking the space) + if(stream.eatSpace()){ + return null; + } + stream.match(/^[^\s]+(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true); state.f = state.inline = inlineNormal; return linkhref; } function inlineRE(endChar) { if (!inlineRE[endChar]) { - // match any not-escaped-non-endChar and any escaped char - // then match endChar or eol - inlineRE[endChar] = new RegExp('^(?:[^\\\\\\' + endChar + ']|\\\\.)*(?:\\' + endChar + '|$)'); + // Escape endChar for RegExp (taken from http://stackoverflow.com/a/494122/526741) + endChar = (endChar+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); + // Match any non-endChar, escaped character, as well as the closing + // endChar. + inlineRE[endChar] = new RegExp('^(?:[^\\\\]+?|\\\\.)*?(' + endChar + ')'); } return inlineRE[endChar]; } @@ -244,7 +306,16 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { token: function(stream, state) { if (stream.sol()) { - if (stream.match(/^\s*$/, true)) { return blankLine(state); } + if (stream.match(/^\s*$/, true)) { + prevLineHasContent = false; + return blankLine(state); + } else { + if(thisLineHasContent){ + prevLineHasContent = true; + thisLineHasContent = false; + } + thisLineHasContent = true; + } // Reset state.header state.header = false; diff --git a/mode/markdown/test.html b/mode/markdown/test.html index 3f4e9e0c90..a9ab9d2fc8 100644 --- a/mode/markdown/test.html +++ b/mode/markdown/test.html @@ -42,16 +42,36 @@

    Code

    'comment', '`bar`'); // Unclosed backticks - // This should *not* be fixed by only adding the style to closed groups. - // Instead, autocomplete should be added (see issue #344). + // Instead of simply marking as CODE, it would be nice to have an + // incomplete flag for CODE, that is styled slightly different. MT.test('foo `bar', - null, 'foo `bar'); + null, 'foo ', + 'comment', '`bar'); // Per documentation: "To include a literal backtick character within a // code span, you can use multiple backticks as the opening and closing // delimiters" MT.test('``foo ` bar``', 'comment', '``foo ` bar``'); + + // Tests based on Dingus + // http://daringfireball.net/projects/markdown/dingus + // + // Multiple backticks within an inline code block + MT.test('`foo```bar`', + 'comment', '`foo```bar`'); + // Multiple backticks within an inline code block with a second code block + MT.test('`foo```bar` hello `world`', + 'comment', '`foo```bar`', + null, ' hello ', + 'comment', '`world`'); + // Unclosed with several different groups of backticks + MT.test('``foo ``` bar` hello', + 'comment', '``foo ``` bar` hello'); + // Closed with several different groups of backticks + MT.test('``foo ``` bar` hello`` world', + 'comment', '``foo ``` bar` hello``', + null, ' world');

    Headers

    @@ -86,22 +106,25 @@

    Headers

    // Setext headers - H1, H2 // Per documentation, "Any number of underlining =’s or -’s will work." // http://daringfireball.net/projects/markdown/syntax#header + // Ideally, the text would be marked as `header` as well, but this is + // not really feasible at the moment. So, instead, we're testing against + // what works today, to avoid any regressions. // // Check if single underlining = works MT.test('foo\n=', - 'header', 'foo', + null, 'foo', 'header', '='); // Check if 3+ ='s work MT.test('foo\n===', - 'header', 'foo', + null, 'foo', 'header', '==='); // Check if single underlining - works MT.test('foo\n-', - 'header', 'foo', + null, 'foo', 'header', '-'); // Check if 3+ -'s work MT.test('foo\n---', - 'header', 'foo', + null, 'foo', 'header', '---'); @@ -193,6 +216,9 @@

    Links

    null, ' ', 'string', '[bar]', null, ' hello'); + // Should only allow a single space ("...use *a* space...") + MT.test('[foo] [bar] hello', + null, '[foo] [bar] hello'); // Reference-style links with implicit link name MT.test('[foo][] hello', @@ -208,46 +234,61 @@

    Links

    // No title MT.test('[foo]: http://example.com/', 'link', '[foo]:', - 'string', ' http://example.com/'); - // Space in ID + null, ' ', + 'string', 'http://example.com/'); + // Space in ID and title MT.test('[foo bar]: http://example.com/ "hello"', 'link', '[foo bar]:', - 'string', ' http://example.com/', - 'string', ' "hello"'); + null, ' ', + 'string', 'http://example.com/ "hello"'); + // Double title + MT.test('[foo bar]: http://example.com/ "hello" "world"', + 'link', '[foo bar]:', + null, ' ', + 'string', 'http://example.com/ "hello"', + null, ' "world"'); // Double quotes around title MT.test('[foo]: http://example.com/ "bar"', 'link', '[foo]:', - 'string', ' http://example.com/ "bar"'); + null, ' ', + 'string', 'http://example.com/ "bar"'); // Single quotes around title MT.test('[foo]: http://example.com/ \'bar\'', 'link', '[foo]:', - 'string', ' http://example.com/ \'bar\''); + null, ' ', + 'string', 'http://example.com/ \'bar\''); // Parentheses around title MT.test('[foo]: http://example.com/ (bar)', 'link', '[foo]:', - 'string', ' http://example.com/ (bar)'); + null, ' ', + 'string', 'http://example.com/ (bar)'); // Invalid title MT.test('[foo]: http://example.com/ bar', 'link', '[foo]:', - 'string', ' http://example.com/', + null, ' ', + 'string', 'http://example.com/', null, ' bar'); // Angle brackets around URL MT.test('[foo]: "bar"', 'link', '[foo]:', - 'string', ' "bar"'); + null, ' ', + 'string', ' "bar"'); // Title on next line per documentation MT.test('[foo]: http://example.com/\n"bar"', 'link', '[foo]:', - 'string', ' http://example.com/', + null, ' ', + 'string', 'http://example.com/', 'string', '"bar"'); // Automatic links - MT.test('', - 'link', ''); + MT.test(' foo', + 'link', '', + null, ' foo'); // Automatic email links - MT.test('', - 'link', ''); + MT.test(' foo', + 'link', '', + null, ' foo');

    Emphasis

    @@ -255,96 +296,80 @@

    Emphasis

    // Single asterisk MT.test('*foo* bar', - 'em', '*', - 'em', 'foo', - 'em', '*', + 'em', '*foo*', null, ' bar'); // Single underscore MT.test('_foo_ bar', - 'em', '_', - 'em', 'foo', - 'em', '_', + 'em', '_foo_', null, ' bar'); // Emphasis characters within a word MT.test('foo*bar*hello', null, 'foo', - 'em', '*', - 'em', 'bar', - 'em', '*', + 'em', '*bar*', null, 'hello'); MT.test('foo_bar_hello', null, 'foo', - 'em', '_', - 'em', 'bar', - 'em', '_', + 'em', '_bar_', null, 'hello'); // Per documentation: "...surround an * or _ with spaces, it’ll be // treated as a literal asterisk or underscore." + // + // Inside EM MT.test('foo _bar _ hello_ world', - null, 'foo', - 'em', '_', - 'em', 'bar _ hello', - 'em', '_', + null, 'foo ', + 'em', '_bar _ hello_', + null, ' world'); + // Outside EM + MT.test('foo _ bar_hello_world', + null, 'foo _ bar', + 'em', '_hello_', null, 'world'); // Unclosed emphasis characters - // This should *not* be fixed by only adding the style to closed groups. - // Instead, autocomplete should be added (see issue #344). + // Instead of simply marking as EM / STRONG, it would be nice to have an + // incomplete flag for EM and STRONG, that is styled slightly different. MT.test('foo *bar', - null, 'foo *bar'); + null, 'foo ', + 'em', '*bar'); MT.test('foo _bar', - null, 'foo _bar'); + null, 'foo ', + 'em', '_bar'); // Double asterisk MT.test('**foo** bar', - 'strong', '**', - 'strong', 'foo', - 'strong', '**', + 'strong', '**foo**', null, ' bar'); // Double underscore MT.test('__foo__ bar', - 'strong', '__', - 'strong', 'foo', - 'strong', '__', + 'strong', '__foo__', null, ' bar'); // Triple asterisk MT.test('*foo**bar*hello** world', - 'em', '*', - 'em', 'foo', - 'emstrong', '**', - 'emstrong', 'bar', - 'emstrong', '*', - 'strong', 'hello', - 'strong', '**', + 'em', '*foo', + 'emstrong', '**bar*', + 'strong', 'hello**', null, ' world'); // Triple underscore MT.test('_foo__bar_hello__ world', - 'em', '_', - 'em', 'foo', - 'emstrong', '__', - 'emstrong', 'bar', - 'emstrong', '_', - 'strong', 'hello', - 'strong', '__', + 'em', '_foo', + 'emstrong', '__bar_', + 'strong', 'hello__', null, ' world'); // Triple mixed // "...same character must be used to open and close an emphasis span."" MT.test('_foo**bar*hello__ world', - 'em', '_', - 'em', 'foo**bar*hello', - 'em', '_', - null, '_ world'); + 'em', '_foo', + 'emstrong', '**bar*hello__ world'); + MT.test('*foo__bar_hello** world', - 'em', '*', - 'em', 'foo__bar_hello', - 'em', '*', - null, '* world'); + 'em', '*foo', + 'emstrong', '__bar_hello** world');

    Escaping

    @@ -366,47 +391,27 @@

    Escaping

    // // Backtick (code) MT.test('foo \\`bar\\`', - null, 'foo ', - null, '\\`', - null, 'bar', - null, '\\`'); + null, 'foo \\`bar\\`'); MT.test('foo \\\\`bar\\\\`', - null, 'foo ', - null, '\\\\', + null, 'foo \\\\', 'comment', '`bar\\\\`'); // Asterisk (em) MT.test('foo \\*bar\\*', - null, 'foo ', - null, '\\*', - null, 'bar', - null, '\\*'); + null, 'foo \\*bar\\*'); MT.test('foo \\\\*bar\\\\*', - null, 'foo ', - null, '\\\\', - 'em', '*', - 'em', 'bar', - 'em', '\\\\', - 'em', '*'); + null, 'foo \\\\', + 'em', '*bar\\\\*'); // Underscore (em) MT.test('foo \\_bar\\_', - null, 'foo ', - null, '\\_', - null, 'bar', - null, '\\_'); + null, 'foo \\_bar\\_'); MT.test('foo \\\\_bar\\\\_', - null, 'foo ', - null, '\\\\', - 'em', '_', - 'em', 'bar', - 'em', '\\\\', - 'em', '_'); + null, 'foo \\\\', + 'em', '_bar\\\\_'); // Hash mark (headers) MT.test('\\# foo', - null, '\\#', - null, ' foo'); + null, '\\# foo'); MT.test('\\\\# foo', - null, '\\\\', - null, '# foo'); + null, '\\\\# foo');

    Summary

    diff --git a/mode/stex/test.html b/mode/stex/test.html index 599e592dde..c63b3cbc31 100644 --- a/mode/stex/test.html +++ b/mode/stex/test.html @@ -20,8 +20,7 @@

    Basics

    null, 'foo'); MT.test('foo bar', - null, 'foo', - null, ' bar'); + null, 'foo bar');

    Tags

    @@ -41,11 +40,7 @@

    Tags

    'bracket', '{', 'atom', 'equation', 'bracket', '}', - null, ' ', - null, ' ', - null, 'E', - null, '=mc', - null, '^2', + null, ' E=mc^2', 'tag', '\\end', 'bracket', '{', 'atom', 'equation', @@ -55,26 +50,21 @@

    Tags

    'tag', '\\begin', 'bracket', '{', 'atom', 'module', - 'bracket', '}', - 'bracket', '[', - 'bracket', ']'); + 'bracket', '}[]'); MT.test('\\begin{module}[id=bbt-size]', 'tag', '\\begin', 'bracket', '{', 'atom', 'module', - 'bracket', '}', - 'bracket', '[', - null, 'id', - null, '=bbt-size', + 'bracket', '}[', + null, 'id=bbt-size', 'bracket', ']'); MT.test('\\importmodule[b-b-t]{b-b-t}', 'tag', '\\importmodule', 'bracket', '[', 'string', 'b-b-t', - 'bracket', ']', - 'bracket', '{', + 'bracket', ']{', 'builtin', 'b-b-t', 'bracket', '}'); @@ -83,12 +73,8 @@

    Tags

    'bracket', '[', 'tag', '\\KWARCslides', 'bracket', '{', - 'string', 'dmath', - 'string', '/en', - 'string', '/cardinality', - 'bracket', '}', - 'bracket', ']', - 'bracket', '{', + 'string', 'dmath/en/cardinality', + 'bracket', '}]{', 'builtin', 'card', 'bracket', '}'); @@ -96,8 +82,7 @@

    Tags

    'tag', '\\PSforPDF', 'bracket', '[', 'atom', '1', - 'bracket', ']', - 'bracket', '{', + 'bracket', ']{', null, '#1', 'bracket', '}'); @@ -105,18 +90,15 @@

    Tags

    Comments

    Character Escapes

    diff --git a/test/mode_test.js b/test/mode_test.js index d77ac143f8..1479bd9280 100644 --- a/test/mode_test.js +++ b/test/mode_test.js @@ -85,11 +85,26 @@ ModeTest.highlight = function(string, mode) { var line = lines[i]; var stream = new CodeMirror.StringStream(line); if (line == "" && mode.blankLine) mode.blankLine(state); + var pos = 0; + var st = []; + /* Start copied code from CodeMirror.highlight */ while (!stream.eol()) { - var style = mode.token(stream, state); - var substr = line.slice(stream.start, stream.pos); - output.push([style, substr]); + var style = mode.token(stream, state), substr = stream.current(); stream.start = stream.pos; + if (pos && st[pos-1] == style) { + st[pos-2] += substr; + } else if (substr) { + st[pos++] = substr; st[pos++] = style; + } + // Give up when line is ridiculously long + if (stream.pos > 5000) { + st[pos++] = this.text.slice(stream.pos); st[pos++] = null; + break; + } + } + /* End copied code from CodeMirror.highlight */ + for (var x = 0; x < st.length; x += 2) { + output.push([st[x + 1], st[x]]); } } @@ -131,7 +146,7 @@ ModeTest.prettyPrintOutputTable = function(output) { var token = output[i]; s += '' + - '' + + '' + ModeTest.htmlEscape(token[1]).replace(/ /g,'·') + '' + ''; From 367cfc6c8b5e184c667e6d2d83f6e29fcdac0761 Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Tue, 4 Sep 2012 11:50:57 -0400 Subject: [PATCH 020/135] [markdown mode] More improvements to parsing and tester. Markdown mode - Add additional link tests and correctly match link titles on next line. - Adjust tests code so sections (including "Basics") can be rearranged and removed easily without breaking the script. - Add tests and fix highlighting for lists. Test harness - Sort styles in mode test script so "quote string" = "string quote". --- mode/markdown/markdown.js | 53 ++++++++- mode/markdown/test.html | 231 +++++++++++++++++++++++++++++++++++++- test/mode_test.js | 2 + 3 files changed, 276 insertions(+), 10 deletions(-) diff --git a/mode/markdown/markdown.js b/mode/markdown/markdown.js index 97ca1b17c4..e8b0e0d02f 100644 --- a/mode/markdown/markdown.js +++ b/mode/markdown/markdown.js @@ -24,7 +24,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { , ulRE = /^[*\-+]\s+/ , olRE = /^[0-9]+\.\s+/ , headerRE = /^(?:\={1,}|-{1,})$/ - , textRE = /^[^\[*_\\<>` ]+/; + , textRE = /^[^\[*_\\<>` "'(]+/; function switchInline(stream, state, f) { state.f = state.inline = f; @@ -40,6 +40,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { // Blocks function blankLine(state) { + // Reset linkTitle state + state.linkTitle = false; // Reset CODE state state.code = false; // Reset EM state @@ -57,8 +59,18 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { function blockNormal(stream, state) { var match; + + 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; + } + state.list = null; + } else { // No longer a list + state.list = false; + } + if (state.indentationDiff >= 4) { - state.indentation -= state.indentationDiff; + state.indentation -= 4; stream.skipToEnd(); return code; } else if (stream.eatSpace()) { @@ -73,8 +85,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { } else if (stream.match(hrRE, true)) { return hr; } else if (match = stream.match(ulRE, true) || stream.match(olRE, true)) { - state.indentation += match[0].length; - return list; + state.indentation += 4; + state.list = true; } return switchInline(stream, state, state.inline); @@ -106,6 +118,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { if (state.header) { styles.push(header); } if (state.quote) { styles.push(quote); } + if (state.list !== false) { styles.push(list); } return styles.length ? styles.join(' ') : null; } @@ -122,6 +135,11 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { if (typeof style !== 'undefined') return style; + if (state.list) { // List marker (*, +, -, 1., etc) + state.list = null; + return list; + } + var ch = stream.next(); if (ch === '\\') { @@ -129,6 +147,20 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { return getType(state); } + // Matches link titles present on next line + if (state.linkTitle) { + state.linkTitle = false; + var matchCh = ch; + if (ch === '(') { + matchCh = ')'; + } + matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); + var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh; + if (stream.match(new RegExp(regex), true)) { + return linkhref; + } + } + if (ch === '`') { var t = getType(state); var before = stream.pos; @@ -243,7 +275,14 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { if(stream.eatSpace()){ return null; } - stream.match(/^[^\s]+(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true); + // Match URL + stream.match(/^[^\s]+/, true); + // Check for link title + if (stream.peek() === undefined) { // End of line, set flag to check next line + state.linkTitle = true; + } else { // More content on line, check if link title + stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true); + } state.f = state.inline = inlineNormal; return linkhref; } @@ -279,9 +318,11 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { inline: inlineNormal, text: handleText, + linkTitle: false, em: false, strong: false, header: false, + list: false, quote: false }; }, @@ -296,9 +337,11 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { inline: s.inline, text: s.text, + linkTitle: s.linkTitle, em: s.em, strong: s.strong, header: s.header, + list: s.list, quote: s.quote, md_inside: s.md_inside }; diff --git a/mode/markdown/test.html b/mode/markdown/test.html index a9ab9d2fc8..192fd83042 100644 --- a/mode/markdown/test.html +++ b/mode/markdown/test.html @@ -14,11 +14,14 @@

    Tests for the Markdown Mode

    -

    Basics

    + +

    Basics

    + @@ -167,6 +170,203 @@

    Blockquotes

    null, 'hello'); +

    Lists

    + +

    Horizontal rules

    - - - - - - - - -

    Tests for the Markdown Mode

    - - -

    Basics

    - - -

    Code

    - - -

    Headers

    - - -

    Blockquotes

    - - -

    Lists

    - - -

    Horizontal rules

    - - -

    Links

    - - -

    Emphasis

    - - -

    Escaping

    - - -

    Summary

    - - - - - diff --git a/mode/markdown/test.js b/mode/markdown/test.js new file mode 100644 index 0000000000..46bda7b90d --- /dev/null +++ b/mode/markdown/test.js @@ -0,0 +1,1084 @@ +// Initiate ModeTest and set defaults +var MT = ModeTest; +MT.modeName = 'markdown'; +MT.modeOptions = {}; + +MT.testMode( + 'plainText', + 'foo', + [ + null, 'foo' + ] +); + +// Code blocks using 4 spaces (regardless of CodeMirror.tabSize value) +MT.testMode( + 'codeBlocksUsing4Spaces', + ' foo', + [ + null, ' ', + 'comment', 'foo' + ] +); + +// Code blocks using 1 tab (regardless of CodeMirror.indentWithTabs value) +MT.testMode( + 'codeBlocksUsing1Tab', + '\tfoo', + [ + null, '\t', + 'comment', 'foo' + ] +); + +// Inline code using backticks +MT.testMode( + 'inlineCodeUsingBackticks', + 'foo `bar`', + [ + null, 'foo ', + 'comment', '`bar`' + ] +); + +// Unclosed backticks +// Instead of simply marking as CODE, it would be nice to have an +// incomplete flag for CODE, that is styled slightly different. +MT.testMode( + 'unclosedBackticks', + 'foo `bar', + [ + null, 'foo ', + 'comment', '`bar' + ] +); + +// Per documentation: "To include a literal backtick character within a +// code span, you can use multiple backticks as the opening and closing +// delimiters" +MT.testMode( + 'doubleBackticks', + '``foo ` bar``', + [ + 'comment', '``foo ` bar``' + ] +); + +// Tests based on Dingus +// http://daringfireball.net/projects/markdown/dingus +// +// Multiple backticks within an inline code block +MT.testMode( + 'consecutiveBackticks', + '`foo```bar`', + [ + 'comment', '`foo```bar`' + ] +); +// Multiple backticks within an inline code block with a second code block +MT.testMode( + 'consecutiveBackticks', + '`foo```bar` hello `world`', + [ + 'comment', '`foo```bar`', + null, ' hello ', + 'comment', '`world`' + ] +); +// Unclosed with several different groups of backticks +MT.testMode( + 'unclosedBackticks', + '``foo ``` bar` hello', + [ + 'comment', '``foo ``` bar` hello' + ] +); +// Closed with several different groups of backticks +MT.testMode( + 'closedBackticks', + '``foo ``` bar` hello`` world', + [ + 'comment', '``foo ``` bar` hello``', + null, ' world' + ] +); + +// atx headers +// http://daringfireball.net/projects/markdown/syntax#header +// +// H1 +MT.testMode( + 'atxH1', + '# foo', + [ + 'header', '# foo' + ] +); +// H2 +MT.testMode( + 'atxH2', + '## foo', + [ + 'header', '## foo' + ] +); +// H3 +MT.testMode( + 'atxH3', + '### foo', + [ + 'header', '### foo' + ] +); +// H4 +MT.testMode( + 'atxH4', + '#### foo', + [ + 'header', '#### foo' + ] +); +// H5 +MT.testMode( + 'atxH5', + '##### foo', + [ + 'header', '##### foo' + ] +); +// H6 +MT.testMode( + 'atxH6', + '###### foo', + [ + 'header', '###### foo' + ] +); +// H6 - 7x '#' should still be H6, per Dingus +// http://daringfireball.net/projects/markdown/dingus +MT.testMode( + 'atxH6NotH7', + '####### foo', + [ + 'header', '####### foo' + ] +); + +// Setext headers - H1, H2 +// Per documentation, "Any number of underlining =’s or -’s will work." +// http://daringfireball.net/projects/markdown/syntax#header +// Ideally, the text would be marked as `header` as well, but this is +// not really feasible at the moment. So, instead, we're testing against +// what works today, to avoid any regressions. +// +// Check if single underlining = works +MT.testMode( + 'setextH1', + 'foo\n=', + [ + null, 'foo', + 'header', '=' + ] +); +// Check if 3+ ='s work +MT.testMode( + 'setextH1', + 'foo\n===', + [ + null, 'foo', + 'header', '===' + ] +); +// Check if single underlining - works +MT.testMode( + 'setextH2', + 'foo\n-', + [ + null, 'foo', + 'header', '-' + ] +); +// Check if 3+ -'s work +MT.testMode( + 'setextH2', + 'foo\n---', + [ + null, 'foo', + 'header', '---' + ] +); + +// Single-line blockquote with trailing space +MT.testMode( + 'blockquoteSpace', + '> foo', + [ + 'quote', '> foo' + ] +); + +// Single-line blockquote +MT.testMode( + 'blockquoteNoSpace', + '>foo', + [ + 'quote', '>foo' + ] +); + +// Single-line blockquote followed by normal paragraph +MT.testMode( + 'blockquoteThenParagraph', + '>foo\n\nbar', + [ + 'quote', '>foo', + null, 'bar' + ] +); + +// Multi-line blockquote (lazy mode) +MT.testMode( + 'multiBlockquoteLazy', + '>foo\nbar', + [ + 'quote', '>foo', + 'quote', 'bar' + ] +); + +// Multi-line blockquote followed by normal paragraph (lazy mode) +MT.testMode( + 'multiBlockquoteLazyThenParagraph', + '>foo\nbar\n\nhello', + [ + 'quote', '>foo', + 'quote', 'bar', + null, 'hello' + ] +); + +// Multi-line blockquote (non-lazy mode) +MT.testMode( + 'multiBlockquote', + '>foo\n>bar', + [ + 'quote', '>foo', + 'quote', '>bar' + ] +); + +// Multi-line blockquote followed by normal paragraph (non-lazy mode) +MT.testMode( + 'multiBlockquoteThenParagraph', + '>foo\n>bar\n\nhello', + [ + 'quote', '>foo', + 'quote', '>bar', + null, 'hello' + ] +); + +// Check list types +MT.testMode( + 'listAsterisk', + '* foo\n* bar', + [ + 'string', '* foo', + 'string', '* bar' + ] +); +MT.testMode( + 'listPlus', + '+ foo\n+ bar', + [ + 'string', '+ foo', + 'string', '+ bar' + ] +); +MT.testMode( + 'listDash', + '- foo\n- bar', + [ + 'string', '- foo', + 'string', '- bar' + ] +); +MT.testMode( + 'listNumber', + '1. foo\n2. bar', + [ + 'string', '1. foo', + 'string', '2. bar' + ] +); + +// Formatting in lists (*) +MT.testMode( + 'listAsteriskFormatting', + '* *foo* bar\n\n* **foo** bar\n\n* ***foo*** bar\n\n* `foo` bar', + [ + 'string', '* ', + 'string em', '*foo*', + 'string', ' bar', + 'string', '* ', + 'string strong', '**foo**', + 'string', ' bar', + 'string', '* ', + 'string strong', '**', + 'string emstrong', '*foo**', + 'string em', '*', + 'string', ' bar', + 'string', '* ', + 'string comment', '`foo`', + 'string', ' bar' + ] +); +// Formatting in lists (+) +MT.testMode( + 'listPlusFormatting', + '+ *foo* bar\n\n+ **foo** bar\n\n+ ***foo*** bar\n\n+ `foo` bar', + [ + 'string', '+ ', + 'string em', '*foo*', + 'string', ' bar', + 'string', '+ ', + 'string strong', '**foo**', + 'string', ' bar', + 'string', '+ ', + 'string strong', '**', + 'string emstrong', '*foo**', + 'string em', '*', + 'string', ' bar', + 'string', '+ ', + 'string comment', '`foo`', + 'string', ' bar' + ] +); +// Formatting in lists (-) +MT.testMode( + 'listDashFormatting', + '- *foo* bar\n\n- **foo** bar\n\n- ***foo*** bar\n\n- `foo` bar', + [ + 'string', '- ', + 'string em', '*foo*', + 'string', ' bar', + 'string', '- ', + 'string strong', '**foo**', + 'string', ' bar', + 'string', '- ', + 'string strong', '**', + 'string emstrong', '*foo**', + 'string em', '*', + 'string', ' bar', + 'string', '- ', + 'string comment', '`foo`', + 'string', ' bar' + ] +); +// Formatting in lists (1.) +MT.testMode( + 'listNumberFormatting', + '1. *foo* bar\n\n2. **foo** bar\n\n3. ***foo*** bar\n\n4. `foo` bar', + [ + 'string', '1. ', + 'string em', '*foo*', + 'string', ' bar', + 'string', '2. ', + 'string strong', '**foo**', + 'string', ' bar', + 'string', '3. ', + 'string strong', '**', + 'string emstrong', '*foo**', + 'string em', '*', + 'string', ' bar', + 'string', '4. ', + 'string comment', '`foo`', + 'string', ' bar' + ] +); + +// Paragraph lists +MT.testMode( + 'listParagraph', + '* foo\n\n* bar', + [ + 'string', '* foo', + 'string', '* bar' + ] +); + +// Multi-paragraph lists +// +// 4 spaces +MT.testMode( + 'listMultiParagraph', + '* foo\n\n* bar\n\n hello', + [ + 'string', '* foo', + 'string', '* bar', + null, ' ', + 'string', 'hello' + ] +); +// 4 spaces, extra blank lines (should still be list, per Dingus) +MT.testMode( + 'listMultiParagraphExtra', + '* foo\n\n* bar\n\n\n hello', + [ + 'string', '* foo', + 'string', '* bar', + null, ' ', + 'string', 'hello' + ] +); +// 4 spaces, plus 1 space (should still be list, per Dingus) +MT.testMode( + 'listMultiParagraphExtraSpace', + '* foo\n\n* bar\n\n hello\n\n world', + [ + 'string', '* foo', + 'string', '* bar', + null, ' ', + 'string', 'hello', + null, ' ', + 'string', 'world' + ] +); +// 1 tab +MT.testMode( + 'listTab', + '* foo\n\n* bar\n\n\thello', + [ + 'string', '* foo', + 'string', '* bar', + null, '\t', + 'string', 'hello' + ] +); +// No indent +MT.testMode( + 'listNoIndent', + '* foo\n\n* bar\n\nhello', + [ + 'string', '* foo', + 'string', '* bar', + null, 'hello' + ] +); +// Blockquote +MT.testMode( + 'blockquote', + '* foo\n\n* bar\n\n > hello', + [ + 'string', '* foo', + 'string', '* bar', + null, ' ', + 'string quote', '> hello' + ] +); +// Code block +MT.testMode( + 'blockquoteCode', + '* foo\n\n* bar\n\n > hello\n\n world', + [ + 'string', '* foo', + 'string', '* bar', + null, ' ', + 'comment', '> hello', + null, ' ', + 'string', 'world' + ] +); +// Code block followed by text +MT.testMode( + 'blockquoteCodeText', + '* foo\n\n bar\n\n hello\n\n world', + [ + 'string', '* foo', + null, ' ', + 'string', 'bar', + null, ' ', + 'comment', 'hello', + null, ' ', + 'string', 'world' + ] +); + +// Nested list +// +// * +MT.testMode( + 'listAsteriskNested', + '* foo\n\n * bar', + [ + 'string', '* foo', + null, ' ', + 'string', '* bar' + ] +); +// + +MT.testMode( + 'listPlusNested', + '+ foo\n\n + bar', + [ + 'string', '+ foo', + null, ' ', + 'string', '+ bar' + ] +); +// - +MT.testMode( + 'listDashNested', + '- foo\n\n - bar', + [ + 'string', '- foo', + null, ' ', + 'string', '- bar' + ] +); +// 1. +MT.testMode( + 'listNumberNested', + '1. foo\n\n 2. bar', + [ + 'string', '1. foo', + null, ' ', + 'string', '2. bar' + ] +); +// Mixed +MT.testMode( + 'listMixed', + '* foo\n\n + bar\n\n - hello\n\n 1. world', + [ + 'string', '* foo', + null, ' ', + 'string', '+ bar', + null, ' ', + 'string', '- hello', + null, ' ', + 'string', '1. world' + ] +); +// Blockquote +MT.testMode( + 'listBlockquote', + '* foo\n\n + bar\n\n > hello', + [ + 'string', '* foo', + null, ' ', + 'string', '+ bar', + null, ' ', + 'quote string', '> hello' + ] +); +// Code +MT.testMode( + 'listCode', + '* foo\n\n + bar\n\n hello', + [ + 'string', '* foo', + null, ' ', + 'string', '+ bar', + null, ' ', + 'comment', 'hello' + ] +); +// Code followed by text +MT.testMode( + 'listCodeText', + '* foo\n\n bar\n\nhello', + [ + 'string', '* foo', + null, ' ', + 'comment', 'bar', + null, 'hello' + ] +); + +// Following tests directly from official Markdown documentation +// http://daringfireball.net/projects/markdown/syntax#hr +MT.testMode( + 'hrSpace', + '* * *', + [ + 'hr', '* * *' + ] +); + +MT.testMode( + 'hr', + '***', + [ + 'hr', '***' + ] +); + +MT.testMode( + 'hrLong', + '*****', + [ + 'hr', '*****' + ] +); + +MT.testMode( + 'hrSpaceDash', + '- - -', + [ + 'hr', '- - -' + ] +); + +MT.testMode( + 'hrDashLong', + '---------------------------------------', + [ + 'hr', '---------------------------------------' + ] +); + +// Inline link with title +MT.testMode( + 'linkTitle', + '[foo](http://example.com/ "bar") hello', + [ + 'link', '[foo]', + 'string', '(http://example.com/ "bar")', + null, ' hello' + ] +); + +// Inline link without title +MT.testMode( + 'linkNoTitle', + '[foo](http://example.com/) bar', + [ + 'link', '[foo]', + 'string', '(http://example.com/)', + null, ' bar' + ] +); + +// Reference-style links +MT.testMode( + 'linkReference', + '[foo][bar] hello', + [ + 'link', '[foo]', + 'string', '[bar]', + null, ' hello' + ] +); + +// Reference-style links with optional space separator (per docuentation) +// "You can optionally use a space to separate the sets of brackets" +MT.testMode( + 'linkReferenceSpace', + '[foo] [bar] hello', + [ + 'link', '[foo]', + null, ' ', + 'string', '[bar]', + null, ' hello' + ] +); +// Should only allow a single space ("...use *a* space...") +MT.testMode( + 'linkReferenceDoubleSpace', + '[foo] [bar] hello', + [ + null, '[foo] [bar] hello' + ] +); + +// Reference-style links with implicit link name +MT.testMode( + 'linkImplicit', + '[foo][] hello', + [ + 'link', '[foo]', + 'string', '[]', + null, ' hello' + ] +); + +// @todo It would be nice if, at some point, the document was actually +// checked to see if the referenced link exists + +// Link label, for reference-style links (taken from documentation) +// +// No title +MT.testMode( + 'labelNoTitle', + '[foo]: http://example.com/', + [ + 'link', '[foo]:', + null, ' ', + 'string', 'http://example.com/' + ] +); +// Space in ID and title +MT.testMode( + 'labelSpaceTitle', + '[foo bar]: http://example.com/ "hello"', + [ + 'link', '[foo bar]:', + null, ' ', + 'string', 'http://example.com/ "hello"' + ] +); +// Double title +MT.testMode( + 'labelDoubleTitle', + '[foo bar]: http://example.com/ "hello" "world"', + [ + 'link', '[foo bar]:', + null, ' ', + 'string', 'http://example.com/ "hello"', + null, ' "world"' + ] +); +// Double quotes around title +MT.testMode( + 'labelTitleDoubleQuotes', + '[foo]: http://example.com/ "bar"', + [ + 'link', '[foo]:', + null, ' ', + 'string', 'http://example.com/ "bar"' + ] +); +// Single quotes around title +MT.testMode( + 'labelTitleSingleQuotes', + '[foo]: http://example.com/ \'bar\'', + [ + 'link', '[foo]:', + null, ' ', + 'string', 'http://example.com/ \'bar\'' + ] +); +// Parentheses around title +MT.testMode( + 'labelTitleParenthese', + '[foo]: http://example.com/ (bar)', + [ + 'link', '[foo]:', + null, ' ', + 'string', 'http://example.com/ (bar)' + ] +); +// Invalid title +MT.testMode( + 'labelTitleInvalid', + '[foo]: http://example.com/ bar', + [ + 'link', '[foo]:', + null, ' ', + 'string', 'http://example.com/', + null, ' bar' + ] +); +// Angle brackets around URL +MT.testMode( + 'labelLinkAngleBrackets', + '[foo]: "bar"', + [ + 'link', '[foo]:', + null, ' ', + 'string', ' "bar"' + ] +); +// Title on next line per documentation (double quotes) +MT.testMode( + 'labelTitleNextDoubleQuotes', + '[foo]: http://example.com/\n"bar" hello', + [ + 'link', '[foo]:', + null, ' ', + 'string', 'http://example.com/', + 'string', '"bar"', + null, ' hello' + ] +); +// Title on next line per documentation (single quotes) +MT.testMode( + 'labelTitleNextSingleQuotes', + '[foo]: http://example.com/\n\'bar\' hello', + [ + 'link', '[foo]:', + null, ' ', + 'string', 'http://example.com/', + 'string', '\'bar\'', + null, ' hello' + ] +); +// Title on next line per documentation (parentheses) +MT.testMode( + 'labelTitleNextParenthese', + '[foo]: http://example.com/\n(bar) hello', + [ + 'link', '[foo]:', + null, ' ', + 'string', 'http://example.com/', + 'string', '(bar)', + null, ' hello' + ] +); +// Title on next line per documentation (mixed) +MT.testMode( + 'labelTitleNextMixed', + '[foo]: http://example.com/\n(bar" hello', + [ + 'link', '[foo]:', + null, ' ', + 'string', 'http://example.com/', + null, '(bar" hello' + ] +); + +// Automatic links +MT.testMode( + 'linkWeb', + ' foo', + [ + 'link', '', + null, ' foo' + ] +); + +// Automatic email links +MT.testMode( + 'linkEmail', + ' foo', + [ + 'link', '', + null, ' foo' + ] +); + +// Single asterisk +MT.testMode( + 'emAsterisk', + '*foo* bar', + [ + 'em', '*foo*', + null, ' bar' + ] +); + +// Single underscore +MT.testMode( + 'emUnderscore', + '_foo_ bar', + [ + 'em', '_foo_', + null, ' bar' + ] +); + +// Emphasis characters within a word +MT.testMode( + 'emInWordAsterisk', + 'foo*bar*hello', + [ + null, 'foo', + 'em', '*bar*', + null, 'hello' + ] +); +MT.testMode( + 'emInWordUnderscore', + 'foo_bar_hello', + [ + null, 'foo', + 'em', '_bar_', + null, 'hello' + ] +); +// Per documentation: "...surround an * or _ with spaces, it’ll be +// treated as a literal asterisk or underscore." +// +// Inside EM +MT.testMode( + 'emEscapedBySpaceIn', + 'foo _bar _ hello_ world', + [ + null, 'foo ', + 'em', '_bar _ hello_', + null, ' world' + ] +); +// Outside EM +MT.testMode( + 'emEscapedBySpaceOut', + 'foo _ bar_hello_world', + [ + null, 'foo _ bar', + 'em', '_hello_', + null, 'world' + ] +); + +// Unclosed emphasis characters +// Instead of simply marking as EM / STRONG, it would be nice to have an +// incomplete flag for EM and STRONG, that is styled slightly different. +MT.testMode( + 'emIncompleteAsterisk', + 'foo *bar', + [ + null, 'foo ', + 'em', '*bar' + ] +); +MT.testMode( + 'emIncompleteUnderscore', + 'foo _bar', + [ + null, 'foo ', + 'em', '_bar' + ] +); + +// Double asterisk +MT.testMode( + 'strongAsterisk', + '**foo** bar', + [ + 'strong', '**foo**', + null, ' bar' + ] +); + +// Double underscore +MT.testMode( + 'strongUnderscore', + '__foo__ bar', + [ + 'strong', '__foo__', + null, ' bar' + ] +); + +// Triple asterisk +MT.testMode( + 'emStrongAsterisk', + '*foo**bar*hello** world', + [ + 'em', '*foo', + 'emstrong', '**bar*', + 'strong', 'hello**', + null, ' world' + ] +); + +// Triple underscore +MT.testMode( + 'emStrongUnderscore', + '_foo__bar_hello__ world', + [ + 'em', '_foo', + 'emstrong', '__bar_', + 'strong', 'hello__', + null, ' world' + ] +); + +// Triple mixed +// "...same character must be used to open and close an emphasis span."" +MT.testMode( + 'emStrongMixed', + '_foo**bar*hello__ world', + [ + 'em', '_foo', + 'emstrong', '**bar*hello__ world' + ] +); + +MT.testMode( + 'emStrongMixed', + '*foo__bar_hello** world', + [ + 'em', '*foo', + 'emstrong', '__bar_hello** world' + ] +); + +// These characters should be escaped: +// \ backslash +// ` backtick +// * asterisk +// _ underscore +// {} curly braces +// [] square brackets +// () parentheses +// # hash mark +// + plus sign +// - minus sign (hyphen) +// . dot +// ! exclamation mark +// +// Backtick (code) +MT.testMode( + 'escapeBacktick', + 'foo \\`bar\\`', + [ + null, 'foo \\`bar\\`' + ] +); +MT.testMode( + 'doubleEscapeBacktick', + 'foo \\\\`bar\\\\`', + [ + null, 'foo \\\\', + 'comment', '`bar\\\\`' + ] +); +// Asterisk (em) +MT.testMode( + 'escapeAsterisk', + 'foo \\*bar\\*', + [ + null, 'foo \\*bar\\*' + ] +); +MT.testMode( + 'doubleEscapeAsterisk', + 'foo \\\\*bar\\\\*', + [ + null, 'foo \\\\', + 'em', '*bar\\\\*' + ] +); +// Underscore (em) +MT.testMode( + 'escapeUnderscore', + 'foo \\_bar\\_', + [ + null, 'foo \\_bar\\_' + ] +); +MT.testMode( + 'doubleEscapeUnderscore', + 'foo \\\\_bar\\\\_', + [ + null, 'foo \\\\', + 'em', '_bar\\\\_' + ] +); +// Hash mark (headers) +MT.testMode( + 'escapeHash', + '\\# foo', + [ + null, '\\# foo' + ] +); +MT.testMode( + 'doubleEscapeHash', + '\\\\# foo', + [ + null, '\\\\# foo' + ] +); \ No newline at end of file diff --git a/mode/stex/index.html b/mode/stex/index.html index 39dc0c2436..2dafe69816 100644 --- a/mode/stex/index.html +++ b/mode/stex/index.html @@ -92,5 +92,7 @@

    CodeMirror: sTeX mode

    MIME types defined: text/x-stex.

    +

    Parsing/Highlighting Tests: normal, verbose.

    + diff --git a/mode/stex/test.html b/mode/stex/test.html deleted file mode 100644 index c63b3cbc31..0000000000 --- a/mode/stex/test.html +++ /dev/null @@ -1,231 +0,0 @@ - - - - - CodeMirror: sTeX mode - - - - - - - - -

    Tests for the sTeX Mode

    -

    Basics

    - - -

    Tags

    - - -

    Comments

    - - -

    Errors

    - - -

    Character Escapes

    - - -

    Spacing control

    - - - -

    New Commands

    - - Should be able to define a new command that happens to be a method on Array - (e.g. pop): - - -

    Summary

    - - - - - diff --git a/mode/stex/test.js b/mode/stex/test.js new file mode 100644 index 0000000000..c5a34f3d8d --- /dev/null +++ b/mode/stex/test.js @@ -0,0 +1,343 @@ +var MT = ModeTest; +MT.modeName = 'stex'; +MT.modeOptions = {}; + +MT.testMode( + 'word', + 'foo', + [ + null, 'foo' + ] +); + +MT.testMode( + 'twoWords', + 'foo bar', + [ + null, 'foo bar' + ] +); + +MT.testMode( + 'beginEndDocument', + '\\begin{document}\n\\end{document}', + [ + 'tag', '\\begin', + 'bracket', '{', + 'atom', 'document', + 'bracket', '}', + 'tag', '\\end', + 'bracket', '{', + 'atom', 'document', + 'bracket', '}' + ] +); + +MT.testMode( + 'beginEndEquation', + '\\begin{equation}\n E=mc^2\n\\end{equation}', + [ + 'tag', '\\begin', + 'bracket', '{', + 'atom', 'equation', + 'bracket', '}', + null, ' E=mc^2', + 'tag', '\\end', + 'bracket', '{', + 'atom', 'equation', + 'bracket', '}' + ] +); + +MT.testMode( + 'beginModule', + '\\begin{module}[]', + [ + 'tag', '\\begin', + 'bracket', '{', + 'atom', 'module', + 'bracket', '}[]' + ] +); + +MT.testMode( + 'beginModuleId', + '\\begin{module}[id=bbt-size]', + [ + 'tag', '\\begin', + 'bracket', '{', + 'atom', 'module', + 'bracket', '}[', + null, 'id=bbt-size', + 'bracket', ']' + ] +); + +MT.testMode( + 'importModule', + '\\importmodule[b-b-t]{b-b-t}', + [ + 'tag', '\\importmodule', + 'bracket', '[', + 'string', 'b-b-t', + 'bracket', ']{', + 'builtin', 'b-b-t', + 'bracket', '}' + ] +); + +MT.testMode( + 'importModulePath', + '\\importmodule[\\KWARCslides{dmath/en/cardinality}]{card}', + [ + 'tag', '\\importmodule', + 'bracket', '[', + 'tag', '\\KWARCslides', + 'bracket', '{', + 'string', 'dmath/en/cardinality', + 'bracket', '}]{', + 'builtin', 'card', + 'bracket', '}' + ] +); + +MT.testMode( + 'psForPDF', + '\\PSforPDF[1]{#1}', // could treat #1 specially + [ + 'tag', '\\PSforPDF', + 'bracket', '[', + 'atom', '1', + 'bracket', ']{', + null, '#1', + 'bracket', '}' + ] +); + +MT.testMode( + 'comment', + '% foo', + [ + 'comment', '% foo' + ] +); + +MT.testMode( + 'tagComment', + '\\item% bar', + [ + 'tag', '\\item', + 'comment', '% bar' + ] +); + +MT.testMode( + 'commentTag', + ' % \\item', + [ + null, ' ', + 'comment', '% \\item' + ] +); + +MT.testMode( + 'commentLineBreak', + '%\nfoo', + [ + 'comment', '%', + null, 'foo' + ] +); + +MT.testMode( + 'tagErrorCurly', + '\\begin}{', + [ + 'tag', '\\begin', + 'error', '}', + 'bracket', '{' + ] +); + +MT.testMode( + 'tagErrorSquare', + '\\item]{', + [ + 'tag', '\\item', + 'error', ']', + 'bracket', '{' + ] +); + +MT.testMode( + 'commentCurly', + '% }', + [ + 'comment', '% }' + ] +); + +MT.testMode( + 'tagHash', + 'the \\# key', + [ + null, 'the ', + 'tag', '\\#', + null, ' key' + ] +); + +MT.testMode( + 'tagNumber', + 'a \\$5 stetson', + [ + null, 'a ', + 'tag', '\\$', + 'atom', 5, + null, ' stetson' + ] +); + +MT.testMode( + 'tagPercent', + '100\\% beef', + [ + 'atom', '100', + 'tag', '\\%', + null, ' beef' + ] +); + +MT.testMode( + 'tagAmpersand', + 'L \\& N', + [ + null, 'L ', + 'tag', '\\&', + null, ' N' + ] +); + +MT.testMode( + 'tagUnderscore', + 'foo\\_bar', + [ + null, 'foo', + 'tag', '\\_', + null, 'bar' + ] +); + +MT.testMode( + 'tagBracketOpen', + '\\emph{\\{}', + [ + 'tag', '\\emph', + 'bracket', '{', + 'tag', '\\{', + 'bracket', '}' + ] +); + +MT.testMode( + 'tagBracketClose', + '\\emph{\\}}', + [ + 'tag', '\\emph', + 'bracket', '{', + 'tag', '\\}', + 'bracket', '}' + ] +); + +MT.testMode( + 'tagLetterNumber', + 'section \\S1', + [ + null, 'section ', + 'tag', '\\S', + 'atom', '1' + ] +); + +MT.testMode( + 'textTagNumber', + 'para \\P2', + [ + null, 'para ', + 'tag', '\\P', + 'atom', '2' + ] +); + +MT.testMode( + 'thinspace', + 'x\\,y', // thinspace + [ + null, 'x', + 'tag', '\\,', + null, 'y' + ] +); + +MT.testMode( + 'thickspace', + 'x\\;y', // thickspace + [ + null, 'x', + 'tag', '\\;', + null, 'y' + ] +); + +MT.testMode( + 'negativeThinspace', + 'x\\!y', // negative thinspace + [ + null, 'x', + 'tag', '\\!', + null, 'y' + ] +); + +MT.testMode( + 'periodNotSentence', + 'J.\\ L.\\ is', // period not ending a sentence + [ + null, 'J.\\ L.\\ is' + ] +); // maybe could be better + +MT.testMode( + 'periodSentence', + 'X\\@. The', // period ending a sentence + [ + null, 'X', + 'tag', '\\@', + null, '. The' + ] +); + +MT.testMode( + 'italicCorrection', + '{\\em If\\/} I', // italic correction + [ + 'bracket', '{', + 'tag', '\\em', + null, ' If', + 'tag', '\\/', + 'bracket', '}', + null, ' I' + ] +); + +MT.testMode( + 'tagBracket', + '\\newcommand{\\pop}', + [ + 'tag', '\\newcommand', + 'bracket', '{', + 'tag', '\\pop', + 'bracket', '}' + ] +); \ No newline at end of file diff --git a/test/driver.js b/test/driver.js index 975c24f9fc..c64360af6e 100644 --- a/test/driver.js +++ b/test/driver.js @@ -1,35 +1,92 @@ -var tests = [], debug = null; +var tests = [], debug = null, debugUsed = new Array(), allNames = []; function Failure(why) {this.message = why;} function test(name, run, expectedFail) { + // Force unique names + var originalName = name; + var i = 2; // Second function would be NAME_2 + while(allNames.indexOf(name) !== -1){ + i++; + name = originalName + "_" + i; + } + allNames.push(name); + // Add test tests.push({name: name, func: run, expectedFail: expectedFail}); return name; } function testCM(name, run, opts, expectedFail) { - return test(name, function() { + return test("core_" + name, function() { var place = document.getElementById("testground"), cm = CodeMirror(place, opts); - if (debug) place.style.visibility = ""; - try {run(cm);} - finally {if (!debug) place.removeChild(cm.getWrapperElement());} + try { + run(cm); + } finally { + if (debug) { + place.style.visibility = ""; + } else { + place.removeChild(cm.getWrapperElement()); + } + } }, expectedFail); } function runTests(callback) { + if (debug) { + if (debug.indexOf("verbose") === 0) { + verbose = true; + debug.splice(0, 1); + } + if (debug.length < 1) { + debug = null; + } else { + if (totalTests > debug.length) { + totalTests = debug.length; + } + } + } function step(i) { - if (i == tests.length) return callback("done"); + if (i === tests.length){ + running = false; + return callback("done"); + } var test = tests[i], expFail = test.expectedFail; - if (debug != null && debug != test.name) return step(i + 1); + if (debug !== null) { + var debugIndex = debug.indexOf(test.name); + if (debugIndex !== -1) { + // Remove from array for reporting incorrect tests later + debug.splice(debugIndex, 1); + } else { + var wildcardName = test.name.split("_").shift() + "_*"; + debugIndex = debug.indexOf(wildcardName); + if (debugIndex !== -1) { + // Remove from array for reporting incorrect tests later + debug.splice(debugIndex, 1); + debugUsed.push(wildcardName); + } else { + debugIndex = debugUsed.indexOf(wildcardName); + if (debugIndex !== -1) { + totalTests++; + } else { + return step(i + 1); + } + } + } + } try { - test.func(); - if (expFail) callback("fail", test.name, "was expected to fail, but succeeded"); - else callback("ok", test.name); + var message = test.func(); + if (expFail) callback("fail", test.name, message); + else callback("ok", test.name, message); } catch(e) { if (expFail) callback("expected", test.name); else if (e instanceof Failure) callback("fail", test.name, e.message); else callback("error", test.name, e.toString()); } - setTimeout(function(){step(i + 1);}, 20); + if (!quit) { // Run next test + setTimeout(function(){step(i + 1);}, 50); + } else { // Quit tests + running = false; + return null; + } } step(0); } diff --git a/test/index.html b/test/index.html index dd319e9db7..486d6d99e6 100644 --- a/test/index.html +++ b/test/index.html @@ -1,8 +1,11 @@ + CodeMirror: Test Suite + + @@ -11,6 +14,7 @@ .ok {color: #090;} .fail {color: #e00;} .error {color: #c90;} + .done {font-weight: bold;} @@ -19,42 +23,135 @@

    CodeMirror: Test Suite

    A limited set of programmatic sanity tests for CodeMirror.

    -
    +
    Ran 0 of 0 tests
    -
    
    +    

    Please enable JavaScript...

    +
    - + + + + + + diff --git a/test/mode_test.css b/test/mode_test.css index f425922e5d..1ac66737fb 100644 --- a/test/mode_test.css +++ b/test/mode_test.css @@ -8,15 +8,3 @@ .mt-output .mt-style { font-size: x-small; } - -.mt-test { - border-left: 10px solid #fff; -} - -.mt-pass { - border-left: 10px solid #cfc; -} - -.mt-fail { - border-left: 10px solid #fcc; -} diff --git a/test/mode_test.js b/test/mode_test.js index 0bda5a2e78..b257d6ca36 100644 --- a/test/mode_test.js +++ b/test/mode_test.js @@ -10,33 +10,50 @@ ModeTest.modeOptions = {}; ModeTest.modeName = CodeMirror.defaults.mode; /* keep track of results for printSummary */ -ModeTest.tests = 0; +ModeTest.testCount = 0; ModeTest.passes = 0; /** * Run a test; prettyprints the results using document.write(). - * - * @param string to highlight - * - * @param style[i] expected style of the i'th token in string - * - * @param token[i] expected value for the i'th token in string + * + * @param name Name of test + * @param text String to highlight. + * @param expected Expected styles and tokens: Array(style, token, [style, token,...]) + * @param modeName + * @param modeOptions + * @param expectedFail */ -ModeTest.test = function() { - ModeTest.tests += 1; +ModeTest.testMode = function(name, text, expected, modeName, modeOptions, expectedFail) { + ModeTest.testCount += 1; + + if (!modeName) modeName = ModeTest.modeName; + + if (!modeOptions) modeOptions = ModeTest.modeOptions; - var mode = CodeMirror.getMode(ModeTest.modeOptions, ModeTest.modeName); + var mode = CodeMirror.getMode(modeOptions, modeName); - if (arguments.length < 1) { - throw "must have text for test"; + if (expected.length < 0) { + throw "must have text for test (" + name + ")"; } - if (arguments.length % 2 != 1) { - throw "must have text for test plus expected (style, token) pairs"; + if (expected.length % 2 != 0) { + throw "must have text for test (" + name + ") plus expected (style, token) pairs"; } + return test( + modeName + "_" + name, + function(){ + var place = document.getElementById("testground"), cm = CodeMirror(place); + try {return ModeTest.compare(cm, text, expected, mode);} + finally {place.removeChild(cm.getWrapperElement());} + }, + expectedFail + ); + +} + +ModeTest.compare = function (cm, text, arguments, mode) { - var text = arguments[0]; var expectedOutput = []; - for (var i = 1; i < arguments.length; i += 2) { + for (var i = 0; i < arguments.length; i += 2) { arguments[i] = (arguments[i] != null ? arguments[i].split(' ').sort().join(' ') : arguments[i]); expectedOutput.push([arguments[i],arguments[i + 1]]); } @@ -51,20 +68,26 @@ ModeTest.test = function() { } var s = ''; - s += '
    '; - s += '
    ' + ModeTest.htmlEscape(text) + '
    '; - s += '
    '; if (pass || expectedOutput.length == 0) { + s += '
    '; + s += '
    ' + ModeTest.htmlEscape(text) + '
    '; + s += '
    '; s += ModeTest.prettyPrintOutputTable(observedOutput); + s += '
    '; + s += '
    '; + return s; } else { + s += '
    '; + s += '
    ' + ModeTest.htmlEscape(text) + '
    '; + s += '
    '; s += 'expected:'; s += ModeTest.prettyPrintOutputTable(expectedOutput); s += 'observed:'; s += ModeTest.prettyPrintOutputTable(observedOutput); + s += '
    '; + s += '
    '; + throw s; } - s += '
    '; - s += '
    '; - document.write(s); } /** @@ -167,7 +190,8 @@ ModeTest.prettyPrintOutputTable = function(output) { * Print how many tests have run so far and how many of those passed. */ ModeTest.printSummary = function() { - document.write(ModeTest.passes + ' passes for ' + ModeTest.tests + ' tests'); + ModeTest.runTests(ModeTest.displayTest); + document.write(ModeTest.passes + ' passes for ' + ModeTest.testCount + ' tests'); } /** diff --git a/test/phantom_driver.js b/test/phantom_driver.js index ad48fd1a84..e5072946c6 100644 --- a/test/phantom_driver.js +++ b/test/phantom_driver.js @@ -7,14 +7,14 @@ page.open("http://localhost:3000/test/index.html", function (status) { } waitFor(function () { return page.evaluate(function () { - var output = document.getElementById('output'); + var output = document.getElementById('status'); if (!output) { return false; } - return (/(\d+ failures?|all passed)$/i).test(output.innerText); + return (/^(\d+ failures?|all passed)/i).test(output.innerText); }); }, function () { var failed = page.evaluate(function () { return window.failed; }); var output = page.evaluate(function () { - return document.getElementById('output').innerText; + return document.getElementById('status').innerText; }); console.log(output); phantom.exit(failed > 0 ? 1 : 0); @@ -27,4 +27,4 @@ function waitFor (test, cb) { } else { setTimeout(function () { waitFor(test, cb); }, 250); } -} \ No newline at end of file +} diff --git a/test/test.js b/test/test.js index 2c8e398c83..1c307eaba8 100644 --- a/test/test.js +++ b/test/test.js @@ -24,7 +24,7 @@ function byClassName(elt, cls) { var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent); -test("fromTextArea", function() { +test("core_fromTextArea", function() { var te = document.getElementById("code"); te.value = "CONTENT"; var cm = CodeMirror.fromTextArea(te); @@ -108,7 +108,7 @@ testCM("indent", function(cm) { eq(cm.getLine(1), "\t\t blah();"); }, {value: "if (x) {\nblah();\n}", indentUnit: 3, indentWithTabs: true, tabSize: 8}); -test("defaults", function() { +test("core_defaults", function() { var olddefaults = CodeMirror.defaults, defs = CodeMirror.defaults = {}; for (var opt in olddefaults) defs[opt] = olddefaults[opt]; defs.indentUnit = 5; From b306f7cc4051e8e16df7fb8a0db9b094799747ef Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 7 Sep 2012 10:44:52 +0200 Subject: [PATCH 023/135] Stop creating unused CodeMirror instances in mode tests --- test/mode_test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/mode_test.js b/test/mode_test.js index b257d6ca36..8d9df65e6c 100644 --- a/test/mode_test.js +++ b/test/mode_test.js @@ -41,16 +41,14 @@ ModeTest.testMode = function(name, text, expected, modeName, modeOptions, expect return test( modeName + "_" + name, function(){ - var place = document.getElementById("testground"), cm = CodeMirror(place); - try {return ModeTest.compare(cm, text, expected, mode);} - finally {place.removeChild(cm.getWrapperElement());} + return ModeTest.compare(text, expected, mode); }, expectedFail ); } -ModeTest.compare = function (cm, text, arguments, mode) { +ModeTest.compare = function (text, arguments, mode) { var expectedOutput = []; for (var i = 0; i < arguments.length; i += 2) { From dcc976c5a802885e1cacbea3d188e1005f791394 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 7 Sep 2012 10:46:59 +0200 Subject: [PATCH 024/135] Don't wait 50 ms between tests There are now enough tests to make this take a serious amount of time. A 0 timeout seems to work just as well. --- test/driver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/driver.js b/test/driver.js index c64360af6e..62af55a117 100644 --- a/test/driver.js +++ b/test/driver.js @@ -82,7 +82,7 @@ function runTests(callback) { else callback("error", test.name, e.toString()); } if (!quit) { // Run next test - setTimeout(function(){step(i + 1);}, 50); + setTimeout(function(){step(i + 1);}, 0); } else { // Quit tests running = false; return null; From fafa9d2495ad1b3d3fd771129e7ef44287f8b7e1 Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Fri, 7 Sep 2012 10:59:48 -0400 Subject: [PATCH 025/135] Prevent tests from locking up browser on slower machines (closes #805). --- test/driver.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/driver.js b/test/driver.js index 62af55a117..a4b09fef5e 100644 --- a/test/driver.js +++ b/test/driver.js @@ -44,12 +44,16 @@ function runTests(callback) { } } } + var totalTime = 0; function step(i) { if (i === tests.length){ running = false; return callback("done"); } - var test = tests[i], expFail = test.expectedFail; + var test = tests[i] + , expFail = test.expectedFail + , startTime = Date.now() + ; if (debug !== null) { var debugIndex = debug.indexOf(test.name); if (debugIndex !== -1) { @@ -82,7 +86,13 @@ function runTests(callback) { else callback("error", test.name, e.toString()); } if (!quit) { // Run next test - setTimeout(function(){step(i + 1);}, 0); + var delay = 0; + totalTime += (Date.now() - startTime); + if (totalTime > 500){ + totalTime = 0; + delay = 50; + } + setTimeout(function(){step(i + 1);}, delay); } else { // Quit tests running = false; return null; From cab941773a50da94ecf524668aefd53b4e7cefe7 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 7 Sep 2012 18:18:43 +0200 Subject: [PATCH 026/135] Fix a number of ECMA-5-isms in the test runner We do still support IE7/8. Also removes some non-CodeMirror-code-style things like left-aligned commas and semicolons. --- test/driver.js | 24 ++++++++++++++---------- test/index.html | 27 +++++++++++++-------------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/test/driver.js b/test/driver.js index a4b09fef5e..108ff0eadb 100644 --- a/test/driver.js +++ b/test/driver.js @@ -2,11 +2,18 @@ var tests = [], debug = null, debugUsed = new Array(), allNames = []; function Failure(why) {this.message = why;} +function indexOf(collection, elt) { + if (collection.indexOf) return collection.indexOf(elt); + for (var i = 0, e = collection.length; i < e; ++i) + if (collection[i] == elt) return i; + return -1; +} + function test(name, run, expectedFail) { // Force unique names var originalName = name; var i = 2; // Second function would be NAME_2 - while(allNames.indexOf(name) !== -1){ + while (indexOf(allNames, name) !== -1){ i++; name = originalName + "_" + i; } @@ -32,7 +39,7 @@ function testCM(name, run, opts, expectedFail) { function runTests(callback) { if (debug) { - if (debug.indexOf("verbose") === 0) { + if (indexOf(debug, "verbose") === 0) { verbose = true; debug.splice(0, 1); } @@ -50,24 +57,21 @@ function runTests(callback) { running = false; return callback("done"); } - var test = tests[i] - , expFail = test.expectedFail - , startTime = Date.now() - ; + var test = tests[i], expFail = test.expectedFail, startTime = +new Date; if (debug !== null) { - var debugIndex = debug.indexOf(test.name); + var debugIndex = indexOf(debug, test.name); if (debugIndex !== -1) { // Remove from array for reporting incorrect tests later debug.splice(debugIndex, 1); } else { var wildcardName = test.name.split("_").shift() + "_*"; - debugIndex = debug.indexOf(wildcardName); + debugIndex = indexOf(debug, wildcardName); if (debugIndex !== -1) { // Remove from array for reporting incorrect tests later debug.splice(debugIndex, 1); debugUsed.push(wildcardName); } else { - debugIndex = debugUsed.indexOf(wildcardName); + debugIndex = indexOf(debugUsed, wildcardName); if (debugIndex !== -1) { totalTests++; } else { @@ -87,7 +91,7 @@ function runTests(callback) { } if (!quit) { // Run next test var delay = 0; - totalTime += (Date.now() - startTime); + totalTime += (+new Date) - startTime; if (totalTime > 500){ totalTime = 0; delay = 50; diff --git a/test/index.html b/test/index.html index 486d6d99e6..af12d844f0 100644 --- a/test/index.html +++ b/test/index.html @@ -41,7 +41,7 @@

    CodeMirror: Test Suite

    window.onload = function() { runHarness(); }; - window.addEventListener('hashchange', function(){ + CodeMirror.connect(window, 'hashchange', function(){ runHarness(); }); @@ -49,17 +49,16 @@

    CodeMirror: Test Suite

    return str.replace(/[<&]/, function(ch) { return ch == "<" ? "<" : "&"; }); } - var output = document.getElementById("output") - , progress = document.getElementById("progress") - , progressRan = document.getElementById("progress_ran").childNodes[0] - , progressTotal = document.getElementById("progress_total").childNodes[0]; - var count = 0 - , failed = 0 - , bad = "" - , running = false // Flag that states tests are running - , quit = false // Flag to quit tests ASAP - , verbose = false // Adds message for *every* test to output - ; + var output = document.getElementById("output"), + progress = document.getElementById("progress"), + progressRan = document.getElementById("progress_ran").childNodes[0], + progressTotal = document.getElementById("progress_total").childNodes[0]; + var count = 0, + failed = 0, + bad = "", + running = false, // Flag that states tests are running + quit = false, // Flag to quit tests ASAP + verbose = false; // Adds message for *every* test to output function runHarness(){ if (running) { @@ -96,12 +95,12 @@

    CodeMirror: Test Suite

    if (!message) throw("must provide message"); var status = document.getElementById("status").childNodes[0]; status.nodeValue = message; - status.parentNode.setAttribute("class", className); + status.parentNode.className = className; } function addOutput(name, className, code){ var newOutput = document.createElement("dl"); var newTitle = document.createElement("dt"); - newTitle.setAttribute("class", className); + newTitle.className = className; newTitle.appendChild(document.createTextNode(name)); newOutput.appendChild(newTitle); var newMessage = document.createElement("dd"); From 5d3d207cf6dec757b8bfc880158158fe82420e1c Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Fri, 7 Sep 2012 11:12:58 -0400 Subject: [PATCH 027/135] A few small improvements to the test suite: - Move unchanging styles for #progress to style sheet - Forgot to update testCM() when I added the `verbose` option --- test/driver.js | 4 +++- test/index.html | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/test/driver.js b/test/driver.js index 108ff0eadb..9ee68fac02 100644 --- a/test/driver.js +++ b/test/driver.js @@ -25,10 +25,12 @@ function test(name, run, expectedFail) { function testCM(name, run, opts, expectedFail) { return test("core_" + name, function() { var place = document.getElementById("testground"), cm = CodeMirror(place, opts); + var successful = false; try { run(cm); + successful = true; } finally { - if (debug) { + if ((debug && !successful) || verbose) { place.style.visibility = ""; } else { place.removeChild(cm.getWrapperElement()); diff --git a/test/index.html b/test/index.html index af12d844f0..85fc7b8d48 100644 --- a/test/index.html +++ b/test/index.html @@ -15,6 +15,12 @@ .fail {color: #e00;} .error {color: #c90;} .done {font-weight: bold;} + #progress { + background: #45d; + color: white; + font-weight: bold; + white-space: pre; + } @@ -23,7 +29,7 @@

    CodeMirror: Test Suite

    A limited set of programmatic sanity tests for CodeMirror.

    -
    Ran 0 of 0 tests
    +
    Ran 0 of 0 tests

    Please enable JavaScript...

    From bd9a83b5480d5daefa95d1a2cc197738c382d43f Mon Sep 17 00:00:00 2001 From: dagsta Date: Fri, 7 Sep 2012 19:56:51 +0300 Subject: [PATCH 028/135] Extend closetag to work with html / PHP Mixed Code I just added support for closing Tags inside a file in "application/x-httpd-php" Mode including HTML Code --- lib/util/closetag.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/util/closetag.js b/lib/util/closetag.js index 656e93c288..8446ae67e9 100644 --- a/lib/util/closetag.js +++ b/lib/util/closetag.js @@ -41,7 +41,7 @@ var mode = cm.getOption('mode'); - if (mode == 'text/html' || mode == 'xml') { + if (mode == 'text/html' || mode == 'xml' || mode == 'application/x-httpd-php') { /* * Relevant structure of token: @@ -72,7 +72,7 @@ } if (ch == '>') { - var type = state.htmlState ? state.htmlState.type : state.type; // htmlmixed : xml + var type = state.htmlState ? state.htmlState.type : state.html ? state.html.type : state.type; // htmlmixed : xml : php if (tok.className == 'tag' && type == 'closeTag') { throw CodeMirror.Pass; // Don't process the '>' at the end of an end-tag. @@ -84,10 +84,10 @@ tok = cm.getTokenAt(cm.getCursor()); state = tok.state; - type = state.htmlState ? state.htmlState.type : state.type; // htmlmixed : xml + type = state.htmlState ? state.htmlState.type : state.html ? state.html.type : state.type; // htmlmixed : xml : php if (tok.className == 'tag' && type != 'selfcloseTag') { - var tagName = state.htmlState ? state.htmlState.tagName : state.tagName; // htmlmixed : xml + var tagName = state.htmlState ? state.htmlState.tagName : state.html ? state.html.tagName : state.tagName; // htmlmixed : xml : php if (tagName.length > 0 && shouldClose(cm, vd, tagName)) { insertEndTag(cm, indent, pos, tagName); } From 1cea0775fe251bff6cb4f8dc5fe88caf3064f1fe Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 7 Sep 2012 20:34:34 +0200 Subject: [PATCH 029/135] [util/closetag] Clean up handling of different modes --- lib/util/closetag.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/util/closetag.js b/lib/util/closetag.js index 8446ae67e9..566d2a30e0 100644 --- a/lib/util/closetag.js +++ b/lib/util/closetag.js @@ -26,6 +26,17 @@ /** Array of tag names where an end tag is forbidden. */ CodeMirror.defaults['closeTagVoid'] = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']; + function resolveMode(mode) { + if (typeof mode == "object") return mode.name; + if (mode.indexOf("/") > -1) return resolveMode(CodeMirror.mimeModes[mode]); + else return mode; + } + + function innerState(state) { + // htmlmixed uses .htmlState, PHP .html, XML just the top state object + return state.htmlState || state.html || state; + } + /** * Call during key processing to close tags. Handles the key event if the tag is closed, otherwise throws CodeMirror.Pass. * - cm: The editor instance. @@ -39,9 +50,8 @@ throw CodeMirror.Pass; } - var mode = cm.getOption('mode'); - if (mode == 'text/html' || mode == 'xml' || mode == 'application/x-httpd-php') { + if (/^(xml|php|htmlmixed)$/.test(resolveMode(cm.getOption('mode')))) { /* * Relevant structure of token: @@ -72,7 +82,7 @@ } if (ch == '>') { - var type = state.htmlState ? state.htmlState.type : state.html ? state.html.type : state.type; // htmlmixed : xml : php + var type = innerState(state).type; if (tok.className == 'tag' && type == 'closeTag') { throw CodeMirror.Pass; // Don't process the '>' at the end of an end-tag. @@ -84,10 +94,10 @@ tok = cm.getTokenAt(cm.getCursor()); state = tok.state; - type = state.htmlState ? state.htmlState.type : state.html ? state.html.type : state.type; // htmlmixed : xml : php + var type = innerState(state).type; if (tok.className == 'tag' && type != 'selfcloseTag') { - var tagName = state.htmlState ? state.htmlState.tagName : state.html ? state.html.tagName : state.tagName; // htmlmixed : xml : php + var tagName = innerState(state).tagName; if (tagName.length > 0 && shouldClose(cm, vd, tagName)) { insertEndTag(cm, indent, pos, tagName); } @@ -100,7 +110,7 @@ } else if (ch == '/') { if (tok.className == 'tag' && tok.string == '<') { - var tagName = state.htmlState ? (state.htmlState.context ? state.htmlState.context.tagName : '') : (state.context ? state.context.tagName : ''); // htmlmixed : xml + var ctx = innerState(state).context, tagName = ctx && ctx.tagName; if (tagName.length > 0) { completeEndTag(cm, pos, tagName); return; From 2d38a15f8771a11c1d527d7d2a15599d6f6b5b27 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 7 Sep 2012 20:37:55 +0200 Subject: [PATCH 030/135] [util/closetag] Fix problem introduced by previous patch --- lib/util/closetag.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util/closetag.js b/lib/util/closetag.js index 566d2a30e0..5b2a900139 100644 --- a/lib/util/closetag.js +++ b/lib/util/closetag.js @@ -110,7 +110,7 @@ } else if (ch == '/') { if (tok.className == 'tag' && tok.string == '<') { - var ctx = innerState(state).context, tagName = ctx && ctx.tagName; + var ctx = innerState(state).context, tagName = ctx ? ctx.tagName : ''; if (tagName.length > 0) { completeEndTag(cm, pos, tagName); return; From ba11716cb592c099cb6d73017b02cb2eb4080afe Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Sat, 8 Sep 2012 16:21:50 -0400 Subject: [PATCH 031/135] Reset `verbose` flag when running tests. --- test/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/test/index.html b/test/index.html index 85fc7b8d48..ab40e63267 100644 --- a/test/index.html +++ b/test/index.html @@ -84,6 +84,7 @@

    CodeMirror: Test Suite

    count = 0; failed = 0; bad = ""; + verbose = false; debugUsed = Array(); totalTests = tests.length; progressTotal.nodeValue = " of " + totalTests; From cde3d179347ab308013c9f8070aaa1886d4284a5 Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Sun, 9 Sep 2012 01:18:34 -0400 Subject: [PATCH 032/135] Fix bug with duplicate test names starting out at the wrong number. If two tests have the name "foo", the second should appear as "foo_2", but instead it appeared as "foo_3". --- test/driver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/driver.js b/test/driver.js index 9ee68fac02..5ad20425d6 100644 --- a/test/driver.js +++ b/test/driver.js @@ -14,8 +14,8 @@ function test(name, run, expectedFail) { var originalName = name; var i = 2; // Second function would be NAME_2 while (indexOf(allNames, name) !== -1){ - i++; name = originalName + "_" + i; + i++; } allNames.push(name); // Add test From c9fb2ba15ac8caf3815a1f15bc825e2f45212b89 Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Sun, 9 Sep 2012 01:47:00 -0400 Subject: [PATCH 033/135] Add a text-shadow behind test progress text so it's readable while the progress bar hasn't passed yet. --- test/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/test/index.html b/test/index.html index ab40e63267..edea7d106f 100644 --- a/test/index.html +++ b/test/index.html @@ -18,6 +18,7 @@ #progress { background: #45d; color: white; + text-shadow: 0 0 1px #45d, 0 0 2px #45d, 0 0 3px #45d; font-weight: bold; white-space: pre; } From d0a4a6733e13a2096efce0df1b2992daf827380c Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Sat, 8 Sep 2012 18:36:09 -0400 Subject: [PATCH 034/135] Add tests for CSS mode and fixed found bugs - Added link to tests on mode page - More consistent naming with CSS spec (some of the previous var naming was completely incorrect/misleading) - Improved highlighting - Match vendor prefixes - Check for known properties and values (known properties are bold, unknown are not) - Added highlighting for media queries - Cleaner stacking of state --- mode/css/css.css | 7 + mode/css/css.js | 400 +++++++++++++++++++++++++++++------ mode/css/index.html | 3 + mode/css/test.js | 501 ++++++++++++++++++++++++++++++++++++++++++++ test/index.html | 2 + 5 files changed, 850 insertions(+), 63 deletions(-) create mode 100644 mode/css/css.css create mode 100644 mode/css/test.js diff --git a/mode/css/css.css b/mode/css/css.css new file mode 100644 index 0000000000..caa2afdac8 --- /dev/null +++ b/mode/css/css.css @@ -0,0 +1,7 @@ +/* Change error (warning) so it's not so scary */ +.cm-s-default span.cm-property {font-weight: bold;} +.cm-s-default span.cm-property.cm-error {color: black; font-weight: normal;} + +/* Differentiate color from .cm-def and change error (warning) so it's not so scary */ +.cm-s-default span.cm-attribute {color: #c0c; font-weight: bold;} +.cm-s-default span.cm-attribute.cm-error {color: #c0c; font-weight: normal;} \ No newline at end of file diff --git a/mode/css/css.js b/mode/css/css.js index 9428c4e32e..d7163201dd 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -1,60 +1,196 @@ CodeMirror.defineMode("css", function(config) { var indentUnit = config.indentUnit, type; + + var atMediaTypes = keySet([ + "all", "aural", "braille", "handheld", "print", "projection", "screen", + "tty", "tv", "embossed" + ]); + + var atMediaFeatures = keySet([ + "width", "min-width", "max-width", "height", "min-height", "max-height", + "device-width", "min-device-width", "max-device-width", "device-height", + "min-device-height", "max-device-height", "aspect-ratio", + "min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio", + "min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color", + "max-color", "color-index", "min-color-index", "max-color-index", + "monochrome", "min-monochrome", "max-monochrome", "resolution", + "min-resolution", "max-resolution", "scan", "grid" + ]); - var keywords = keySet(["above", "absolute", "activeborder", "activecaption", "afar", "after-white-space", "ahead", "alias", "all", "all-scroll", - "alternate", "always", "amharic", "amharic-abegede", "antialiased", "appworkspace", "arabic-indic", "armenian", "asterisks", - "auto", "avoid", "background", "backwards", "baseline", "below", "bidi-override", "binary", "bengali", "blink", - "block", "block-axis", "bold", "bolder", "border", "border-box", "both", "bottom", "break-all", "break-word", "button", - "button-bevel", "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian", "capitalize", "caps-lock-indicator", - "caption", "captiontext", "caret", "cell", "center", "checkbox", "circle", "cjk-earthly-branch", "cjk-heavenly-stem", "cjk-ideographic", - "clear", "clip", "close-quote", "col-resize", "collapse", "compact", "condensed", "contain", "content", "content-box", "context-menu", - "continuous", "copy", "cover", "crop", "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal", "decimal-leading-zero", "default", - "default-button", "destination-atop", "destination-in", "destination-out", "destination-over", "devanagari", "disc", "discard", "document", - "dot-dash", "dot-dot-dash", "dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", "element", - "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede", "ethiopic-abegede-am-et", "ethiopic-abegede-gez", - "ethiopic-abegede-ti-er", "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er", "ethiopic-halehame-aa-et", - "ethiopic-halehame-am-et", "ethiopic-halehame-gez", "ethiopic-halehame-om-et", "ethiopic-halehame-sid-et", - "ethiopic-halehame-so-et", "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig", "ew-resize", "expanded", - "extra-condensed", "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "footnotes", "forwards", "from", "geometricPrecision", - "georgian", "graytext", "groove", "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew", "help", - "hidden", "hide", "higher", "highlight", "highlighttext", "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore", - "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", "infobackground", "infotext", "inherit", "initial", "inline", - "inline-axis", "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert", "italic", "justify", "kannada", "katakana", - "katakana-iroha", "khmer", "landscape", "lao", "large", "larger", "left", "level", "lighter", "line-through", "linear", "lines", - "list-item", "listbox", "listitem", "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian", "lower-greek", - "lower-hexadecimal", "lower-latin", "lower-norwegian", "lower-roman", "lowercase", "ltr", "malayalam", "match", "media-controls-background", - "media-current-time-display", "media-fullscreen-button", "media-mute-button", "media-play-button", "media-return-to-realtime-button", - "media-rewind-button", "media-seek-back-button", "media-seek-forward-button", "media-slider", "media-sliderthumb", "media-time-remaining-display", - "media-volume-slider", "media-volume-slider-container", "media-volume-sliderthumb", "medium", "menu", "menulist", "menulist-button", - "menulist-text", "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic", "mix", "mongolian", "monospace", "move", "multiple", - "myanmar", "n-resize", "narrower", "navy", "ne-resize", "nesw-resize", "no-close-quote", "no-drop", "no-open-quote", "no-repeat", "none", - "normal", "not-allowed", "nowrap", "ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote", "optimizeLegibility", - "optimizeSpeed", "oriya", "oromo", "outset", "outside", "overlay", "overline", "padding", "padding-box", "painted", "paused", - "persian", "plus-darker", "plus-lighter", "pointer", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", - "push-button", "radio", "read-only", "read-write", "read-write-plaintext-only", "relative", "repeat", "repeat-x", - "repeat-y", "reset", "reverse", "rgb", "rgba", "ridge", "right", "round", "row-resize", "rtl", "run-in", "running", "s-resize", "sans-serif", - "scroll", "scrollbar", "se-resize", "searchfield", "searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button", - "searchfield-results-decoration", "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama", "single", - "skip-white-space", "slide", "slider-horizontal", "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow", - "small", "small-caps", "small-caption", "smaller", "solid", "somali", "source-atop", "source-in", "source-out", "source-over", - "space", "square", "square-button", "start", "static", "status-bar", "stretch", "stroke", "sub", "subpixel-antialiased", "super", - "sw-resize", "table", "table-caption", "table-cell", "table-column", "table-column-group", "table-footer-group", "table-header-group", - "table-row", "table-row-group", "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai", "thick", "thin", - "threeddarkshadow", "threedface", "threedhighlight", "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er", "tigrinya-er-abegede", - "tigrinya-et", "tigrinya-et-abegede", "to", "top", "transparent", "ultra-condensed", "ultra-expanded", "underline", "up", "upper-alpha", "upper-armenian", - "upper-greek", "upper-hexadecimal", "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url", "vertical", "vertical-text", "visible", - "visibleFill", "visiblePainted", "visibleStroke", "visual", "w-resize", "wait", "wave", "white", "wider", "window", "windowframe", "windowtext", - "x-large", "x-small", "xor", "xx-large", "xx-small", "yellow", "-wap-marquee", "-webkit-activelink", "-webkit-auto", "-webkit-baseline-middle", - "-webkit-body", "-webkit-box", "-webkit-center", "-webkit-control", "-webkit-focus-ring-color", "-webkit-grab", "-webkit-grabbing", - "-webkit-gradient", "-webkit-inline-box", "-webkit-left", "-webkit-link", "-webkit-marquee", "-webkit-mini-control", "-webkit-nowrap", "-webkit-pictograph", - "-webkit-right", "-webkit-small-control", "-webkit-text", "-webkit-xxx-large", "-webkit-zoom-in", "-webkit-zoom-out"]); + var propertyKeywords = keySet([ + "align-content", "align-items", "align-self", "alignment-adjust", + "alignment-baseline", "anchor-point", "animation", "animation-delay", + "animation-direction", "animation-duration", "animation-iteration-count", + "animation-name", "animation-play-state", "animation-timing-function", + "appearance", "azimuth", "backface-visibility", "background", + "background-attachment", "background-clip", "background-color", + "background-image", "background-origin", "background-position", + "background-repeat", "background-size", "baseline-shift", "binding", + "bleed", "bookmark-label", "bookmark-level", "bookmark-state", + "bookmark-target", "border", "border-bottom", "border-bottom-color", + "border-bottom-left-radius", "border-bottom-right-radius", + "border-bottom-style", "border-bottom-width", "border-collapse", + "border-color", "border-image", "border-image-outset", + "border-image-repeat", "border-image-slice", "border-image-source", + "border-image-width", "border-left", "border-left-color", + "border-left-style", "border-left-width", "border-radius", "border-right", + "border-right-color", "border-right-style", "border-right-width", + "border-spacing", "border-style", "border-top", "border-top-color", + "border-top-left-radius", "border-top-right-radius", "border-top-style", + "border-top-width", "border-width", "bottom", "box-decoration-break", + "box-shadow", "box-sizing", "break-after", "break-before", "break-inside", + "caption-side", "clear", "clip", "color", "color-profile", "column-count", + "column-fill", "column-gap", "column-rule", "column-rule-color", + "column-rule-style", "column-rule-width", "column-span", "column-width", + "columns", "content", "counter-increment", "counter-reset", "crop", "cue", + "cue-after", "cue-before", "cursor", "direction", "display", + "dominant-baseline", "drop-initial-after-adjust", + "drop-initial-after-align", "drop-initial-before-adjust", + "drop-initial-before-align", "drop-initial-size", "drop-initial-value", + "elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis", + "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap", + "float", "float-offset", "font", "font-feature-settings", "font-family", + "font-kerning", "font-language-override", "font-size", "font-size-adjust", + "font-stretch", "font-style", "font-synthesis", "font-variant", + "font-variant-alternates", "font-variant-caps", "font-variant-east-asian", + "font-variant-ligatures", "font-variant-numeric", "font-variant-position", + "font-weight", "grid-cell", "grid-column", "grid-column-align", + "grid-column-sizing", "grid-column-span", "grid-columns", "grid-flow", + "grid-row", "grid-row-align", "grid-row-sizing", "grid-row-span", + "grid-rows", "grid-template", "hanging-punctuation", "height", "hyphens", + "icon", "image-orientation", "image-rendering", "image-resolution", + "inline-box-align", "justify-content", "left", "letter-spacing", + "line-break", "line-height", "line-stacking", "line-stacking-ruby", + "line-stacking-shift", "line-stacking-strategy", "list-style", + "list-style-image", "list-style-position", "list-style-type", "margin", + "margin-bottom", "margin-left", "margin-right", "margin-top", + "marker-offset", "marks", "marquee-direction", "marquee-loop", + "marquee-play-count", "marquee-speed", "marquee-style", "max-height", + "max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index", + "nav-left", "nav-right", "nav-up", "opacity", "order", "orphans", "outline", + "outline-color", "outline-offset", "outline-style", "outline-width", + "overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y", + "padding", "padding-bottom", "padding-left", "padding-right", "padding-top", + "page", "page-break-after", "page-break-before", "page-break-inside", + "page-policy", "pause", "pause-after", "pause-before", "perspective", + "perspective-origin", "pitch", "pitch-range", "play-during", "position", + "presentation-level", "punctuation-trim", "quotes", "rendering-intent", + "resize", "rest", "rest-after", "rest-before", "richness", "right", + "rotation", "rotation-point", "ruby-align", "ruby-overhang", + "ruby-position", "ruby-span", "size", "speak", "speak-as", "speak-header", + "speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set", + "tab-size", "table-layout", "target", "target-name", "target-new", + "target-position", "text-align", "text-align-last", "text-decoration", + "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-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" + ]); + + var colorKeywords = keySet([ + "black", "silver", "gray", "white", "maroon", "red", "purple", "fuchsia", + "green", "lime", "olive", "yellow", "navy", "blue", "teal", "aqua" + ]); + + var valueKeywords = keySet([ + "above", "absolute", "activeborder", "activecaption", "afar", + "after-white-space", "ahead", "alias", "all", "all-scroll", "alternate", + "always", "amharic", "amharic-abegede", "antialiased", "appworkspace", + "arabic-indic", "armenian", "asterisks", "auto", "avoid", "background", + "backwards", "baseline", "below", "bidi-override", "binary", "bengali", + "blink", "block", "block-axis", "bold", "bolder", "border", "border-box", + "both", "bottom", "break-all", "break-word", "button", "button-bevel", + "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian", + "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret", + "cell", "center", "checkbox", "circle", "cjk-earthly-branch", + "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote", + "col-resize", "collapse", "compact", "condensed", "contain", "content", + "content-box", "context-menu", "continuous", "copy", "cover", "crop", + "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal", + "decimal-leading-zero", "default", "default-button", "destination-atop", + "destination-in", "destination-out", "destination-over", "devanagari", + "disc", "discard", "document", "dot-dash", "dot-dot-dash", "dotted", + "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", + "element", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede", + "ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er", + "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er", + "ethiopic-halehame-aa-et", "ethiopic-halehame-am-et", + "ethiopic-halehame-gez", "ethiopic-halehame-om-et", + "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et", + "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", + "ethiopic-halehame-tig", "ew-resize", "expanded", "extra-condensed", + "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "footnotes", + "forwards", "from", "geometricPrecision", "georgian", "graytext", "groove", + "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew", + "help", "hidden", "hide", "higher", "highlight", "highlighttext", + "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore", + "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", + "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis", + "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert", + "italic", "justify", "kannada", "katakana", "katakana-iroha", "khmer", + "landscape", "lao", "large", "larger", "left", "level", "lighter", + "line-through", "linear", "lines", "list-item", "listbox", "listitem", + "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian", + "lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian", + "lower-roman", "lowercase", "ltr", "malayalam", "match", + "media-controls-background", "media-current-time-display", + "media-fullscreen-button", "media-mute-button", "media-play-button", + "media-return-to-realtime-button", "media-rewind-button", + "media-seek-back-button", "media-seek-forward-button", "media-slider", + "media-sliderthumb", "media-time-remaining-display", "media-volume-slider", + "media-volume-slider-container", "media-volume-sliderthumb", "medium", + "menu", "menulist", "menulist-button", "menulist-text", + "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic", + "mix", "mongolian", "monospace", "move", "multiple", "myanmar", "n-resize", + "narrower", "navy", "ne-resize", "nesw-resize", "no-close-quote", "no-drop", + "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap", + "ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote", + "optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset", + "outside", "overlay", "overline", "padding", "padding-box", "painted", + "paused", "persian", "plus-darker", "plus-lighter", "pointer", "portrait", + "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "push-button", + "radio", "read-only", "read-write", "read-write-plaintext-only", "relative", + "repeat", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba", + "ridge", "right", "round", "row-resize", "rtl", "run-in", "running", + "s-resize", "sans-serif", "scroll", "scrollbar", "se-resize", "searchfield", + "searchfield-cancel-button", "searchfield-decoration", + "searchfield-results-button", "searchfield-results-decoration", + "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama", + "single", "skip-white-space", "slide", "slider-horizontal", + "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow", + "small", "small-caps", "small-caption", "smaller", "solid", "somali", + "source-atop", "source-in", "source-out", "source-over", "space", "square", + "square-button", "start", "static", "status-bar", "stretch", "stroke", + "sub", "subpixel-antialiased", "super", "sw-resize", "table", + "table-caption", "table-cell", "table-column", "table-column-group", + "table-footer-group", "table-header-group", "table-row", "table-row-group", + "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai", + "thick", "thin", "threeddarkshadow", "threedface", "threedhighlight", + "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er", + "tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top", + "transparent", "ultra-condensed", "ultra-expanded", "underline", "up", + "upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal", + "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url", + "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted", + "visibleStroke", "visual", "w-resize", "wait", "wave", "white", "wider", + "window", "windowframe", "windowtext", "x-large", "x-small", "xor", + "xx-large", "xx-small", "yellow" + ]); function keySet(array) { var keys = {}; for (var i = 0; i < array.length; ++i) keys[array[i]] = true; return keys; } function ret(style, tp) {type = tp; return style;} function tokenBase(stream, state) { var ch = stream.next(); - if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("meta", stream.current());} + if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("def", stream.current());} else if (ch == "/" && stream.eat("*")) { state.tokenize = tokenCComment; return tokenCComment(stream, state); @@ -75,21 +211,35 @@ CodeMirror.defineMode("css", function(config) { } else if (ch == "!") { stream.match(/^\s*\w*/); - return ret("keyword", "important"); + return ret("builtin", "important"); } else if (/\d/.test(ch)) { stream.eatWhile(/[\w.%]/); return ret("number", "unit"); } - else if (/[,.+>*\/]/.test(ch)) { + else if (ch === "-") { + if (/\d/.test(stream.peek())) { + stream.eatWhile(/[\w.%]/); + return ret("number", "unit"); + } else if (stream.match(/^[^-]+-/)) { + return ret("meta", type); + } + } + else if (/[,+>*\/]/.test(ch)) { return ret(null, "select-op"); } - else if (/[;{}:\[\]\(\)]/.test(ch)) { + else if (ch == "." && stream.match(/^\w+/)) { + return ret("qualifier", type); + } + else if (ch == ":") { + return ret("operator", ch); + } + else if (/[;{}\[\]\(\)]/.test(ch)) { return ret(null, ch); } else { stream.eatWhile(/[\w\\\-]/); - return ret("variable", "variable"); + return ret("property", "variable"); } } @@ -138,32 +288,156 @@ CodeMirror.defineMode("css", function(config) { }, token: function(stream, state) { + + // Use these terms when applicable (see http://www.xanthir.com/blog/b4E50) + // + // rule** or **ruleset: + // A selector + braces combo, or an at-rule. + // + // declaration block: + // A sequence of declarations. + // + // declaration: + // A property + colon + value combo. + // + // property value: + // The entire value of a property. + // + // component value: + // A single piece of a property value. Like the 5px in + // text-shadow: 0 0 5px blue;. Can also refer to things that are + // multiple terms, like the 1-4 terms that make up the background-size + // portion of the background shorthand. + // + // term: + // The basic unit of author-facing CSS, like a single number (5), + // dimension (5px), string ("foo"), or function. Officially defined + // by the CSS 2.1 grammar (look for the 'term' production) + // + // + // simple selector: + // A single atomic selector, like a type selector, an attr selector, a + // class selector, etc. + // + // compound selector: + // One or more simple selectors without a combinator. div.example is + // compound, div > .example is not. + // + // complex selector: + // One or more compound selectors chained with combinators. + // + // combinator: + // The parts of selectors that express relationships. There are four + // currently - the space (descendant combinator), the greater-than + // bracket (child combinator), the plus sign (next sibling combinator), + // and the tilda (following sibling combinator). + // + // sequence of selectors: + // One or more of the named type of selector chained with commas. + if (stream.eatSpace()) return null; var style = state.tokenize(stream, state); + // Changing style returned based on context var context = state.stack[state.stack.length-1]; - if (type == "hash" && context != "rule") style = "string-2"; - else if (style == "variable") { - if (context == "rule") style = keywords[stream.current()] ? "keyword" : "number"; - else if (!context || context == "@media{") style = "tag"; + if (style == "property") { + if (context == "propertyValue"){ + if (valueKeywords[stream.current()]) { + style = "string-2"; + } else if (colorKeywords[stream.current()]) { + style = "keyword"; + } else { + style = "variable-2"; + } + } else if (context == "rule") { + if (!propertyKeywords[stream.current()]) { + style += " error"; + } + } else if (!context || context == "@media{") { + style = "tag"; + } else if (context == "@media") { + if (atMediaTypes[stream.current()]) { + style = "attribute"; // Known attribute + } else if (/^(only|not)$/i.test(stream.current())) { + style = "keyword"; + } else if (stream.current().toLowerCase() == "and") { + style = "error"; // "and" is only allowed in @mediaType + } else if (atMediaFeatures[stream.current()]) { + style = "error"; // Known property, should be in @mediaType( + } else { + // Unknown, expecting keyword or attribute, assuming attribute + style = "attribute error"; + } + } else if (context == "@mediaType") { + if (atMediaTypes[stream.current()]) { + style = "attribute"; + } else if (stream.current().toLowerCase() == "and") { + style = "operator"; + } else if (/^(only|not)$/i.test(stream.current())) { + style = "error"; // Only allowed in @media + } else if (atMediaFeatures[stream.current()]) { + style = "error"; // Known property, should be in parentheses + } else { + // Unknown attribute or property, but expecting property (preceded + // by "and"). Should be in parentheses + style = "error"; + } + } else if (context == "@mediaType(") { + if (propertyKeywords[stream.current()]) { + // do nothing, remains "property" + } else if (atMediaTypes[stream.current()]) { + style = "error"; // Known property, should be in parentheses + } else if (stream.current().toLowerCase() == "and") { + style = "operator"; + } else if (/^(only|not)$/i.test(stream.current())) { + style = "error"; // Only allowed in @media + } else { + style += " error"; + } + } else { + style = "error"; + } + } else if (style == "atom") { + if(!context || context == "@media{") { + style = "header"; + } else if (context == "propertyValue") { + if (!/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) { + style += " error"; + } + } else { + style = "error"; + } + } else if (context == "@media" && type == "{") { + style = "error"; } - if (context == "rule" && /^[\{\};]$/.test(type)) - state.stack.pop(); + // Push/pop context stack if (type == "{") { - if (context == "@media") state.stack[state.stack.length-1] = "@media{"; - else state.stack.push("{"); + if (context == "@media" || context == "@mediaType") { + state.stack.pop(); + state.stack[state.stack.length-1] = "@media{"; + } + else state.stack.push("rule"); + } + else if (type == "}") { + state.stack.pop(); + if (context == "propertyValue") state.stack.pop(); } - else if (type == "}") state.stack.pop(); else if (type == "@media") state.stack.push("@media"); - else if (context == "{" && type != "comment") state.stack.push("rule"); + else if (context == "@media" && /\b(keyword|attribute)\b/.test(style)) + state.stack.push("@mediaType"); + else if (context == "@mediaType" && stream.current() == ",") state.stack.pop(); + else if (context == "@mediaType" && type == "(") state.stack.push("@mediaType("); + else if (context == "@mediaType(" && type == ")") state.stack.pop(); + else if (context == "rule" && type == ":") state.stack.push("propertyValue"); + else if (context == "propertyValue" && type == ";") state.stack.pop(); return style; }, indent: function(state, textAfter) { var n = state.stack.length; if (/^\}/.test(textAfter)) - n -= state.stack[state.stack.length-1] == "rule" ? 2 : 1; + n -= state.stack[state.stack.length-1] == "propertyValue" ? 2 : 1; return state.baseIndent + n * indentUnit; }, diff --git a/mode/css/index.html b/mode/css/index.html index 1a591cbf3d..c77a11c5db 100644 --- a/mode/css/index.html +++ b/mode/css/index.html @@ -6,6 +6,7 @@ + @@ -52,5 +53,7 @@

    CodeMirror: CSS mode

    MIME types defined: text/css.

    +

    Parsing/Highlighting Tests: normal, verbose.

    + diff --git a/mode/css/test.js b/mode/css/test.js new file mode 100644 index 0000000000..f54336e1e5 --- /dev/null +++ b/mode/css/test.js @@ -0,0 +1,501 @@ +// Initiate ModeTest and set defaults +var MT = ModeTest; +MT.modeName = 'css'; +MT.modeOptions = {}; + +// Requires at least one media query +MT.testMode( + 'atMediaEmpty', + '@media { }', + [ + 'def', '@media', + null, ' ', + 'error', '{', + null, ' }' + ] +); + +MT.testMode( + 'atMediaMultiple', + '@media not screen and (color), not print and (color) { }', + [ + 'def', '@media', + null, ' ', + 'keyword', 'not', + null, ' ', + 'attribute', 'screen', + null, ' ', + 'operator', 'and', + null, ' (', + 'property', 'color', + null, '), ', + 'keyword', 'not', + null, ' ', + 'attribute', 'print', + null, ' ', + 'operator', 'and', + null, ' (', + 'property', 'color', + null, ') { }' + ] +); + +MT.testMode( + 'atMediaCheckStack', + '@media screen { } foo { }', + [ + 'def', '@media', + null, ' ', + 'attribute', 'screen', + null, ' { } ', + 'tag', 'foo', + null, ' { }' + ] +); + +MT.testMode( + 'atMediaCheckStack', + '@media screen (color) { } foo { }', + [ + 'def', '@media', + null, ' ', + 'attribute', 'screen', + null, ' (', + 'property', 'color', + null, ') { } ', + 'tag', 'foo', + null, ' { }' + ] +); + +MT.testMode( + 'atMediaCheckStackInvalidAttribute', + '@media foobarhello { } foo { }', + [ + 'def', '@media', + null, ' ', + 'attribute error', 'foobarhello', + null, ' { } ', + 'tag', 'foo', + null, ' { }' + ] +); + +// Error, because "and" is only allowed immediately preceding a media expression +MT.testMode( + 'atMediaInvalidAttribute', + '@media foobarhello { }', + [ + 'def', '@media', + null, ' ', + 'attribute error', 'foobarhello', + null, ' { }' + ] +); + +// Error, because "and" is only allowed immediately preceding a media expression +MT.testMode( + 'atMediaInvalidAnd', + '@media and screen { }', + [ + 'def', '@media', + null, ' ', + 'error', 'and', + null, ' ', + 'attribute', 'screen', + null, ' { }' + ] +); + +// Error, because "not" is only allowed as the first item in each media query +MT.testMode( + 'atMediaInvalidNot', + '@media screen not (not) { }', + [ + 'def', '@media', + null, ' ', + 'attribute', 'screen', + null, ' ', + 'error', 'not', + null, ' (', + 'error', 'not', + null, ') { }' + ] +); + +// Error, because "only" is only allowed as the first item in each media query +MT.testMode( + 'atMediaInvalidOnly', + '@media screen only (only) { }', + [ + 'def', '@media', + null, ' ', + 'attribute', 'screen', + null, ' ', + 'error', 'only', + null, ' (', + 'error', 'only', + null, ') { }' + ] +); + +// Error, because "foobarhello" is neither a known type or property, but +// property was expected (after "and"), and it should be in parenthese. +MT.testMode( + 'atMediaUnknownType', + '@media screen and foobarhello { }', + [ + 'def', '@media', + null, ' ', + 'attribute', 'screen', + null, ' ', + 'operator', 'and', + null, ' ', + 'error', 'foobarhello', + null, ' { }' + ] +); + +// Error, because "color" is not a known type, but is a known property, and +// should be in parentheses. +MT.testMode( + 'atMediaInvalidType', + '@media screen and color { }', + [ + 'def', '@media', + null, ' ', + 'attribute', 'screen', + null, ' ', + 'operator', 'and', + null, ' ', + 'error', 'color', + null, ' { }' + ] +); + +// Error, because "print" is not a known property, but is a known type, +// and should not be in parenthese. +MT.testMode( + 'atMediaInvalidProperty', + '@media screen and (print) { }', + [ + 'def', '@media', + null, ' ', + 'attribute', 'screen', + null, ' ', + 'operator', 'and', + null, ' (', + 'error', 'print', + null, ') { }' + ] +); + +// Soft error, because "foobarhello" is not a known property or type. +MT.testMode( + 'atMediaUnknownProperty', + '@media screen and (foobarhello) { }', + [ + 'def', '@media', + null, ' ', + 'attribute', 'screen', + null, ' ', + 'operator', 'and', + null, ' (', + 'property error', 'foobarhello', + null, ') { }' + ] +); + +MT.testMode( + 'tagSelector', + 'foo { }', + [ + 'tag', 'foo', + null, ' { }' + ] +); + +MT.testMode( + 'classSelector', + '.foo { }', + [ + 'qualifier', '.foo', + null, ' { }' + ] +); + +MT.testMode( + 'idSelector', + '#foo { #foo }', + [ + 'header', '#foo', + null, ' { ', + 'error', '#foo', + null, ' }' + ] +); + +MT.testMode( + 'tagSelectorUnclosed', + 'foo { margin: 0 } bar { }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'margin', + 'operator', ':', + null, ' ', + 'number', '0', + null, ' } ', + 'tag', 'bar', + null, ' { }' + ] +); + +MT.testMode( + 'tagStringNoQuotes', + 'foo { font-family: hello world; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'font-family', + 'operator', ':', + null, ' ', + 'variable-2', 'hello', + null, ' ', + 'variable-2', 'world', + null, '; }' + ] +); + +MT.testMode( + 'tagStringDouble', + 'foo { font-family: "hello world"; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'font-family', + 'operator', ':', + null, ' ', + 'string', '"hello world"', + null, '; }' + ] +); + +MT.testMode( + 'tagStringSingle', + 'foo { font-family: \'hello world\'; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'font-family', + 'operator', ':', + null, ' ', + 'string', '\'hello world\'', + null, '; }' + ] +); + +MT.testMode( + 'tagColorKeyword', + 'foo { color: black; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'color', + 'operator', ':', + null, ' ', + 'keyword', 'black', + null, '; }' + ] +); + +MT.testMode( + 'tagColorHex3', + 'foo { background: #fff; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'background', + 'operator', ':', + null, ' ', + 'atom', '#fff', + null, '; }' + ] +); + +MT.testMode( + 'tagColorHex6', + 'foo { background: #ffffff; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'background', + 'operator', ':', + null, ' ', + 'atom', '#ffffff', + null, '; }' + ] +); + +MT.testMode( + 'tagColorHex4', + 'foo { background: #ffff; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'background', + 'operator', ':', + null, ' ', + 'atom error', '#ffff', + null, '; }' + ] +); + +MT.testMode( + 'tagColorHexInvalid', + 'foo { background: #ffg; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'background', + 'operator', ':', + null, ' ', + 'atom error', '#ffg', + null, '; }' + ] +); + +MT.testMode( + 'tagNegativeNumber', + 'foo { margin: -5px; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'margin', + 'operator', ':', + null, ' ', + 'number', '-5px', + null, '; }' + ] +); + +MT.testMode( + 'tagPositiveNumber', + 'foo { padding: 5px; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'padding', + 'operator', ':', + null, ' ', + 'number', '5px', + null, '; }' + ] +); + +MT.testMode( + 'tagVendor', + 'foo { -foo-box-sizing: -foo-border-box; }', + [ + 'tag', 'foo', + null, ' { ', + 'meta', '-foo-', + 'property', 'box-sizing', + 'operator', ':', + null, ' ', + 'meta', '-foo-', + 'string-2', 'border-box', + null, '; }' + ] +); + +MT.testMode( + 'tagBogusProperty', + 'foo { barhelloworld: 0; }', + [ + 'tag', 'foo', + null, ' { ', + 'property error', 'barhelloworld', + 'operator', ':', + null, ' ', + 'number', '0', + null, '; }' + ] +); + +MT.testMode( + 'tagTwoProperties', + 'foo { margin: 0; padding: 0; }', + [ + 'tag', 'foo', + null, ' { ', + 'property', 'margin', + 'operator', ':', + null, ' ', + 'number', '0', + null, '; ', + 'property', 'padding', + 'operator', ':', + null, ' ', + 'number', '0', + null, '; }' + ] +); +// +//MT.testMode( +// 'tagClass', +// '@media only screen and (min-width: 500px), print {foo.bar#hello { color: black !important; background: #f00; margin: -5px; padding: 5px; -foo-box-sizing: border-box; } /* world */}', +// [ +// 'def', '@media', +// null, ' ', +// 'keyword', 'only', +// null, ' ', +// 'attribute', 'screen', +// null, ' ', +// 'operator', 'and', +// null, ' ', +// 'bracket', '(', +// 'property', 'min-width', +// 'operator', ':', +// null, ' ', +// 'number', '500px', +// 'bracket', ')', +// null, ', ', +// 'attribute', 'print', +// null, ' {', +// 'tag', 'foo', +// 'qualifier', '.bar', +// 'header', '#hello', +// null, ' { ', +// 'property', 'color', +// 'operator', ':', +// null, ' ', +// 'keyword', 'black', +// null, ' ', +// 'builtin', '!important', +// null, '; ', +// 'property', 'background', +// 'operator', ':', +// null, ' ', +// 'atom', '#f00', +// null, '; ', +// 'property', 'padding', +// 'operator', ':', +// null, ' ', +// 'number', '5px', +// null, '; ', +// 'property', 'margin', +// 'operator', ':', +// null, ' ', +// 'number', '-5px', +// null, '; ', +// 'meta', '-foo-', +// 'property', 'box-sizing', +// 'operator', ':', +// null, ' ', +// 'string-2', 'border-box', +// null, '; } ', +// 'comment', '/* world */', +// null, '}' +// ] +//); \ No newline at end of file diff --git a/test/index.html b/test/index.html index edea7d106f..9b10b62118 100644 --- a/test/index.html +++ b/test/index.html @@ -40,6 +40,8 @@

    CodeMirror: Test Suite

    + + From fdfba6db20ad626fdd5a71fc6273c503b3b3839f Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Sep 2012 09:53:55 +0200 Subject: [PATCH 035/135] [css mode] Partially move back to old look --- mode/css/css.css | 7 ------- mode/css/css.js | 4 ++-- mode/css/index.html | 1 - mode/css/test.js | 4 ++-- 4 files changed, 4 insertions(+), 12 deletions(-) delete mode 100644 mode/css/css.css diff --git a/mode/css/css.css b/mode/css/css.css deleted file mode 100644 index caa2afdac8..0000000000 --- a/mode/css/css.css +++ /dev/null @@ -1,7 +0,0 @@ -/* Change error (warning) so it's not so scary */ -.cm-s-default span.cm-property {font-weight: bold;} -.cm-s-default span.cm-property.cm-error {color: black; font-weight: normal;} - -/* Differentiate color from .cm-def and change error (warning) so it's not so scary */ -.cm-s-default span.cm-attribute {color: #c0c; font-weight: bold;} -.cm-s-default span.cm-attribute.cm-error {color: #c0c; font-weight: normal;} \ No newline at end of file diff --git a/mode/css/css.js b/mode/css/css.js index d7163201dd..5e3e233edb 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -211,7 +211,7 @@ CodeMirror.defineMode("css", function(config) { } else if (ch == "!") { stream.match(/^\s*\w*/); - return ret("builtin", "important"); + return ret("keyword", "important"); } else if (/\d/.test(ch)) { stream.eatWhile(/[\w.%]/); @@ -399,7 +399,7 @@ CodeMirror.defineMode("css", function(config) { } } else if (style == "atom") { if(!context || context == "@media{") { - style = "header"; + style = "builtin"; } else if (context == "propertyValue") { if (!/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) { style += " error"; diff --git a/mode/css/index.html b/mode/css/index.html index c77a11c5db..ae2c3bfcee 100644 --- a/mode/css/index.html +++ b/mode/css/index.html @@ -6,7 +6,6 @@ - diff --git a/mode/css/test.js b/mode/css/test.js index f54336e1e5..4e2d0e8e56 100644 --- a/mode/css/test.js +++ b/mode/css/test.js @@ -228,7 +228,7 @@ MT.testMode( 'idSelector', '#foo { #foo }', [ - 'header', '#foo', + 'builtin', '#foo', null, ' { ', 'error', '#foo', null, ' }' @@ -472,7 +472,7 @@ MT.testMode( // null, ' ', // 'keyword', 'black', // null, ' ', -// 'builtin', '!important', +// 'keyword', '!important', // null, '; ', // 'property', 'background', // 'operator', ':', From 328780de299eabb4795eff8e04a87c33769ca1ab Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Sep 2012 10:28:36 +0200 Subject: [PATCH 036/135] On Webkit, add file/line no to test failures caused by errors --- test/driver.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/driver.js b/test/driver.js index 5ad20425d6..934cb936c9 100644 --- a/test/driver.js +++ b/test/driver.js @@ -89,7 +89,10 @@ function runTests(callback) { } catch(e) { if (expFail) callback("expected", test.name); else if (e instanceof Failure) callback("fail", test.name, e.message); - else callback("error", test.name, e.toString()); + else { + var pos = /\bat .*?([^\/:]+):(\d+):/.exec(e.stack); + callback("error", test.name, e.toString() + (pos ? " (" + pos[1] + ":" + pos[2] + ")" : "")); + } } if (!quit) { // Run next test var delay = 0; From 0d3df7ef16add9a11f3632bcbd048a4a243187bf Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 7 Sep 2012 18:05:32 +0200 Subject: [PATCH 037/135] Make undo/redo preserve text markers and bookmarks Cleans up the implementation of marked ranges, makes the data structure for markers-within-a-line persistant, and attaches them to the lines stored in the undo history when necessary. Closes #675 --- doc/manual.html | 12 +- lib/codemirror.js | 467 +++++++++++++++++++++------------------------- test/test.js | 26 ++- 3 files changed, 241 insertions(+), 264 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index 2d1a136175..758b4e5839 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -590,14 +590,20 @@

    Programming API

    state
    The mode's state at the end of this token.
    -
    markText(from, to, className) → object
    +
    markText(from, to, className, options) → object
    Can be used to mark a range of text with a specific CSS class name. from and to should - be {line, ch} objects. The method will return an + be {line, ch} objects. The options + parameter is optional, and can be an object + with inclusiveLeft and inclusiveRight + properties, which determine whether typing at the left or right + of the marker will cause the new text to become part of the + marker (the default is false for both). The method will return an object with two methods, clear(), which removes the mark, and find(), which returns a {from, to} (both document positions), indicating the current - position of the marked range.
    + position of the marked range, or undefined if the + marker is no longer in the document.
    setBookmark(pos) → object
    Inserts a bookmark, a handle that follows the text around it diff --git a/lib/codemirror.js b/lib/codemirror.js index eba48afca7..55c209427a 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -702,13 +702,16 @@ window.CodeMirror = (function() { // Afterwards, set the selection to selFrom, selTo. function updateLines(from, to, newText, selFrom, selTo) { if (suppressEdits) return; + var old = []; + doc.iter(from.line, to.line + 1, function(line) { + old.push(newHL(line.text, line.markedSpans)); + }); if (history) { - var old = []; - doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); }); history.addChange(from.line, newText.length, old); while (history.done.length > options.undoDepth) history.done.shift(); } - updateLinesNoUndo(from, to, newText, selFrom, selTo); + var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(old[old.length-1]), from.ch, to.ch, newText); + updateLinesNoUndo(from, to, lines, selFrom, selTo); } function unredoHelper(from, to) { if (!from.length) return; @@ -716,11 +719,12 @@ window.CodeMirror = (function() { for (var i = set.length - 1; i >= 0; i -= 1) { var change = set[i]; var replaced = [], end = change.start + change.added; - doc.iter(change.start, end, function(line) { replaced.push(line.text); }); + doc.iter(change.start, end, function(line) { replaced.push(newHL(line.text, line.markedSpans)); }); out.push({start: change.start, added: change.old.length, old: replaced}); var pos = {line: change.start + change.old.length - 1, - ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])}; - updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos); + ch: editEnd(hlText(replaced[replaced.length-1]), hlText(change.old[change.old.length-1]))}; + updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, + change.old, pos, pos); } updateInput = true; to.push(out); @@ -728,66 +732,59 @@ window.CodeMirror = (function() { function undo() {unredoHelper(history.done, history.undone);} function redo() {unredoHelper(history.undone, history.done);} - function updateLinesNoUndo(from, to, newText, selFrom, selTo) { + function updateLinesNoUndo(from, to, lines, selFrom, selTo) { if (suppressEdits) return; var recomputeMaxLength = false, maxLineLength = maxLine.text.length; if (!options.lineWrapping) doc.iter(from.line, to.line + 1, function(line) { if (!line.hidden && line.text.length == maxLineLength) {recomputeMaxLength = true; return true;} }); - if (from.line != to.line || newText.length > 1) gutterDirty = true; + if (from.line != to.line || lines.length > 1) gutterDirty = true; var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line); - // First adjust the line structure, taking some care to leave highlighting intact. - if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") { + var lastHL = lines[lines.length-1]; + + // First adjust the line structure + if (from.ch == 0 && to.ch == 0 && hlText(lastHL) == "") { // This is a whole-line replace. Treated specially to make // sure line objects move the way they are supposed to. var added = [], prevLine = null; - if (from.line) { - prevLine = getLine(from.line - 1); - prevLine.fixMarkEnds(lastLine); - } else lastLine.fixMarkStarts(); - for (var i = 0, e = newText.length - 1; i < e; ++i) - added.push(Line.inheritMarks(newText[i], prevLine)); + for (var i = 0, e = lines.length - 1; i < e; ++i) + added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); + lastLine.update(lastLine.text, hlSpans(lastHL)); if (nlines) doc.remove(from.line, nlines, callbacks); if (added.length) doc.insert(from.line, added); } else if (firstLine == lastLine) { - if (newText.length == 1) - firstLine.replace(from.ch, to.ch, newText[0]); - else { - lastLine = firstLine.split(to.ch, newText[newText.length-1]); - firstLine.replace(from.ch, null, newText[0]); - firstLine.fixMarkEnds(lastLine); - var added = []; - for (var i = 1, e = newText.length - 1; i < e; ++i) - added.push(Line.inheritMarks(newText[i], firstLine)); - added.push(lastLine); + if (lines.length == 1) { + firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + firstLine.text.slice(to.ch), hlSpans(lines[0])); + } else { + for (var added = [], i = 1, e = lines.length - 1; i < e; ++i) + added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); + added.push(new Line(hlText(lastHL) + firstLine.text.slice(to.ch), hlSpans(lastHL))); + firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); doc.insert(from.line + 1, added); } - } else if (newText.length == 1) { - firstLine.replace(from.ch, null, newText[0]); - lastLine.replace(null, to.ch, ""); - firstLine.append(lastLine); + } else if (lines.length == 1) { + firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + lastLine.text.slice(to.ch), hlSpans(lines[0])); doc.remove(from.line + 1, nlines, callbacks); } else { var added = []; - firstLine.replace(from.ch, null, newText[0]); - lastLine.replace(null, to.ch, newText[newText.length-1]); - firstLine.fixMarkEnds(lastLine); - for (var i = 1, e = newText.length - 1; i < e; ++i) - added.push(Line.inheritMarks(newText[i], firstLine)); + firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); + lastLine.update(hlText(lastHL) + lastLine.text.slice(to.ch), hlSpans(lastHL)); + for (var i = 1, e = lines.length - 1; i < e; ++i) + added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks); doc.insert(from.line + 1, added); } if (options.lineWrapping) { var perLine = Math.max(5, scroller.clientWidth / charWidth() - 3); - doc.iter(from.line, from.line + newText.length, function(line) { + doc.iter(from.line, from.line + lines.length, function(line) { if (line.hidden) return; var guess = Math.ceil(line.text.length / perLine) || 1; if (guess != line.height) updateLineHeight(line, guess); }); } else { - doc.iter(from.line, from.line + newText.length, function(line) { + doc.iter(from.line, from.line + lines.length, function(line) { var l = line.text; if (!line.hidden && l.length > maxLineLength) { maxLine = line; maxLineLength = l.length; maxLineChanged = true; @@ -801,14 +798,20 @@ window.CodeMirror = (function() { frontier = Math.min(frontier, from.line); startWorker(400); - var lendiff = newText.length - nlines - 1; + var lendiff = lines.length - nlines - 1; // Remember that these lines changed, for updating the display changes.push({from: from.line, to: to.line + 1, diff: lendiff}); - var changeObj = {from: from, to: to, text: newText}; - if (textChanged) { - for (var cur = textChanged; cur.next; cur = cur.next) {} - cur.next = changeObj; - } else textChanged = changeObj; + if (options.onChange) { + // Normalize lines to contain only strings, since that's what + // the change event handler expects + for (var i = 0; i < lines.length; ++i) + if (typeof lines[i] != "string") lines[i] = lines[i].text; + var changeObj = {from: from, to: to, text: lines}; + if (textChanged) { + for (var cur = textChanged; cur.next; cur = cur.next) {} + cur.next = changeObj; + } else textChanged = changeObj; + } // Update the selection function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;} @@ -1488,74 +1491,71 @@ window.CodeMirror = (function() { (style ? " cm-keymap-" + style : ""); } - function TextMarker() { this.set = []; } + function TextMarker(type, style) { this.lines = []; this.type = type; if (style) this.style = style; } TextMarker.prototype.clear = operation(function() { var min = Infinity, max = -Infinity; - for (var i = 0, e = this.set.length; i < e; ++i) { - var line = this.set[i], mk = line.marked; - if (!mk || !line.parent) continue; - var lineN = lineNo(line); - min = Math.min(min, lineN); max = Math.max(max, lineN); - for (var j = 0; j < mk.length; ++j) - if (mk[j].marker == this) mk.splice(j--, 1); + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this, true); + if (span.from != null || span.to != null) { + var lineN = lineNo(line); + min = Math.min(min, lineN); max = Math.max(max, lineN); + } } if (min != Infinity) changes.push({from: min, to: max + 1}); + this.lines.length = 0; }); TextMarker.prototype.find = function() { var from, to; - for (var i = 0, e = this.set.length; i < e; ++i) { - var line = this.set[i], mk = line.marked; - for (var j = 0; j < mk.length; ++j) { - var mark = mk[j]; - if (mark.marker == this) { - if (mark.from != null || mark.to != null) { - var found = lineNo(line); - if (found != null) { - if (mark.from != null) from = {line: found, ch: mark.from}; - if (mark.to != null) to = {line: found, ch: mark.to}; - } - } - } + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null || span.to != null) { + var found = lineNo(line); + if (span.from != null) from = {line: found, ch: span.from}; + if (span.to != null) to = {line: found, ch: span.to}; } } - return {from: from, to: to}; + if (this.type == "bookmark") return from; + return from && {from: from, to: to}; }; - function markText(from, to, className) { + function markText(from, to, className, options) { from = clipPos(from); to = clipPos(to); - var tm = new TextMarker(); - if (!posLess(from, to)) return tm; - function add(line, from, to, className) { - getLine(line).addMark(new MarkedText(from, to, className, tm)); - } - if (from.line == to.line) add(from.line, from.ch, to.ch, className); - else { - add(from.line, from.ch, null, className); - for (var i = from.line + 1, e = to.line; i < e; ++i) - add(i, null, null, className); - add(to.line, null, to.ch, className); - } + var marker = new TextMarker("range", className); + if (options && options.inclusiveLeft) marker.inclusiveLeft = true; + if (options && options.inclusiveRight) marker.inclusiveRight = true; + var curLine = from.line; + doc.iter(curLine, to.line + 1, function(line) { + var span = {from: curLine == from.line ? from.ch : null, + to: curLine == to.line ? to.ch : null, + marker: marker}; + (line.markedSpans || (line.markedSpans = [])).push(span); + marker.lines.push(line); + ++curLine; + }); changes.push({from: from.line, to: to.line + 1}); - return tm; + return marker; } function setBookmark(pos) { pos = clipPos(pos); - var bm = new Bookmark(pos.ch); - getLine(pos.line).addMark(bm); - return bm; + var marker = new TextMarker("bookmark"), line = getLine(pos.line); + var span = {from: pos.ch, to: pos.ch, marker: marker}; + (line.markedSpans || (line.markedSpans = [])).push(span); + marker.lines.push(line); + return marker; } function findMarksAt(pos) { pos = clipPos(pos); - var markers = [], marked = getLine(pos.line).marked; - if (!marked) return markers; - for (var i = 0, e = marked.length; i < e; ++i) { - var m = marked[i]; - if ((m.from == null || m.from <= pos.ch) && - (m.to == null || m.to >= pos.ch)) - markers.push(m.marker || m); + var markers = [], spans = getLine(pos.line).markedSpans; + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + markers.push(span.marker); } return markers; } @@ -2316,69 +2316,123 @@ window.CodeMirror = (function() { }; CodeMirror.StringStream = StringStream; - function MarkedText(from, to, className, marker) { - this.from = from; this.to = to; this.style = className; this.marker = marker; + function MarkedSpan(from, to, marker) { + this.from = from; this.to = to; this.marker = marker; } - MarkedText.prototype = { - attach: function(line) { this.marker.set.push(line); }, - detach: function(line) { - var ix = indexOf(this.marker.set, line); - if (ix > -1) this.marker.set.splice(ix, 1); - }, - split: function(pos, lenBefore) { - if (this.to <= pos && this.to != null) return null; - var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore; - var to = this.to == null ? null : this.to - pos + lenBefore; - return new MarkedText(from, to, this.style, this.marker); - }, - dup: function() { return new MarkedText(null, null, this.style, this.marker); }, - clipTo: function(fromOpen, from, toOpen, to, diff) { - if (fromOpen && to > this.from && (to < this.to || this.to == null)) - this.from = null; - else if (this.from != null && this.from >= from) - this.from = Math.max(to, this.from) + diff; - if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null)) - this.to = null; - else if (this.to != null && this.to > from) - this.to = to < this.to ? this.to + diff : from; - }, - isDead: function() { return this.from != null && this.to != null && this.from >= this.to; }, - sameSet: function(x) { return this.marker == x.marker; } - }; - function Bookmark(pos) { - this.from = pos; this.to = pos; this.line = null; + function getMarkedSpanFor(spans, marker, del) { + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.marker == marker) { + if (del) spans.splice(i, 1); + return span; + } + } } - Bookmark.prototype = { - attach: function(line) { this.line = line; }, - detach: function(line) { if (this.line == line) this.line = null; }, - split: function(pos, lenBefore) { - if (pos < this.from) { - this.from = this.to = (this.from - pos) + lenBefore; - return this; + + function markedSpansBefore(old, startCh, endCh) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); + if (startsBefore || marker.type == "bookmark" && span.from == startCh && span.from != endCh) { + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); + (nw || (nw = [])).push({from: span.from, + to: endsAfter ? null : span.to, + marker: marker}); } - }, - isDead: function() { return this.from > this.to; }, - clipTo: function(fromOpen, from, toOpen, to, diff) { - if ((fromOpen || from < this.from) && (toOpen || to > this.to)) { - this.from = 0; this.to = -1; - } else if (this.from > from) { - this.from = this.to = Math.max(to, this.from) + diff; + } + return nw; + } + + function markedSpansAfter(old, endCh) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); + if (endsAfter || marker.type == "bookmark" && span.from == endCh) { + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); + (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh, + to: span.to == null ? null : span.to - endCh, + marker: marker}); } - }, - sameSet: function(x) { return false; }, - find: function() { - if (!this.line || !this.line.parent) return null; - return {line: lineNo(this.line), ch: this.from}; - }, - clear: function() { - if (this.line) { - var found = indexOf(this.line.marked, this); - if (found != -1) this.line.marked.splice(found, 1); - this.line = null; + } + return nw; + } + + function updateMarkedSpans(oldFirst, oldLast, startCh, endCh, newText) { + if (!oldFirst && !oldLast) return newText; + // Get the spans that 'stick out' on both sides + var first = markedSpansBefore(oldFirst, startCh); + var last = markedSpansAfter(oldLast, endCh); + + // Next, merge those two ends + var sameLine = newText.length == 1, offset = newText[newText.length-1].length + (sameLine ? startCh : 0); + if (first) { + // Fix up .to properties of first + for (var i = 0; i < first.length; ++i) { + var span = first[i]; + if (span.to == null) { + var found = getMarkedSpanFor(last, span.marker); + if (!found) span.to = startCh; + else if (sameLine) span.to = found.to == null ? null : found.to + offset; + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (var i = 0; i < last.length; ++i) { + var span = last[i]; + if (span.to != null) span.to += offset; + if (span.from == null) { + var found = getMarkedSpanFor(first, span.marker); + if (!found) { + span.from = offset; + if (sameLine) (first || (first = [])).push(span); + } + } else { + span.from += offset; + if (sameLine) (first || (first = [])).push(span); + } } } - }; + + var newMarkers = [newHL(newText[0], first)]; + if (!sameLine) { + // Fill gap with whole-line-spans + var gap = newText.length - 2, gapMarkers; + if (gap > 0 && first) + for (var i = 0; i < first.length; ++i) + if (first[i].to == null) + (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker}); + for (var i = 0; i < gap; ++i) + newMarkers.push(newHL(newText[i+1], gapMarkers)); + newMarkers.push(newHL(newText[newText.length-1], last)); + } + return newMarkers; + } + + // hl stands for history-line, a data structure that can be either a + // string (line without markers) or a {text, markedSpans} object. + function hlText(val) { return typeof val == "string" ? val : val.text; } + function hlSpans(val) { return typeof val == "string" ? null : val.markedSpans; } + function newHL(text, spans) { return spans ? {text: text, markedSpans: spans} : text; } + + function detachMarkedSpans(line) { + var spans = line.markedSpans; + if (!spans) return; + for (var i = 0; i < spans.length; ++i) { + var lines = spans[i].marker.lines; + var ix = indexOf(lines, line); + lines.splice(ix, 1); + } + line.markedSpans = null; + } + + function attachMarkedSpans(line, spans) { + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + var marker = spans[i].marker.lines.push(line); + line.markedSpans = spans; + } // When measuring the position of the end of a line, different // browsers require different approaches. If an empty span is added, @@ -2392,118 +2446,17 @@ window.CodeMirror = (function() { // Line objects. These hold state related to a line, including // highlighting info (the styles array). - function Line(text) { + function Line(text, markedSpans) { this.text = text; this.height = 1; + attachMarkedSpans(this, markedSpans); } - Line.inheritMarks = function(text, orig) { - var ln = new Line(text), mk = orig && orig.marked; - if (mk) { - for (var i = 0; i < mk.length; ++i) { - if (mk[i].to == null && mk[i].style) { - var newmk = ln.marked || (ln.marked = []), mark = mk[i]; - var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln); - } - } - } - return ln; - }; Line.prototype = { - // Replace a piece of a line, keeping the markers intact. - replace: function(from, to_, text) { - var mk = this.marked, to = to_ == null ? this.text.length : to_; - this.text = this.text.slice(0, from) + text + this.text.slice(to); + update: function(text, markedSpans) { + this.text = text; this.stateAfter = this.styles = null; - if (mk) { - var diff = text.length - (to - from); - for (var i = 0; i < mk.length; ++i) { - var mark = mk[i]; - mark.clipTo(from == null, from || 0, to_ == null, to, diff); - if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);} - } - } - }, - // Split a part off a line, keeping markers intact. - split: function(pos, textBefore) { - var mk = this.marked; - var taken = new Line(textBefore + this.text.slice(pos)); - if (mk) { - for (var i = 0; i < mk.length; ++i) { - var mark = mk[i]; - var newmark = mark.split(pos, textBefore.length); - if (newmark) { - if (!taken.marked) taken.marked = []; - taken.marked.push(newmark); newmark.attach(taken); - if (newmark == mark) mk.splice(i--, 1); - } - } - } - return taken; - }, - append: function(line) { - var mylen = this.text.length, mk = line.marked, mymk = this.marked; - this.text += line.text; - this.styles = this.stateAfter = null; - if (mymk) { - for (var i = 0; i < mymk.length; ++i) - if (mymk[i].to == null) mymk[i].to = mylen; - } - if (mk && mk.length) { - if (!mymk) this.marked = mymk = []; - outer: for (var i = 0; i < mk.length; ++i) { - var mark = mk[i]; - if (!mark.from) { - for (var j = 0; j < mymk.length; ++j) { - var mymark = mymk[j]; - if (mymark.to == mylen && mymark.sameSet(mark)) { - mymark.to = mark.to == null ? null : mark.to + mylen; - if (mymark.isDead()) { - mymark.detach(this); - mk.splice(i--, 1); - } - continue outer; - } - } - } - mymk.push(mark); - mark.attach(this); - mark.from += mylen; - if (mark.to != null) mark.to += mylen; - } - } - }, - fixMarkEnds: function(other) { - var mk = this.marked, omk = other.marked; - if (!mk) return; - outer: for (var i = 0; i < mk.length; ++i) { - var mark = mk[i], close = mark.to == null; - if (close && omk) { - for (var j = 0; j < omk.length; ++j) { - var om = omk[j]; - if (!om.sameSet(mark) || om.from != null) continue; - if (mark.from == this.text.length && om.to == 0) { - omk.splice(j, 1); - mk.splice(i--, 1); - continue outer; - } else { - close = false; break; - } - } - } - if (close) mark.to = this.text.length; - } - }, - fixMarkStarts: function() { - var mk = this.marked; - if (!mk) return; - for (var i = 0; i < mk.length; ++i) - if (mk[i].from == null) mk[i].from = 0; - }, - addMark: function(mark) { - mark.attach(this); - if (this.marked == null) this.marked = []; - this.marked.push(mark); - this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);}); + detachMarkedSpans(this); + attachMarkedSpans(this, markedSpans); }, // Run the given mode's parser over a line, update the styles // array, which contains alternating fragments of text and CSS @@ -2620,7 +2573,7 @@ window.CodeMirror = (function() { }; } - var st = this.styles, allText = this.text, marked = this.marked; + var st = this.styles, allText = this.text, marked = this.markedSpans; var len = allText.length; function styleToClass(style) { if (!style) return null; @@ -2636,13 +2589,14 @@ window.CodeMirror = (function() { span(pre, str, styleToClass(style)); } } else { + marked.sort(function(a, b) { return a.from - b.from; }); var pos = 0, i = 0, text = "", style, sg = 0; var nextChange = marked[0].from || 0, marks = [], markpos = 0; var advanceMarks = function() { var m; while (markpos < marked.length && ((m = marked[markpos]).from == pos || m.from == null)) { - if (m.style != null) marks.push(m); + if (m.marker.style != null) marks.push(m); ++markpos; } nextChange = markpos < marked.length ? marked[markpos].from : Infinity; @@ -2662,7 +2616,7 @@ window.CodeMirror = (function() { var end = pos + text.length; var appliedStyle = style; for (var j = 0; j < marks.length; ++j) - appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].style; + appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].marker.style; span(pre, end > upto ? text.slice(0, upto - pos) : text, appliedStyle); if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} pos = end; @@ -2675,8 +2629,7 @@ window.CodeMirror = (function() { }, cleanUp: function() { this.parent = null; - if (this.marked) - for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this); + detachMarkedSpans(this); } }; diff --git a/test/test.js b/test/test.js index 1c307eaba8..bf72734045 100644 --- a/test/test.js +++ b/test/test.js @@ -257,13 +257,14 @@ testCM("markTextSingleLine", function(cm) { var r = cm.markText({line: 0, ch: 3}, {line: 0, ch: 6}, "foo"); cm.replaceRange(test.c, {line: 0, ch: test.a}, {line: 0, ch: test.b}); var f = r.find(); - eq(f.from && f.from.ch, test.f); eq(f.to && f.to.ch, test.t); + eq(f && f.from.ch, test.f); eq(f && f.to.ch, test.t); }); }); testCM("markTextMultiLine", function(cm) { function p(v) { return v && {line: v[0], ch: v[1]}; } forEach([{a: [0, 0], b: [0, 5], c: "", f: [0, 0], t: [2, 5]}, + {a: [0, 0], b: [0, 5], c: "foo\n", f: [1, 0], t: [3, 5]}, {a: [0, 1], b: [0, 10], c: "", f: [0, 1], t: [2, 5]}, {a: [0, 5], b: [0, 6], c: "x", f: [0, 6], t: [2, 5]}, {a: [0, 0], b: [1, 0], c: "", f: [0, 0], t: [1, 5]}, @@ -275,16 +276,33 @@ testCM("markTextMultiLine", function(cm) { {a: [1, 5], b: [2, 5], c: "", f: [0, 5], t: [1, 5]}, {a: [2, 0], b: [2, 3], c: "", f: [0, 5], t: [2, 2]}, {a: [2, 5], b: [3, 0], c: "a\nb", f: [0, 5], t: [2, 5]}, - {a: [2, 3], b: [3, 0], c: "x", f: [0, 5], t: [2, 4]}, + {a: [2, 3], b: [3, 0], c: "x", f: [0, 5], t: [2, 3]}, {a: [1, 1], b: [1, 9], c: "1\n2\n3", f: [0, 5], t: [4, 5]}], function(test) { cm.setValue("aaaaaaaaaa\nbbbbbbbbbb\ncccccccccc\ndddddddd\n"); - var r = cm.markText({line: 0, ch: 5}, {line: 2, ch: 5}, "foo"); + var r = cm.markText({line: 0, ch: 5}, {line: 2, ch: 5}, "CodeMirror-matchingbracket"); cm.replaceRange(test.c, p(test.a), p(test.b)); var f = r.find(); - eqPos(f.from, p(test.f)); eqPos(f.to, p(test.t)); + eqPos(f && f.from, p(test.f)); eqPos(f && f.to, p(test.t)); }); }); +testCM("markTextUndo", function(cm) { + var marker1 = cm.markText({line: 0, ch: 1}, {line: 0, ch: 3}, "CodeMirror-matchingbracket"); + var marker2 = cm.markText({line: 0, ch: 0}, {line: 2, ch: 1}, "CodeMirror-matchingbracket"); + var bookmark = cm.setBookmark({line: 1, ch: 5}); + cm.replaceRange("foo", {line: 0, ch: 2}); + cm.replaceRange("bar\baz\bug\n", {line: 2, ch: 0}, {line: 3, ch: 0}); + cm.setValue(""); + eq(marker1.find(), null); eq(marker2.find(), null); eq(bookmark.find(), null); + cm.undo(); + eqPos(bookmark.find(), {line: 1, ch: 5}); + cm.undo(); cm.undo(); + var m1Pos = marker1.find(), m2Pos = marker2.find(); + eqPos(m1Pos.from, {line: 0, ch: 1}); eqPos(m1Pos.to, {line: 0, ch: 3}); + eqPos(m2Pos.from, {line: 0, ch: 0}); eqPos(m2Pos.to, {line: 2, ch: 1}); + eqPos(bookmark.find(), {line: 1, ch: 5}); +}, {value: "1234\n56789\n00\n"}); + testCM("markClearBetween", function(cm) { cm.setValue("aaa\nbbb\nccc\nddd\n"); cm.markText({line: 0, ch: 0}, {line: 2}, "foo"); From 9a7a53c2874a0d16674fd84bf24d7aefbc4dd624 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Sep 2012 12:15:12 +0200 Subject: [PATCH 038/135] Introduce a lst() function to reduce arr[arr.length-1] noise --- lib/codemirror.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 55c209427a..c7bb7cd606 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -710,7 +710,7 @@ window.CodeMirror = (function() { history.addChange(from.line, newText.length, old); while (history.done.length > options.undoDepth) history.done.shift(); } - var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(old[old.length-1]), from.ch, to.ch, newText); + var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(lst(old)), from.ch, to.ch, newText); updateLinesNoUndo(from, to, lines, selFrom, selTo); } function unredoHelper(from, to) { @@ -722,7 +722,7 @@ window.CodeMirror = (function() { doc.iter(change.start, end, function(line) { replaced.push(newHL(line.text, line.markedSpans)); }); out.push({start: change.start, added: change.old.length, old: replaced}); var pos = {line: change.start + change.old.length - 1, - ch: editEnd(hlText(replaced[replaced.length-1]), hlText(change.old[change.old.length-1]))}; + ch: editEnd(hlText(lst(replaced)), hlText(lst(change.old)))}; updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos); } @@ -742,7 +742,7 @@ window.CodeMirror = (function() { if (from.line != to.line || lines.length > 1) gutterDirty = true; var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line); - var lastHL = lines[lines.length-1]; + var lastHL = lst(lines); // First adjust the line structure if (from.ch == 0 && to.ch == 0 && hlText(lastHL) == "") { @@ -873,7 +873,7 @@ window.CodeMirror = (function() { var line = pos.line + code.length - (to.line - from.line) - 1; var ch = pos.ch; if (pos.line == to.line) - ch += code[code.length-1].length - (to.ch - (to.line == from.line ? from.ch : 0)); + ch += lst(code).length - (to.ch - (to.line == from.line ? from.ch : 0)); return {line: line, ch: ch}; } var end; @@ -891,7 +891,7 @@ window.CodeMirror = (function() { }); } function replaceRange1(code, from, to, computeSel) { - var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length; + var endch = code.length == 1 ? code[0].length + from.ch : lst(code).length; var newSel = computeSel({line: from.line + code.length - 1, ch: endch}); updateLines(from, to, code, newSel.from, newSel.to); } @@ -2365,7 +2365,7 @@ window.CodeMirror = (function() { var last = markedSpansAfter(oldLast, endCh); // Next, merge those two ends - var sameLine = newText.length == 1, offset = newText[newText.length-1].length + (sameLine ? startCh : 0); + var sameLine = newText.length == 1, offset = lst(newText).length + (sameLine ? startCh : 0); if (first) { // Fix up .to properties of first for (var i = 0; i < first.length; ++i) { @@ -2405,7 +2405,7 @@ window.CodeMirror = (function() { (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker}); for (var i = 0; i < gap; ++i) newMarkers.push(newHL(newText[i+1], gapMarkers)); - newMarkers.push(newHL(newText[newText.length-1], last)); + newMarkers.push(newHL(lst(newText), last)); } return newMarkers; } @@ -2832,7 +2832,7 @@ window.CodeMirror = (function() { History.prototype = { addChange: function(start, added, old) { this.undone.length = 0; - var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1]; + var time = +new Date, cur = lst(this.done), last = cur && lst(cur); var dtime = time - this.time; if (this.compound && cur && !this.closed) { @@ -2985,10 +2985,12 @@ window.CodeMirror = (function() { var spaceStrs = [""]; function spaceStr(n) { while (spaceStrs.length <= n) - spaceStrs.push(spaceStrs[spaceStrs.length - 1] + " "); + spaceStrs.push(lst(spaceStrs) + " "); return spaceStrs[n]; } + function lst(arr) { return arr[arr.length-1]; } + function selectInput(node) { if (ios) { // Mobile Safari apparently has a bug where select() is broken. node.selectionStart = 0; From d00082ab428da2ea7c5e9dd2bcfb36c9ed360ddd Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Sep 2012 13:33:01 +0200 Subject: [PATCH 039/135] Downcase package name in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8658354fc0..9a0d99c2e8 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "CodeMirror", + "name": "codemirror", "version":"2.33.0", "main": "codemirror.js", "description": "In-browser code editing made bearable", From f23da64e25b4a49c267b84dff9287c3e5a82b6f0 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Sep 2012 16:42:34 +0200 Subject: [PATCH 040/135] Add a Common Lisp mode --- doc/compress.html | 1 + index.html | 1 + lib/codemirror.css | 4 +- mode/commonlisp/commonlisp.js | 98 ++++++++++++++++++++ mode/commonlisp/index.html | 165 ++++++++++++++++++++++++++++++++++ 5 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 mode/commonlisp/commonlisp.js create mode 100644 mode/commonlisp/index.html diff --git a/doc/compress.html b/doc/compress.html index a651072c5d..3b99deae27 100644 --- a/doc/compress.html +++ b/doc/compress.html @@ -62,6 +62,7 @@

    { } CodeMi + diff --git a/index.html b/index.html index 5a5a39c5e2..f7ae5ea393 100644 --- a/index.html +++ b/index.html @@ -38,6 +38,7 @@

    Supported modes:

  • C, C++, C#
  • Clojure
  • CoffeeScript
  • +
  • Common Lisp
  • CSS
  • diff
  • ECL
  • diff --git a/lib/codemirror.css b/lib/codemirror.css index f0e91b2d73..05ad0ed013 100644 --- a/lib/codemirror.css +++ b/lib/codemirror.css @@ -145,7 +145,7 @@ div.CodeMirror-selected { background: #d9d9d9; } .cm-s-default span.cm-error {color: #f00;} .cm-s-default span.cm-qualifier {color: #555;} .cm-s-default span.cm-builtin {color: #30a;} -.cm-s-default span.cm-bracket {color: #cc7;} +.cm-s-default span.cm-bracket {color: #997;} .cm-s-default span.cm-tag {color: #170;} .cm-s-default span.cm-attribute {color: #00c;} .cm-s-default span.cm-header {color: blue;} @@ -170,4 +170,4 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} visibility: hidden; } -} \ No newline at end of file +} diff --git a/mode/commonlisp/commonlisp.js b/mode/commonlisp/commonlisp.js new file mode 100644 index 0000000000..b95a8686bb --- /dev/null +++ b/mode/commonlisp/commonlisp.js @@ -0,0 +1,98 @@ +CodeMirror.defineMode("commonlisp", function (config) { + var assumeBody = /^with|^def|^do|^prog|case$|^cond$|bind$|when$|unless$/; + var numLiteral = /^(?:[+\-]?(?:\d+|\d*\.\d+)(?:[efd][+\-]?\d+)?|[+\-]?\d+(?:\/[+\-]?\d+)?|#b[+\-]?[01]+|#o[+\-]?[0-7]+|#x[+\-]?[\da-f]+)/; + var symbol = /[^\s'`,@()\[\]";]/; + var type; + + function readSym(stream) { + while (ch = stream.next()) { + if (ch == "\\") stream.next(); + else if (!symbol.test(ch)) { stream.backUp(1); break; } + } + return stream.current(); + } + + function base(stream, state) { + if (stream.eatSpace()) {type = "ws"; return null;} + if (stream.match(numLiteral)) return "number"; + var ch = stream.next(); + if (ch == "\\") ch = stream.next(); + + if (ch == '"') return (state.tokenize = inString)(stream, state); + else if (ch == "(") { type = "open"; return "bracket"; } + else if (ch == ")" || ch == "]") { type = "close"; return "bracket"; } + else if (ch == ";") { stream.skipToEnd(); type = "ws"; return "comment"; } + else if (/['`,@]/.test(ch)) return null; + else if (ch == "|") { + if (stream.skipTo("|")) { stream.next(); return "symbol"; } + else { stream.skipToEnd(); return "error"; } + } else if (ch == "#") { + if (stream.eat("[")) { type = "open"; return "bracket"; } + else if (stream.eat(/[+\-=\.]/)) return null; + else if (stream.match(/^\d+#/)) return null; + else if (stream.eat("|")) return (state.tokenize = inComment)(stream, state); + else if (stream.eat(":")) { readSym(stream); return "meta"; } + else { stream.next(); return "error"; } + } else { + var name = readSym(stream); + if (name == ".") return null; + type = "symbol"; + if (name == "nil" || name == "t") return "atom"; + if (name.charAt(0) == ":") return "keyword"; + return "variable"; + } + } + + function inString(stream, state) { + var escaped = false, next; + while (next = stream.next()) { + if (next == '"' && !escaped) { state.tokenize = base; break; } + escaped = !escaped && next == "\\"; + } + return "string"; + } + + function inComment(stream, state) { + var next, last; + while (next = stream.next()) { + if (next == "#" && last == "|") { state.tokenize = base; break; } + last = next; + } + type = "ws"; + return "comment"; + } + + return { + startState: function () { + return {ctx: {prev: null, start: 0, indentTo: 0}, tokenize: base}; + }, + + token: function (stream, state) { + if (stream.sol() && typeof state.ctx.indentTo != "number") + state.ctx.indentTo = state.ctx.start + 1; + + type = null; + var style = state.tokenize(stream, state); + if (type != "ws") { + if (state.ctx.indentTo == null) { + if (type == "symbol" && assumeBody.test(stream.current())) + state.ctx.indentTo = state.ctx.start + config.indentUnit; + else + state.ctx.indentTo = "next"; + } else if (state.ctx.indentTo == "next") { + state.ctx.indentTo = stream.column(); + } + } + if (type == "open") state.ctx = {prev: state.ctx, start: stream.column(), indentTo: null}; + else if (type == "close") state.ctx = state.ctx.prev; + return style; + }, + + indent: function (state, textAfter) { + var i = state.ctx.indentTo; + return typeof i == "number" ? i : state.ctx.start + 1; + } + }; +}); + +CodeMirror.defineMIME("text/x-common-lisp", "commonlisp"); diff --git a/mode/commonlisp/index.html b/mode/commonlisp/index.html new file mode 100644 index 0000000000..f9766a844c --- /dev/null +++ b/mode/commonlisp/index.html @@ -0,0 +1,165 @@ + + + + + CodeMirror: Common Lisp mode + + + + + + + +

    CodeMirror: Common Lisp mode

    +
    + + +

    MIME types defined: text/x-common-lisp.

    + + + From db012e8a542aa6fe6fdbb7cf886d73d6c389ff9e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Sep 2012 16:49:46 +0200 Subject: [PATCH 041/135] [commonlisp mode] Fix accidental global --- mode/commonlisp/commonlisp.js | 1 + 1 file changed, 1 insertion(+) diff --git a/mode/commonlisp/commonlisp.js b/mode/commonlisp/commonlisp.js index b95a8686bb..6f2d1d7af5 100644 --- a/mode/commonlisp/commonlisp.js +++ b/mode/commonlisp/commonlisp.js @@ -5,6 +5,7 @@ CodeMirror.defineMode("commonlisp", function (config) { var type; function readSym(stream) { + var ch; while (ch = stream.next()) { if (ch == "\\") stream.next(); else if (!symbol.test(ch)) { stream.backUp(1); break; } From 8556412ecd43b14cb180d1e58ca62e8ba9373199 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Sep 2012 16:59:18 +0200 Subject: [PATCH 042/135] [commonlisp mode] Various small fixes --- mode/commonlisp/commonlisp.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/mode/commonlisp/commonlisp.js b/mode/commonlisp/commonlisp.js index 6f2d1d7af5..7a56b61de5 100644 --- a/mode/commonlisp/commonlisp.js +++ b/mode/commonlisp/commonlisp.js @@ -28,18 +28,20 @@ CodeMirror.defineMode("commonlisp", function (config) { if (stream.skipTo("|")) { stream.next(); return "symbol"; } else { stream.skipToEnd(); return "error"; } } else if (ch == "#") { - if (stream.eat("[")) { type = "open"; return "bracket"; } - else if (stream.eat(/[+\-=\.]/)) return null; - else if (stream.match(/^\d+#/)) return null; - else if (stream.eat("|")) return (state.tokenize = inComment)(stream, state); - else if (stream.eat(":")) { readSym(stream); return "meta"; } - else { stream.next(); return "error"; } + var ch = stream.next(); + if (ch == "[") { type = "open"; return "bracket"; } + else if (/[+\-=\.']/.test(ch)) return null; + else if (/\d/.test(ch) && stream.match(/^\d*#/)) return null; + else if (ch == "|") return (state.tokenize = inComment)(stream, state); + else if (ch == ":") { readSym(stream); return "meta"; } + else return "error"; } else { var name = readSym(stream); if (name == ".") return null; type = "symbol"; if (name == "nil" || name == "t") return "atom"; if (name.charAt(0) == ":") return "keyword"; + if (name.charAt(0) == "&") return "variable-2"; return "variable"; } } From 53b68526229237773ca65153bc071109fde732a5 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Sep 2012 17:05:20 +0200 Subject: [PATCH 043/135] Add node.js-capable runMode implementation --- lib/util/runmode-standalone.js | 105 +++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 lib/util/runmode-standalone.js diff --git a/lib/util/runmode-standalone.js b/lib/util/runmode-standalone.js new file mode 100644 index 0000000000..789836b288 --- /dev/null +++ b/lib/util/runmode-standalone.js @@ -0,0 +1,105 @@ +/* Just enough of CodeMirror to run runMode under node.js */ + +function splitLines(string){ return string.split(/\r?\n|\r/); }; + +// Counts the column offset in a string, taking tabs into account. +// Used mostly to find indentation. +function countColumn(string, end) { + tabSize = 4; + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) end = string.length; + } + for (var i = 0, n = 0; i < end; ++i) { + if (string.charAt(i) == "\t") n += tabSize - (n % tabSize); + else ++n; + } + return n; +} + +function StringStream(string) { + this.pos = this.start = 0; + this.string = string; +} +StringStream.prototype = { + eol: function() {return this.pos >= this.string.length;}, + sol: function() {return this.pos == 0;}, + peek: function() {return this.string.charAt(this.pos) || null;}, + next: function() { + if (this.pos < this.string.length) + return this.string.charAt(this.pos++); + }, + eat: function(match) { + var ch = this.string.charAt(this.pos); + if (typeof match == "string") var ok = ch == match; + else var ok = ch && (match.test ? match.test(ch) : match(ch)); + if (ok) {++this.pos; return ch;} + }, + eatWhile: function(match) { + var start = this.pos; + while (this.eat(match)){} + return this.pos > start; + }, + eatSpace: function() { + var start = this.pos; + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; + return this.pos > start; + }, + skipToEnd: function() {this.pos = this.string.length;}, + skipTo: function(ch) { + var found = this.string.indexOf(ch, this.pos); + if (found > -1) {this.pos = found; return true;} + }, + backUp: function(n) {this.pos -= n;}, + column: function() {return countColumn(this.string, this.start);}, + indentation: function() {return countColumn(this.string);}, + match: function(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} + if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { + if (consume !== false) this.pos += pattern.length; + return true; + } + } + else { + var match = this.string.slice(this.pos).match(pattern); + if (match && consume !== false) this.pos += match[0].length; + return match; + } + }, + current: function(){return this.string.slice(this.start, this.pos);} +}; +exports.StringStream = StringStream; + +exports.startState = function(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true; +}; + +var modes = exports.modes = {}, mimeModes = exports.mimeModes = {}; +exports.defineMode = function(name, mode) { modes[name] = mode; }; +exports.defineMIME = function(mime, spec) { mimeModes[mime] = spec; }; +exports.getMode = function(options, spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) + spec = mimeModes[spec]; + if (typeof spec == "string") + var mname = spec, config = {}; + else if (spec != null) + var mname = spec.name, config = spec; + var mfactory = modes[mname]; + if (!mfactory) throw new Error("Unknown mode: " + spec); + return mfactory(options, config || {}); +}; + +exports.runMode = function(string, modespec, callback) { + var mode = exports.getMode({indentUnit: 2}, modespec); + var lines = splitLines(string), state = exports.startState(mode); + for (var i = 0, e = lines.length; i < e; ++i) { + if (i) callback("\n"); + var stream = new exports.StringStream(lines[i]); + while (!stream.eol()) { + var style = mode.token(stream, state); + callback(stream.current(), style, i, stream.start); + stream.start = stream.pos; + } + } +}; From 4ec75a9ad878b862880069b6db31ce4f9fa6826c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Sep 2012 17:19:53 +0200 Subject: [PATCH 044/135] Further minimize runmode-standalone.js, fix failing build --- lib/util/runmode-standalone.js | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/lib/util/runmode-standalone.js b/lib/util/runmode-standalone.js index 789836b288..afdf044d8d 100644 --- a/lib/util/runmode-standalone.js +++ b/lib/util/runmode-standalone.js @@ -2,21 +2,6 @@ function splitLines(string){ return string.split(/\r?\n|\r/); }; -// Counts the column offset in a string, taking tabs into account. -// Used mostly to find indentation. -function countColumn(string, end) { - tabSize = 4; - if (end == null) { - end = string.search(/[^\s\u00a0]/); - if (end == -1) end = string.length; - } - for (var i = 0, n = 0; i < end; ++i) { - if (string.charAt(i) == "\t") n += tabSize - (n % tabSize); - else ++n; - } - return n; -} - function StringStream(string) { this.pos = this.start = 0; this.string = string; @@ -51,8 +36,8 @@ StringStream.prototype = { if (found > -1) {this.pos = found; return true;} }, backUp: function(n) {this.pos -= n;}, - column: function() {return countColumn(this.string, this.start);}, - indentation: function() {return countColumn(this.string);}, + column: function() {return this.start;}, + indentation: function() {return 0;}, match: function(pattern, consume, caseInsensitive) { if (typeof pattern == "string") { function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} From acb6aa6bc39bb9581bbd578865c550745ebd3ca8 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 11 Sep 2012 13:40:39 +0200 Subject: [PATCH 045/135] Add startStyle and endStyle options for markers Closes #819 --- doc/manual.html | 30 ++++++++++++++++++++---------- lib/codemirror.js | 14 +++++++++----- mode/xml/index.html | 2 +- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index 758b4e5839..b59a03d5af 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -594,16 +594,26 @@

    Programming API

    Can be used to mark a range of text with a specific CSS class name. from and to should be {line, ch} objects. The options - parameter is optional, and can be an object - with inclusiveLeft and inclusiveRight - properties, which determine whether typing at the left or right - of the marker will cause the new text to become part of the - marker (the default is false for both). The method will return an - object with two methods, clear(), which removes the - mark, and find(), which returns a {from, - to} (both document positions), indicating the current - position of the marked range, or undefined if the - marker is no longer in the document.
    + parameter is optional. When given, it should be an object that + may contain the following configuration options: +
    +
    inclusiveLeft
    Determines whether + text inserted on the left of the marker will end up inside + or outside of it.
    +
    inclusiveRight
    Like inclusiveLeft, + but for the right side.
    +
    startStyle
    Can be used to specify + an extra CSS class to be applied to the leftmost span that + is part of the marker.
    +
    endStyle
    Equivalent + to startStyle, but for the rightmost span.
    +
    + The method will return an object with two methods, + clear(), which removes the mark, + and find(), which returns a {from, to} + (both document positions), indicating the current position of + the marked range, or undefined if the marker is no + longer in the document.
    setBookmark(pos) → object
    Inserts a bookmark, a handle that follows the text around it diff --git a/lib/codemirror.js b/lib/codemirror.js index c7bb7cd606..1d6aeff686 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1524,8 +1524,8 @@ window.CodeMirror = (function() { function markText(from, to, className, options) { from = clipPos(from); to = clipPos(to); var marker = new TextMarker("range", className); - if (options && options.inclusiveLeft) marker.inclusiveLeft = true; - if (options && options.inclusiveRight) marker.inclusiveRight = true; + if (options) for (var opt in options) if (options.hasOwnProperty(opt)) + marker[opt] = options[opt]; var curLine = from.line; doc.iter(curLine, to.line + 1, function(line) { var span = {from: curLine == from.line ? from.ch : null, @@ -2596,7 +2596,7 @@ window.CodeMirror = (function() { var m; while (markpos < marked.length && ((m = marked[markpos]).from == pos || m.from == null)) { - if (m.marker.style != null) marks.push(m); + if (m.marker.type == "range") marks.push(m); ++markpos; } nextChange = markpos < marked.length ? marked[markpos].from : Infinity; @@ -2615,8 +2615,12 @@ window.CodeMirror = (function() { if (text) { var end = pos + text.length; var appliedStyle = style; - for (var j = 0; j < marks.length; ++j) - appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].marker.style; + for (var j = 0; j < marks.length; ++j) { + var mark = marks[j]; + appliedStyle = (appliedStyle ? appliedStyle + " " : "") + mark.marker.style; + if (mark.marker.endStyle && mark.to === Math.min(end, upto)) appliedStyle += " " + mark.marker.endStyle; + if (mark.marker.startStyle && mark.from === pos) appliedStyle += " " + mark.marker.startStyle; + } span(pre, end > upto ? text.slice(0, upto - pos) : text, appliedStyle); if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} pos = end; diff --git a/mode/xml/index.html b/mode/xml/index.html index 9628d954c0..49a627de73 100644 --- a/mode/xml/index.html +++ b/mode/xml/index.html @@ -6,7 +6,7 @@ - + From 23c8c3e79ab2ae3e4127150482f94a850a7a8516 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 12 Sep 2012 10:46:46 +0200 Subject: [PATCH 046/135] Add new state introspection mechanism for nested modes And move closetag over to it. This makes the code that gets the XML state out of the mode actually sound and extensible. Issue #820 --- doc/manual.html | 22 +++++++++ lib/codemirror.js | 16 ++++++- lib/util/closetag.js | 79 +++++++++++++++---------------- lib/util/multiplex.js | 6 ++- lib/util/overlay.js | 4 +- mode/htmlembedded/htmlembedded.js | 6 ++- mode/htmlmixed/htmlmixed.js | 13 ++--- mode/php/php.js | 10 ++-- 8 files changed, 98 insertions(+), 58 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index b59a03d5af..b0331ca41f 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -531,6 +531,11 @@

    Programming API

    getOption(option) → value
    Retrieves the current value of the given option for this editor instance.
    +
    getMode() → object
    +
    Gets the 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.
    cursorCoords(start, mode) → object
    Returns an {x, y, yBot} object containing the @@ -1154,6 +1159,14 @@

    Writing CodeMirror Modes

    where mode is the mode that created the given state.

    +

    In a nested mode, it is recommended to add an + extra methods, innerMode which, given a state object, + returns a {state, mode} object with the inner mode + and its state for the current position. These are used by utility + scripts such as the autoformatter and + the tag closer to get context + information.

    +

    To make indentation work properly in a nested parser, it is advisable to give the startState method of modes that are intended to be nested an optional argument that provides the @@ -1172,6 +1185,15 @@

    Writing CodeMirror Modes

    mode, as in the mode option.

    +

    Sometimes, it is useful to add or override mode + object properties from external code. + The CodeMirror.extendMode can be used to add + properties to mode objects produced for a specific mode. Its first + argument is the name of the mode, its second an object that + specifies the properties that should be added. This is mostly + useful to add utilities that can later be looked + up getMode.

    +

    Contents

    diff --git a/lib/codemirror.js b/lib/codemirror.js index 1d6aeff686..2bc83f352a 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -174,6 +174,7 @@ window.CodeMirror = (function() { } }, getOption: function(option) {return options[option];}, + getMode: function() {return mode;}, undo: operation(undo), redo: operation(redo), indentLine: operation(function(n, dir) { @@ -2037,7 +2038,13 @@ window.CodeMirror = (function() { var spec = CodeMirror.resolveMode(spec); var mfactory = modes[spec.name]; if (!mfactory) return CodeMirror.getMode(options, "text/plain"); - return mfactory(options, spec); + var modeObj = mfactory(options, spec); + if (modeExtensions.hasOwnProperty(spec.name)) { + var exts = modeExtensions[spec.name]; + for (var prop in exts) if (exts.hasOwnProperty(prop)) modeObj[prop] = exts[prop]; + } + modeObj.name = spec.name; + return modeObj; }; CodeMirror.listModes = function() { var list = []; @@ -2057,6 +2064,13 @@ window.CodeMirror = (function() { extensions[name] = func; }; + var modeExtensions = CodeMirror.modeExtensions = {}; + CodeMirror.extendMode = function(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); + for (var prop in properties) if (properties.hasOwnProperty(prop)) + exts[prop] = properties[prop]; + }; + var commands = CodeMirror.commands = { selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});}, killLine: function(cm) { diff --git a/lib/util/closetag.js b/lib/util/closetag.js index 5b2a900139..83dc2c7e75 100644 --- a/lib/util/closetag.js +++ b/lib/util/closetag.js @@ -26,16 +26,15 @@ /** Array of tag names where an end tag is forbidden. */ CodeMirror.defaults['closeTagVoid'] = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']; - function resolveMode(mode) { - if (typeof mode == "object") return mode.name; - if (mode.indexOf("/") > -1) return resolveMode(CodeMirror.mimeModes[mode]); - else return mode; + function innerState(cm, state) { + for (var mode = cm.getMode(); mode.innerMode;) { + var info = mode.innerMode(state); + mode = info.mode; + state = info.state; + } + if (mode.name == "xml") return state; } - function innerState(state) { - // htmlmixed uses .htmlState, PHP .html, XML just the top state object - return state.htmlState || state.html || state; - } /** * Call during key processing to close tags. Handles the key event if the tag is closed, otherwise throws CodeMirror.Pass. @@ -50,39 +49,34 @@ throw CodeMirror.Pass; } + /* + * Relevant structure of token: + * + * htmlmixed + * className + * state + * htmlState + * type + * tagName + * context + * tagName + * mode + * + * xml + * className + * state + * tagName + * type + */ - if (/^(xml|php|htmlmixed)$/.test(resolveMode(cm.getOption('mode')))) { - - /* - * Relevant structure of token: - * - * htmlmixed - * className - * state - * htmlState - * type - * tagName - * context - * tagName - * mode - * - * xml - * className - * state - * tagName - * type - */ - - var pos = cm.getCursor(); - var tok = cm.getTokenAt(pos); - var state = tok.state; - - if (state.mode && state.mode != 'html') { - throw CodeMirror.Pass; // With htmlmixed, we only care about the html sub-mode. - } + var pos = cm.getCursor(); + var tok = cm.getTokenAt(pos); + var state = innerState(cm, tok.state); + + if (state) { if (ch == '>') { - var type = innerState(state).type; + var type = state.type; if (tok.className == 'tag' && type == 'closeTag') { throw CodeMirror.Pass; // Don't process the '>' at the end of an end-tag. @@ -93,11 +87,12 @@ cm.setCursor(pos); tok = cm.getTokenAt(cm.getCursor()); - state = tok.state; - var type = innerState(state).type; + state = innerState(cm, tok.state); + if (!state) throw CodeMirror.Pass; + var type = state.type; if (tok.className == 'tag' && type != 'selfcloseTag') { - var tagName = innerState(state).tagName; + var tagName = state.tagName; if (tagName.length > 0 && shouldClose(cm, vd, tagName)) { insertEndTag(cm, indent, pos, tagName); } @@ -110,7 +105,7 @@ } else if (ch == '/') { if (tok.className == 'tag' && tok.string == '<') { - var ctx = innerState(state).context, tagName = ctx ? ctx.tagName : ''; + var ctx = state.context, tagName = ctx ? ctx.tagName : ''; if (tagName.length > 0) { completeEndTag(cm, pos, tagName); return; diff --git a/lib/util/multiplex.js b/lib/util/multiplex.js index b7c1838f62..214730839d 100644 --- a/lib/util/multiplex.js +++ b/lib/util/multiplex.js @@ -68,6 +68,10 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { return mode.indent(state.innerActive ? state.inner : state.outer, textAfter); }, - electricChars: outer.electricChars + electricChars: outer.electricChars, + + innerMode: function(state) { + return state.inner ? {state: state.inner, mode: state.innerActive.mode} : {state: state.outer, mode: outer}; + } }; }; diff --git a/lib/util/overlay.js b/lib/util/overlay.js index 1d5df6c64c..c38d0ca6bb 100644 --- a/lib/util/overlay.js +++ b/lib/util/overlay.js @@ -47,6 +47,8 @@ CodeMirror.overlayMode = CodeMirror.overlayParser = function(base, overlay, comb indent: base.indent && function(state, textAfter) { return base.indent(state.base, textAfter); }, - electricChars: base.electricChars + electricChars: base.electricChars, + + innerMode: function(state) { return {state: state.base, mode: base}; } }; }; diff --git a/mode/htmlembedded/htmlembedded.js b/mode/htmlembedded/htmlembedded.js index a8a7e6e603..195d48e328 100644 --- a/mode/htmlembedded/htmlembedded.js +++ b/mode/htmlembedded/htmlembedded.js @@ -58,8 +58,12 @@ CodeMirror.defineMode("htmlembedded", function(config, parserConfig) { }; }, + electricChars: "/{}:", - electricChars: "/{}:" + innerMode: function(state) { + if (state.token == scriptingDispatch) return {state: state.scriptState, mode: scriptingMode}; + else return {state: state.htmlState, mode: htmlMixedMode}; + } }; }, "htmlmixed"); diff --git a/mode/htmlmixed/htmlmixed.js b/mode/htmlmixed/htmlmixed.js index 5f2fc238c9..4652848296 100644 --- a/mode/htmlmixed/htmlmixed.js +++ b/mode/htmlmixed/htmlmixed.js @@ -1,4 +1,4 @@ -CodeMirror.defineMode("htmlmixed", function(config, parserConfig) { +CodeMirror.defineMode("htmlmixed", function(config) { var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true}); var jsMode = CodeMirror.getMode(config, "javascript"); var cssMode = CodeMirror.getMode(config, "css"); @@ -9,12 +9,10 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) { if (/^script$/i.test(state.htmlState.context.tagName)) { state.token = javascript; state.localState = jsMode.startState(htmlMode.indent(state.htmlState, "")); - state.mode = "javascript"; } else if (/^style$/i.test(state.htmlState.context.tagName)) { state.token = css; state.localState = cssMode.startState(htmlMode.indent(state.htmlState, "")); - state.mode = "css"; } } return style; @@ -33,7 +31,6 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) { if (stream.match(/^<\/\s*script\s*>/i, false)) { state.token = html; state.localState = null; - state.mode = "html"; return html(stream, state); } return maybeBackup(stream, /<\/\s*script\s*>/, @@ -43,7 +40,6 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) { if (stream.match(/^<\/\s*style\s*>/i, false)) { state.token = html; state.localState = null; - state.mode = "html"; return html(stream, state); } return maybeBackup(stream, /<\/\s*style\s*>/, @@ -76,7 +72,12 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) { return cssMode.indent(state.localState, textAfter); }, - electricChars: "/{}:" + electricChars: "/{}:", + + innerMode: function(state) { + var mode = state.token == html ? htmlMode : state.token == javascript ? jsMode : cssMode; + return {state: state.localState || state.htmlState, mode: mode}; + } }; }, "xml", "javascript", "css"); diff --git a/mode/php/php.js b/mode/php/php.js index dbe774fa59..b94317c749 100644 --- a/mode/php/php.js +++ b/mode/php/php.js @@ -56,14 +56,13 @@ var phpMode = CodeMirror.getMode(config, phpConfig); function dispatch(stream, state) { // TODO open PHP inside text/css - var isPHP = state.mode == "php"; + var isPHP = state.curMode == phpMode; if (stream.sol() && state.pending != '"') state.pending = null; if (state.curMode == htmlMode) { if (stream.match(/^<\?\w*/)) { state.curMode = phpMode; state.curState = state.php; state.curClose = "?>"; - state.mode = "php"; return "meta"; } if (state.pending == '"') { @@ -86,13 +85,11 @@ state.curMode = jsMode; state.curState = jsMode.startState(htmlMode.indent(state.curState, "")); state.curClose = /^<\/\s*script\s*>/i; - state.mode = "javascript"; } else if (/^style$/i.test(state.curState.context.tagName)) { state.curMode = cssMode; state.curState = cssMode.startState(htmlMode.indent(state.curState, "")); state.curClose = /^<\/\s*style\s*>/i; - state.mode = "css"; } } return style; @@ -101,7 +98,6 @@ state.curMode = htmlMode; state.curState = state.html; state.curClose = null; - state.mode = "html"; if (isPHP) return "meta"; else return dispatch(stream, state); } else { @@ -141,7 +137,9 @@ return state.curMode.indent(state.curState, textAfter); }, - electricChars: "/{}:" + electricChars: "/{}:", + + innerMode: function(state) { return {state: state.curState, mode: state.curMode}; } }; }, "xml", "clike", "javascript", "css"); CodeMirror.defineMIME("application/x-httpd-php", "php"); From ad7a8353270621ee9e13efdad0be453366deea21 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 12 Sep 2012 11:57:00 +0200 Subject: [PATCH 047/135] [util/formatting] Move over to new mode extension/introspection This makes the formatter easier to adjust to new mode, and cleans it up somewhat. --- doc/manual.html | 13 +- lib/codemirror.js | 8 + lib/util/closetag.js | 7 +- lib/util/formatting.js | 393 +++++++++++++++-------------------------- mode/gfm/gfm.js | 7 +- 5 files changed, 165 insertions(+), 263 deletions(-) diff --git a/doc/manual.html b/doc/manual.html index b0331ca41f..09fb1c74c5 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -959,6 +959,11 @@

    Add-ons

    Depends on the searchcursor add-on. Demo here.
    +
    formatting.js
    +
    Adds commentRange, autoIndentRange, + and autoFormatRange methods that, respectively, + comment (or uncomment), indent, or format (add line breaks) a + range of code. Demo here.
    closetag.js
    Provides utility functions for adding automatic tag closing to XML modes. See @@ -1163,9 +1168,11 @@

    Writing CodeMirror Modes

    extra methods, innerMode which, given a state object, returns a {state, mode} object with the inner mode and its state for the current position. These are used by utility - scripts such as the autoformatter and - the tag closer to get context - information.

    + scripts such as the autoformatter + and the tag closer to get context + information. Use the CodeMirror.innerMode helper + function to, starting from a mode and a state, recursively walk + down to the innermost mode and state.

    To make indentation work properly in a nested parser, it is advisable to give the startState method of modes that diff --git a/lib/codemirror.js b/lib/codemirror.js index 2bc83f352a..688f5d51b0 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2273,6 +2273,14 @@ window.CodeMirror = (function() { return mode.startState ? mode.startState(a1, a2) : true; } CodeMirror.startState = startState; + CodeMirror.innerMode = function(mode, state) { + while (mode.innerMode) { + var info = mode.innerMode(state); + state = info.state; + mode = info.mode; + } + return info || {mode: mode, state: state}; + }; // The character stream used by a mode's parser. function StringStream(string, tabSize) { diff --git a/lib/util/closetag.js b/lib/util/closetag.js index 83dc2c7e75..5096678473 100644 --- a/lib/util/closetag.js +++ b/lib/util/closetag.js @@ -27,12 +27,7 @@ CodeMirror.defaults['closeTagVoid'] = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']; function innerState(cm, state) { - for (var mode = cm.getMode(); mode.innerMode;) { - var info = mode.innerMode(state); - mode = info.mode; - state = info.state; - } - if (mode.name == "xml") return state; + return CodeMirror.innerMode(cm.getMode(), state).state; } diff --git a/lib/util/formatting.js b/lib/util/formatting.js index 22c943fb40..4375dd729a 100644 --- a/lib/util/formatting.js +++ b/lib/util/formatting.js @@ -1,110 +1,22 @@ // ============== Formatting extensions ============================ -// A common storage for all mode-specific formatting features -if (!CodeMirror.modeExtensions) CodeMirror.modeExtensions = {}; - -// Returns the extension of the editor's current mode -CodeMirror.defineExtension("getModeExt", function () { - var mname = CodeMirror.resolveMode(this.getOption("mode")).name; - var ext = CodeMirror.modeExtensions[mname]; - if (!ext) throw new Error("No extensions found for mode " + mname); - return ext; -}); - -// If the current mode is 'htmlmixed', returns the extension of a mode located at -// the specified position (can be htmlmixed, css or javascript). Otherwise, simply -// returns the extension of the editor's current mode. -CodeMirror.defineExtension("getModeExtAtPos", function (pos) { - var token = this.getTokenAt(pos); - if (token && token.state && token.state.mode) - return CodeMirror.modeExtensions[token.state.mode == "html" ? "htmlmixed" : token.state.mode]; - else - return this.getModeExt(); -}); - -// Comment/uncomment the specified range -CodeMirror.defineExtension("commentRange", function (isComment, from, to) { - var curMode = this.getModeExtAtPos(this.getCursor()); - if (isComment) { // Comment range - var commentedText = this.getRange(from, to); - this.replaceRange(curMode.commentStart + this.getRange(from, to) + curMode.commentEnd - , from, to); - if (from.line == to.line && from.ch == to.ch) { // An empty comment inserted - put cursor inside - this.setCursor(from.line, from.ch + curMode.commentStart.length); - } - } - else { // Uncomment range - var selText = this.getRange(from, to); - var startIndex = selText.indexOf(curMode.commentStart); - var endIndex = selText.lastIndexOf(curMode.commentEnd); - if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) { - // Take string till comment start - selText = selText.substr(0, startIndex) - // From comment start till comment end - + selText.substring(startIndex + curMode.commentStart.length, endIndex) - // From comment end till string end - + selText.substr(endIndex + curMode.commentEnd.length); - } - this.replaceRange(selText, from, to); - } -}); - -// Applies automatic mode-aware indentation to the specified range -CodeMirror.defineExtension("autoIndentRange", function (from, to) { - var cmInstance = this; - this.operation(function () { - for (var i = from.line; i <= to.line; i++) { - cmInstance.indentLine(i, "smart"); +(function() { + // Define extensions for a few modes + CodeMirror.extendMode("css", { + commentStart: "/*", + commentEnd: "*/", + wordWrapChars: [";", "\\{", "\\}"], + autoFormatLineBreaks: function (text) { + return text.replace(new RegExp("(;|\\{|\\})([^\r\n])", "g"), "$1\n$2"); } }); -}); - -// Applies automatic formatting to the specified range -CodeMirror.defineExtension("autoFormatRange", function (from, to) { - var absStart = this.indexFromPos(from); - var absEnd = this.indexFromPos(to); - // Insert additional line breaks where necessary according to the - // mode's syntax - var res = this.getModeExt().autoFormatLineBreaks(this.getValue(), absStart, absEnd); - var cmInstance = this; - - // Replace and auto-indent the range - this.operation(function () { - cmInstance.replaceRange(res, from, to); - var startLine = cmInstance.posFromIndex(absStart).line; - var endLine = cmInstance.posFromIndex(absStart + res.length).line; - for (var i = startLine; i <= endLine; i++) { - cmInstance.indentLine(i, "smart"); - } - }); -}); - -// Define extensions for a few modes - -CodeMirror.modeExtensions["css"] = { - commentStart: "/*", - commentEnd: "*/", - wordWrapChars: [";", "\\{", "\\}"], - autoFormatLineBreaks: function (text, startPos, endPos) { - text = text.substring(startPos, endPos); - return text.replace(new RegExp("(;|\\{|\\})([^\r\n])", "g"), "$1\n$2"); - } -}; -CodeMirror.modeExtensions["javascript"] = { - commentStart: "/*", - commentEnd: "*/", - wordWrapChars: [";", "\\{", "\\}"], - - getNonBreakableBlocks: function (text) { - var nonBreakableRegexes = [ - new RegExp("for\\s*?\\(([\\s\\S]*?)\\)"), - new RegExp("\\\\\"([\\s\\S]*?)(\\\\\"|$)"), - new RegExp("\\\\\'([\\s\\S]*?)(\\\\\'|$)"), - new RegExp("'([\\s\\S]*?)('|$)"), - new RegExp("\"([\\s\\S]*?)(\"|$)"), - new RegExp("//.*([\r\n]|$)") - ]; - var nonBreakableBlocks = new Array(); + function jsNonBreakableBlocks(text) { + var nonBreakableRegexes = [/for\s*?\((.*?)\)/, + /\"(.*?)(\"|$)/, + /\'(.*?)(\'|$)/, + /\/\*(.*?)(\*\/|$)/, + /\/\/.*/]; + var nonBreakableBlocks = []; for (var i = 0; i < nonBreakableRegexes.length; i++) { var curPos = 0; while (curPos < text.length) { @@ -126,174 +38,149 @@ CodeMirror.modeExtensions["javascript"] = { }); return nonBreakableBlocks; - }, + } - autoFormatLineBreaks: function (text, startPos, endPos) { - text = text.substring(startPos, endPos); - var curPos = 0; - var reLinesSplitter = new RegExp("(;|\\{|\\})([^\r\n;])", "g"); - var nonBreakableBlocks = this.getNonBreakableBlocks(text); - if (nonBreakableBlocks != null) { - var res = ""; - for (var i = 0; i < nonBreakableBlocks.length; i++) { - if (nonBreakableBlocks[i].start > curPos) { // Break lines till the block - res += text.substring(curPos, nonBreakableBlocks[i].start).replace(reLinesSplitter, "$1\n$2"); - curPos = nonBreakableBlocks[i].start; - } - if (nonBreakableBlocks[i].start <= curPos - && nonBreakableBlocks[i].end >= curPos) { // Skip non-breakable block - res += text.substring(curPos, nonBreakableBlocks[i].end); - curPos = nonBreakableBlocks[i].end; + CodeMirror.extendMode("javascript", { + commentStart: "/*", + commentEnd: "*/", + wordWrapChars: [";", "\\{", "\\}"], + + autoFormatLineBreaks: function (text) { + var curPos = 0; + var reLinesSplitter = /(;|\{|\})([^\r\n;])/g; + var nonBreakableBlocks = jsNonBreakableBlocks(text); + if (nonBreakableBlocks != null) { + var res = ""; + for (var i = 0; i < nonBreakableBlocks.length; i++) { + if (nonBreakableBlocks[i].start > curPos) { // Break lines till the block + res += text.substring(curPos, nonBreakableBlocks[i].start).replace(reLinesSplitter, "$1\n$2"); + curPos = nonBreakableBlocks[i].start; + } + if (nonBreakableBlocks[i].start <= curPos + && nonBreakableBlocks[i].end >= curPos) { // Skip non-breakable block + res += text.substring(curPos, nonBreakableBlocks[i].end); + curPos = nonBreakableBlocks[i].end; + } } + if (curPos < text.length) + res += text.substr(curPos).replace(reLinesSplitter, "$1\n$2"); + return res; + } else { + return text.replace(reLinesSplitter, "$1\n$2"); } - if (curPos < text.length - 1) { - res += text.substr(curPos).replace(reLinesSplitter, "$1\n$2"); - } - return res; } - else { - return text.replace(reLinesSplitter, "$1\n$2"); - } - } -}; - -CodeMirror.modeExtensions["xml"] = { - commentStart: "", - wordWrapChars: [">"], + }); - autoFormatLineBreaks: function (text, startPos, endPos) { - text = text.substring(startPos, endPos); - var lines = text.split("\n"); - var reProcessedPortion = new RegExp("(^\\s*?<|^[^<]*?)(.+)(>\\s*?$|[^>]*?$)"); - var reOpenBrackets = new RegExp("<", "g"); - var reCloseBrackets = new RegExp("(>)([^\r\n])", "g"); - for (var i = 0; i < lines.length; i++) { - var mToProcess = lines[i].match(reProcessedPortion); - if (mToProcess != null && mToProcess.length > 3) { // The line starts with whitespaces and ends with whitespaces - lines[i] = mToProcess[1] + CodeMirror.extendMode("xml", { + commentStart: "", + wordWrapChars: [">"], + + autoFormatLineBreaks: function (text) { + var lines = text.split("\n"); + var reProcessedPortion = new RegExp("(^\\s*?<|^[^<]*?)(.+)(>\\s*?$|[^>]*?$)"); + var reOpenBrackets = new RegExp("<", "g"); + var reCloseBrackets = new RegExp("(>)([^\r\n])", "g"); + for (var i = 0; i < lines.length; i++) { + var mToProcess = lines[i].match(reProcessedPortion); + if (mToProcess != null && mToProcess.length > 3) { // The line starts with whitespaces and ends with whitespaces + lines[i] = mToProcess[1] + mToProcess[2].replace(reOpenBrackets, "\n$&").replace(reCloseBrackets, "$1\n$2") + mToProcess[3]; - continue; + continue; + } } + return lines.join("\n"); } + }); - return lines.join("\n"); + function localModeAt(cm, pos) { + return CodeMirror.innerMode(cm.getMode(), cm.getTokenAt(pos).state).mode; } -}; - -CodeMirror.modeExtensions["htmlmixed"] = { - commentStart: "", - wordWrapChars: [">", ";", "\\{", "\\}"], - getModeInfos: function (text, absPos) { - var modeInfos = new Array(); - modeInfos[0] = - { - pos: 0, - modeExt: CodeMirror.modeExtensions["xml"], - modeName: "xml" - }; - - var modeMatchers = new Array(); - modeMatchers[0] = - { - regex: new RegExp("]*>([\\s\\S]*?)(]*>|$)", "i"), - modeExt: CodeMirror.modeExtensions["css"], - modeName: "css" - }; - modeMatchers[1] = - { - regex: new RegExp("]*>([\\s\\S]*?)(]*>|$)", "i"), - modeExt: CodeMirror.modeExtensions["javascript"], - modeName: "javascript" - }; + function enumerateModesBetween(cm, line, start, end) { + var outer = cm.getMode(); + if (!outer.innerMode) return [{from: start, to: end, mode: outer}]; + var init = CodeMirror.innerMode(outer, cm.getTokenAt({line: line, ch: start}).state); + var state = init.state, mode = init.mode; + var found = [], stream = new CodeMirror.StringStream(cm.getLine(line)); + stream.pos = stream.start = start; + for (;;) { + outer.token(stream, state); + var cur = CodeMirror.innerMode(outer, state).mode; + if (curMode != mode) { + found.push({from: start, to: stream.pos, mode: mode}); + start = stream.pos; + mode = curMode; + } + if (stream.pos >= end) break; + stream.start = stream.pos; + } + if (start < end) found.push({from: start, to: end, mode: mode}); + return found; + } - var lastCharPos = (typeof (absPos) !== "undefined" ? absPos : text.length - 1); - // Detect modes for the entire text - for (var i = 0; i < modeMatchers.length; i++) { - var curPos = 0; - while (curPos <= lastCharPos) { - var m = text.substr(curPos).match(modeMatchers[i].regex); - if (m != null) { - if (m.length > 1 && m[1].length > 0) { - // Push block begin pos - var blockBegin = curPos + m.index + m[0].indexOf(m[1]); - modeInfos.push( - { - pos: blockBegin, - modeExt: modeMatchers[i].modeExt, - modeName: modeMatchers[i].modeName - }); - // Push block end pos - modeInfos.push( - { - pos: blockBegin + m[1].length, - modeExt: modeInfos[0].modeExt, - modeName: modeInfos[0].modeName - }); - curPos += m.index + m[0].length; - continue; - } - else { - curPos += m.index + Math.max(m[0].length, 1); - } - } - else { // No more matches - break; + // Comment/uncomment the specified range + CodeMirror.defineExtension("commentRange", function (isComment, from, to) { + var curMode = localModeAt(this, from); + this.operation(function() { + if (isComment) { // Comment range + this.replaceRange(curMode.commentEnd, to); + this.replaceRange(curMode.commentStart, from); + if (from.line == to.line && from.ch == to.ch) // An empty comment inserted - put cursor inside + this.setCursor(from.line, from.ch + curMode.commentStart.length); + } else { // Uncomment range + var selText = this.getRange(from, to); + var startIndex = selText.indexOf(curMode.commentStart); + var endIndex = selText.lastIndexOf(curMode.commentEnd); + if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) { + // Take string till comment start + selText = selText.substr(0, startIndex) + // From comment start till comment end + + selText.substring(startIndex + curMode.commentStart.length, endIndex) + // From comment end till string end + + selText.substr(endIndex + curMode.commentEnd.length); } + this.replaceRange(selText, from, to); } - } - // Sort mode infos - modeInfos.sort(function sortModeInfo(a, b) { - return a.pos - b.pos; }); + }); - return modeInfos; - }, - - autoFormatLineBreaks: function (text, startPos, endPos) { - var modeInfos = this.getModeInfos(text); - var reBlockStartsWithNewline = new RegExp("^\\s*?\n"); - var reBlockEndsWithNewline = new RegExp("\n\\s*?$"); - var res = ""; - // Use modes info to break lines correspondingly - if (modeInfos.length > 1) { // Deal with multi-mode text - for (var i = 1; i <= modeInfos.length; i++) { - var selStart = modeInfos[i - 1].pos; - var selEnd = (i < modeInfos.length ? modeInfos[i].pos : endPos); + // Applies automatic mode-aware indentation to the specified range + CodeMirror.defineExtension("autoIndentRange", function (from, to) { + var cmInstance = this; + this.operation(function () { + for (var i = from.line; i <= to.line; i++) { + cmInstance.indentLine(i, "smart"); + } + }); + }); - if (selStart >= endPos) { // The block starts later than the needed fragment - break; + // Applies automatic formatting to the specified range + CodeMirror.defineExtension("autoFormatRange", function (from, to) { + var cm = this; + cm.operation(function () { + for (var cur = from.line, end = to.line; cur <= end; ++cur) { + var f = {line: cur, ch: cur == from.line ? from.ch : 0}; + var t = {line: cur, ch: cur == end ? to.ch : null}; + var modes = enumerateModesBetween(cm, cur, f.ch, t.ch), mangled = ""; + var text = cm.getRange(f, t); + for (var i = 0; i < modes.length; ++i) { + var part = modes.length > 1 ? text.slice(modes[i].from, modes[i].to) : text; + if (i) mangled += "\n"; + if (modes[i].mode.autoFormatLineBreaks) { + mangled += modes[i].mode.autoFormatLineBreaks(part); + } else mangled += text; } - if (selStart < startPos) { - if (selEnd <= startPos) { // The block starts earlier than the needed fragment - continue; - } - selStart = startPos; - } - if (selEnd > endPos) { - selEnd = endPos; - } - var textPortion = text.substring(selStart, selEnd); - if (modeInfos[i - 1].modeName != "xml") { // Starting a CSS or JavaScript block - if (!reBlockStartsWithNewline.test(textPortion) - && selStart > 0) { // The block does not start with a line break - textPortion = "\n" + textPortion; - } - if (!reBlockEndsWithNewline.test(textPortion) - && selEnd < text.length - 1) { // The block does not end with a line break - textPortion += "\n"; - } + if (mangled != text) { + for (var count = 0, pos = mangled.indexOf("\n"); pos != -1; pos = mangled.indexOf("\n", pos + 1), ++count) {} + cm.replaceRange(mangled, f, t); + cur += count; + end += count; } - res += modeInfos[i - 1].modeExt.autoFormatLineBreaks(textPortion); } - } - else { // Single-mode text - res = modeInfos[0].modeExt.autoFormatLineBreaks(text.substring(startPos, endPos)); - } - - return res; - } -}; + for (var cur = from.line + 1; cur <= end; ++cur) + cm.indentLine(cur, "smart"); + }); + }); +})(); diff --git a/mode/gfm/gfm.js b/mode/gfm/gfm.js index b83fbc683a..2bc273ab68 100644 --- a/mode/gfm/gfm.js +++ b/mode/gfm/gfm.js @@ -96,7 +96,7 @@ CodeMirror.defineMode("gfm", function(config, parserConfig) { }, copyState: function(state) { - return {token: state.token, mode: state.mode, mdState: CodeMirror.copyState(mdMode, state.mdState), + return {token: state.token, mdState: CodeMirror.copyState(mdMode, state.mdState), localMode: state.localMode, localState: state.localMode ? CodeMirror.copyState(state.localMode, state.localState) : null}; }, @@ -140,6 +140,11 @@ CodeMirror.defineMode("gfm", function(config, parserConfig) { } return state.token(stream, state); + }, + + innerMode: function(state) { + if (state.token == markdown) return {state: state.mdState, mode: mdMode}; + else return {state: state.localState, mode: state.localMode}; } }; }, "markdown"); From 03fb1d99a9d79f15bbf6cbeab8485987cc31fcd1 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 12 Sep 2012 12:32:00 +0200 Subject: [PATCH 048/135] [util/formatting] Fix a bunch of bugs That's what you get when you test with the published version, rather than the one you're actually editing. --- lib/util/formatting.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/util/formatting.js b/lib/util/formatting.js index 4375dd729a..ccd6db1b21 100644 --- a/lib/util/formatting.js +++ b/lib/util/formatting.js @@ -99,18 +99,24 @@ } function enumerateModesBetween(cm, line, start, end) { - var outer = cm.getMode(); + var outer = cm.getMode(), text = cm.getLine(line); + if (end == null) end = text.length; if (!outer.innerMode) return [{from: start, to: end, mode: outer}]; - var init = CodeMirror.innerMode(outer, cm.getTokenAt({line: line, ch: start}).state); - var state = init.state, mode = init.mode; - var found = [], stream = new CodeMirror.StringStream(cm.getLine(line)); + var state = cm.getTokenAt({line: line, ch: start}).state; + var mode = CodeMirror.innerMode(outer, state).mode; + var found = [], stream = new CodeMirror.StringStream(text); stream.pos = stream.start = start; for (;;) { outer.token(stream, state); - var cur = CodeMirror.innerMode(outer, state).mode; + var curMode = CodeMirror.innerMode(outer, state).mode; if (curMode != mode) { - found.push({from: start, to: stream.pos, mode: mode}); - start = stream.pos; + var cut = stream.start; + // Crappy heuristic to deal with the fact that a change in + // mode can occur both at the end and the start of a token, + // and we don't know which it was. + if (mode.name == "xml" && text.charAt(stream.pos - 1) == ">") cut = stream.pos; + found.push({from: start, to: cut, mode: mode}); + start = cut; mode = curMode; } if (stream.pos >= end) break; @@ -167,7 +173,7 @@ var text = cm.getRange(f, t); for (var i = 0; i < modes.length; ++i) { var part = modes.length > 1 ? text.slice(modes[i].from, modes[i].to) : text; - if (i) mangled += "\n"; + if (mangled) mangled += "\n"; if (modes[i].mode.autoFormatLineBreaks) { mangled += modes[i].mode.autoFormatLineBreaks(part); } else mangled += text; @@ -181,6 +187,7 @@ } for (var cur = from.line + 1; cur <= end; ++cur) cm.indentLine(cur, "smart"); + cm.setSelection(from, cm.getCursor(false)); }); }); })(); From 95b5962bd335ed0d489751a2565aa469adbf60fb Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 12 Sep 2012 20:22:15 +0200 Subject: [PATCH 049/135] Add NoTex to real-world uses --- index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/index.html b/index.html index f7ae5ea393..c895bd47df 100644 --- a/index.html +++ b/index.html @@ -145,6 +145,7 @@

    Real-world uses:

  • CSSDeck (CSS showcase)
  • CKWNC (UML editor)
  • sketchPatch Livecodelab
  • +
  • NoTex (rST authoring)
  • From 92367194be9fdc2e3aa5ff3fc539e4fa8700cd93 Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Sun, 16 Sep 2012 16:51:05 -0400 Subject: [PATCH 050/135] Fix formatting demo's "[Un]Comment Selected" functionality (closes #826). --- lib/util/formatting.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/util/formatting.js b/lib/util/formatting.js index ccd6db1b21..2c502b25c6 100644 --- a/lib/util/formatting.js +++ b/lib/util/formatting.js @@ -128,15 +128,15 @@ // Comment/uncomment the specified range CodeMirror.defineExtension("commentRange", function (isComment, from, to) { - var curMode = localModeAt(this, from); + var curMode = localModeAt(this, from), cm = this; this.operation(function() { if (isComment) { // Comment range - this.replaceRange(curMode.commentEnd, to); - this.replaceRange(curMode.commentStart, from); + cm.replaceRange(curMode.commentEnd, to); + cm.replaceRange(curMode.commentStart, from); if (from.line == to.line && from.ch == to.ch) // An empty comment inserted - put cursor inside - this.setCursor(from.line, from.ch + curMode.commentStart.length); + cm.setCursor(from.line, from.ch + curMode.commentStart.length); } else { // Uncomment range - var selText = this.getRange(from, to); + var selText = cm.getRange(from, to); var startIndex = selText.indexOf(curMode.commentStart); var endIndex = selText.lastIndexOf(curMode.commentEnd); if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) { @@ -147,7 +147,7 @@ // From comment end till string end + selText.substr(endIndex + curMode.commentEnd.length); } - this.replaceRange(selText, from, to); + cm.replaceRange(selText, from, to); } }); }); From 275d6edfa82014a8bd79b167a507fe5f399ab485 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 17 Sep 2012 11:16:45 +0200 Subject: [PATCH 051/135] [javascript mode] Fix variable-scope-restoration --- 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 6ece1befc9..e754a047f0 100644 --- a/mode/javascript/javascript.js +++ b/mode/javascript/javascript.js @@ -175,8 +175,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { var defaultVars = {name: "this", next: {name: "arguments"}}; function pushcontext() { - if (!cx.state.context) cx.state.localVars = defaultVars; cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; + cx.state.localVars = defaultVars; } function popcontext() { cx.state.localVars = cx.state.context.vars; From 52cfc2e79b6a7530f6297ea3510100d03a5b2ed9 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 17 Sep 2012 15:52:56 +0200 Subject: [PATCH 052/135] [util/simple-hint] Capture pageup/pagedown keys --- lib/util/simple-hint.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util/simple-hint.js b/lib/util/simple-hint.js index 8e481c37c2..04909cb236 100644 --- a/lib/util/simple-hint.js +++ b/lib/util/simple-hint.js @@ -71,7 +71,7 @@ if (code == 13) {CodeMirror.e_stop(event); pick();} // Escape else if (code == 27) {CodeMirror.e_stop(event); close(); editor.focus();} - else if (code != 38 && code != 40) { + else if (code != 38 && code != 40 && code != 33 && code != 34) { close(); editor.focus(); // Pass the event to the CodeMirror instance so that it can handle things like backspace properly. editor.triggerOnKeyDown(event); From af7755984d836d6a0073a35c7c87bb96b2f447ad Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 18 Sep 2012 09:55:22 +0200 Subject: [PATCH 053/135] [vb mode] Highlight Try/Catch keywords --- mode/vb/vb.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mode/vb/vb.js b/mode/vb/vb.js index 01f9890389..be01d13ad9 100644 --- a/mode/vb/vb.js +++ b/mode/vb/vb.js @@ -12,8 +12,8 @@ CodeMirror.defineMode("vb", function(conf, parserConf) { var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))"); var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*"); - var openingKeywords = ['class','module', 'sub','enum','select','while','if','function', 'get','set','property']; - var middleKeywords = ['else','elseif','case']; + var openingKeywords = ['class','module', 'sub','enum','select','while','if','function', 'get','set','property', 'try']; + var middleKeywords = ['else','elseif','case', 'catch']; var endKeywords = ['next','loop']; var wordOperators = wordRegexp(['and', 'or', 'not', 'xor', 'in']); From 949bcc48445ff3deda0cb47a4280f2b76b986e29 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 18 Sep 2012 14:35:41 +0200 Subject: [PATCH 054/135] Make output of getHistory JSON-serializable again --- lib/codemirror.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 688f5d51b0..8ed6049bb7 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -193,8 +193,18 @@ window.CodeMirror = (function() { history.undone = histData.undone; }, getHistory: function() { - history.time = 0; - return {done: history.done.concat([]), undone: history.undone.concat([])}; + function cp(arr) { + for (var i = 0, nw = [], nwelt; i < arr.length; ++i) { + nw.push(nwelt = []); + for (var j = 0, elt = arr[i]; j < elt.length; ++j) { + var old = [], cur = elt[j]; + nwelt.push({start: cur.start, added: cur.added, old: old}); + for (var k = 0; k < cur.old.length; ++k) old.push(hlText(cur.old[k])); + } + } + return nw; + } + return {done: cp(history.done), undone: cp(history.undone)}; }, matchBrackets: operation(function(){matchBrackets(true);}), getTokenAt: operation(function(pos) { From 54095aac018b76c8cc29a234fcc2f79ce8cab622 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 18 Sep 2012 23:43:24 +0200 Subject: [PATCH 055/135] Flip ctrlKey and metaKey properties on Opera Mac I don't know what they were thinking, but this, on recent Opera versions, seems to give the correct result. Closes #10 --- lib/codemirror.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 8ed6049bb7..6e5d6a2ed5 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -612,10 +612,11 @@ window.CodeMirror = (function() { }, 50); var name = keyNames[e_prop(e, "keyCode")], handled = false; + var flipCtrlCmd = opera && mac; if (name == null || e.altGraphKey) return false; if (e_prop(e, "altKey")) name = "Alt-" + name; - if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name; - if (e_prop(e, "metaKey")) name = "Cmd-" + name; + if (e_prop(e, flipCtrlCmd ? "metaKey" : "ctrlKey")) name = "Ctrl-" + name; + if (e_prop(e, flipCtrlCmd ? "ctrlKey" : "metaKey")) name = "Cmd-" + name; var stopped = false; function stop() { stopped = true; } From da3a289048b06a95dbf846ad0204f39a21a083be Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 19 Sep 2012 12:15:36 +0200 Subject: [PATCH 056/135] [commonlisp mode] Fix bug in context management --- mode/commonlisp/commonlisp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/commonlisp/commonlisp.js b/mode/commonlisp/commonlisp.js index 7a56b61de5..4fb4bdf9bd 100644 --- a/mode/commonlisp/commonlisp.js +++ b/mode/commonlisp/commonlisp.js @@ -87,7 +87,7 @@ CodeMirror.defineMode("commonlisp", function (config) { } } if (type == "open") state.ctx = {prev: state.ctx, start: stream.column(), indentTo: null}; - else if (type == "close") state.ctx = state.ctx.prev; + else if (type == "close") state.ctx = state.ctx.prev || state.ctx; return style; }, From fc17d2d418d50fba292bae4fdcdb8a5bf1102867 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 19 Sep 2012 13:12:24 +0200 Subject: [PATCH 057/135] Mark release 2.34 --- doc/compress.html | 2 ++ doc/oldrelease.html | 23 ++++++++++++++++++++ index.html | 53 +++++++++++++++++++++++++-------------------- lib/codemirror.js | 2 +- package.json | 2 +- 5 files changed, 56 insertions(+), 26 deletions(-) diff --git a/doc/compress.html b/doc/compress.html index 3b99deae27..a7e23e21c5 100644 --- a/doc/compress.html +++ b/doc/compress.html @@ -30,6 +30,8 @@

    { } CodeMi

    Version:

    Version:

    diff --git a/mode/gfm/test.js b/mode/gfm/test.js new file mode 100644 index 0000000000..3a261f8f77 --- /dev/null +++ b/mode/gfm/test.js @@ -0,0 +1,225 @@ +// Initiate ModeTest and set defaults +var MT = ModeTest; +MT.modeName = 'gfm'; +MT.modeOptions = {}; + +// Emphasis characters within a word +MT.testMode( + 'emInWordAsterisk', + 'foo*bar*hello', + [ + null, 'foo', + 'em', '*bar*', + null, 'hello' + ] +); +MT.testMode( + 'emInWordUnderscore', + 'foo_bar_hello', + [ + null, 'foo_bar_hello' + ] +); +MT.testMode( + 'emStrongUnderscore', + '___foo___ bar', + [ + 'strong', '__', + 'emstrong', '_foo__', + 'em', '_', + null, ' bar' + ] +); + +// Fenced code blocks +MT.testMode( + 'fencedCodeBlocks', + '```\nfoo\n\n```\nbar', + [ + 'comment', '```', + 'comment', 'foo', + 'comment', '```', + null, 'bar' + ] +); +// Fenced code block mode switching +MT.testMode( + 'fencedCodeBlockModeSwitching', + '```javascript\nfoo\n\n```\nbar', + [ + 'comment', '```javascript', + 'variable', 'foo', + 'comment', '```', + null, 'bar' + ] +); + +// SHA +MT.testMode( + 'SHA', + 'foo be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2 bar', + [ + null, 'foo ', + 'link', 'be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2', + null, ' bar' + ] +); +// GitHub highlights hashes 7-40 chars in length +MT.testMode( + 'shortSHA', + 'foo be6a8cc bar', + [ + null, 'foo ', + 'link', 'be6a8cc', + null, ' bar' + ] +); +// Invalid SHAs +// +// GitHub does not highlight hashes shorter than 7 chars +MT.testMode( + 'tooShortSHA', + 'foo be6a8c bar', + [ + null, 'foo be6a8c bar' + ] +); +// GitHub does not highlight hashes longer than 40 chars +MT.testMode( + 'longSHA', + 'foo be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd22 bar', + [ + null, 'foo be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd22 bar' + ] +); +MT.testMode( + 'badSHA', + 'foo be6a8cc1c1ecfe9489fb51e4869af15a13fc2cg2 bar', + [ + null, 'foo be6a8cc1c1ecfe9489fb51e4869af15a13fc2cg2 bar' + ] +); +// User@SHA +MT.testMode( + 'userSHA', + 'foo bar@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2 hello', + [ + null, 'foo ', + 'link', 'bar@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2', + null, ' hello' + ] +); +// User/Project@SHA +MT.testMode( + 'userProjectSHA', + 'foo bar/hello@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2 world', + [ + null, 'foo ', + 'link', 'bar/hello@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2', + null, ' world' + ] +); + +// #Num +MT.testMode( + 'num', + 'foo #1 bar', + [ + null, 'foo ', + 'link', '#1', + null, ' bar' + ] +); +// bad #Num +MT.testMode( + 'badNum', + 'foo #1bar hello', + [ + null, 'foo #1bar hello' + ] +); +// User#Num +MT.testMode( + 'userNum', + 'foo bar#1 hello', + [ + null, 'foo ', + 'link', 'bar#1', + null, ' hello' + ] +); +// User/Project#Num +MT.testMode( + 'userProjectNum', + 'foo bar/hello#1 world', + [ + null, 'foo ', + 'link', 'bar/hello#1', + null, ' world' + ] +); + +// Vanilla links +MT.testMode( + 'vanillaLink', + 'foo http://www.example.com/ bar', + [ + null, 'foo ', + 'link', 'http://www.example.com/', + null, ' bar' + ] +); +MT.testMode( + 'vanillaLinkPunctuation', + 'foo http://www.example.com/. bar', + [ + null, 'foo ', + 'link', 'http://www.example.com/', + null, '. bar' + ] +); +MT.testMode( + 'vanillaLinkExtension', + 'foo http://www.example.com/index.html bar', + [ + null, 'foo ', + 'link', 'http://www.example.com/index.html', + null, ' bar' + ] +); +// Not a link +MT.testMode( + 'notALink', + '```css\nfoo {color:black;}\n```http://www.example.com/', + [ + 'comment', '```css', + 'tag', 'foo', + null, ' {', + 'property', 'color', + 'operator', ':', + 'keyword', 'black', + null, ';}', + 'comment', '```', + 'link', 'http://www.example.com/' + ] +); +// Not a link +MT.testMode( + 'notALink', + '``foo `bar` http://www.example.com/`` hello', + [ + 'comment', '``foo `bar` http://www.example.com/``', + null, ' hello' + ] +); +// Not a link +MT.testMode( + 'notALink', + '`foo\nhttp://www.example.com/\n`foo\n\nhttp://www.example.com/', + [ + 'comment', '`foo', + 'link', 'http://www.example.com/', + 'comment', '`foo', + 'link', 'http://www.example.com/' + ] +); \ No newline at end of file diff --git a/mode/markdown/markdown.js b/mode/markdown/markdown.js index 2375c4f94e..33c4a34499 100644 --- a/mode/markdown/markdown.js +++ b/mode/markdown/markdown.js @@ -2,6 +2,46 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { var htmlFound = CodeMirror.mimeModes.hasOwnProperty("text/html"); var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? "text/html" : "text/plain"); + var aliases = { + html: "htmlmixed", + js: "javascript", + json: "application/json", + c: "text/x-csrc", + "c++": "text/x-c++src", + java: "text/x-java", + csharp: "text/x-csharp", + "c#": "text/x-csharp" + }; + + var getMode = (function () { + var i, modes = {}, mimes = {}, mime; + + var list = CodeMirror.listModes(); + for (i = 0; i < list.length; i++) { + modes[list[i]] = list[i]; + } + var mimesList = CodeMirror.listMIMEs(); + for (i = 0; i < mimesList.length; i++) { + mime = mimesList[i].mime; + mimes[mime] = mimesList[i].mime; + } + + for (var a in aliases) { + if (aliases[a] in modes || aliases[a] in mimes) + modes[a] = aliases[a]; + } + + return function (lang) { + return modes[lang] ? CodeMirror.getMode(cmCfg, modes[lang]) : null; + }; + }()); + + // Should underscores in words open/close em/strong? + if (modeCfg.underscoresBreakWords === undefined) + modeCfg.underscoresBreakWords = true; + + // Turn on fenced code blocks? ("```" to start/end) + if (modeCfg.fencedCodeBlocks === undefined) modeCfg.fencedCodeBlocks = false; var codeDepth = 0; var prevLineHasContent = false @@ -43,8 +83,6 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { function blankLine(state) { // Reset linkTitle state state.linkTitle = false; - // Reset CODE state - state.code = false; // Reset EM state state.em = false; // Reset STRONG state @@ -59,7 +97,6 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { } function blockNormal(stream, state) { - var match; if (state.list !== false && state.indentationDiff >= 0) { // Continued list if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block @@ -85,9 +122,15 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { return switchInline(stream, state, footnoteLink); } else if (stream.match(hrRE, true)) { return hr; - } else if (match = stream.match(ulRE, true) || stream.match(olRE, true)) { + } else if (stream.match(ulRE, true) || stream.match(olRE, true)) { state.indentation += 4; state.list = true; + } else if (modeCfg.fencedCodeBlocks && stream.match(/^```([\w+#]*)/, true)) { + // try switching mode + state.localMode = getMode(RegExp.$1); + if (state.localMode) state.localState = state.localMode.startState(); + switchBlock(stream, state, local); + return code; } return switchInline(stream, state, state.inline); @@ -107,6 +150,30 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { return style; } + function local(stream, state) { + if (stream.sol() && stream.match(/^```/, true)) { + state.localMode = state.localState = null; + state.f = inlineNormal; + state.block = blockNormal; + return code; + } else if (state.localMode) { + return state.localMode.token(stream, state.localState); + } else { + stream.skipToEnd(); + return code; + } + } + + function codeBlock(stream, state) { + if(stream.match(codeBlockRE, true)){ + state.f = inlineNormal; + state.block = blockNormal; + switchInline(stream, state, state.inline); + return code; + } + stream.skipToEnd(); + return code; + } // Inline function getType(state) { @@ -164,6 +231,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { } } + // If this block is changed, it may need to be updated in GFM mode if (ch === '`') { var t = getType(state); var before = stream.pos; @@ -227,8 +295,20 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { return "tag"; } + var ignoreUnderscore = false; + if (!modeCfg.underscoresBreakWords) { + if (ch === '_' && stream.peek() !== '_' && stream.match(/(\w)/, false)) { + var prevPos = stream.pos - 2; + if (prevPos >= 0) { + var prevCh = stream.string.charAt(prevPos); + if (prevCh !== '_' && prevCh.match(/(\w)/, false)) { + ignoreUnderscore = true; + } + } + } + } var t = getType(state); - if (ch === '*' || ch === '_') { + if (ch === '*' || (ch === '_' && !ignoreUnderscore)) { if (state.strong === ch && stream.eat(ch)) { // Remove STRONG state.strong = false; return t; @@ -344,6 +424,9 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { block: s.block, htmlState: CodeMirror.copyState(htmlMode, s.htmlState), indentation: s.indentation, + + localMode: s.localMode, + localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null, inline: s.inline, text: s.text, @@ -372,6 +455,9 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { // Reset state.header state.header = false; + + // Reset state.code + state.code = false; state.f = state.block; var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, ' ').length; diff --git a/mode/markdown/test.js b/mode/markdown/test.js index e7fbc32fbb..e0269dcc40 100644 --- a/mode/markdown/test.js +++ b/mode/markdown/test.js @@ -41,6 +41,17 @@ MT.testMode( ] ); +// Block code using single backtick (shouldn't work) +MT.testMode( + 'blockCodeSingleBacktick', + '`\nfoo\n`', + [ + 'comment', '`', + null, 'foo', + 'comment', '`' + ] +); + // Unclosed backticks // Instead of simply marking as CODE, it would be nice to have an // incomplete flag for CODE, that is styled slightly different. @@ -1204,4 +1215,4 @@ MT.testMode( [ null, '\\\\# foo' ] -); \ No newline at end of file +); diff --git a/test/index.html b/test/index.html index 9b10b62118..043cadea31 100644 --- a/test/index.html +++ b/test/index.html @@ -7,6 +7,7 @@ + @@ -44,6 +45,8 @@

    CodeMirror: Test Suite

    + + +

    Optionally depends on other modes for properly highlighted code blocks.

    + +

    Parsing/Highlighting Tests: normal, verbose.

    + From 422a582d46e2aff4da7bf6d5bf979fd36de54893 Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Sun, 16 Sep 2012 20:59:14 -0400 Subject: [PATCH 077/135] [markdown mode] Fix highlighting for code blocks with internal indentation. --- mode/markdown/markdown.js | 3 +++ mode/markdown/test.js | 48 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/mode/markdown/markdown.js b/mode/markdown/markdown.js index 33c4a34499..d227fc9b91 100644 --- a/mode/markdown/markdown.js +++ b/mode/markdown/markdown.js @@ -461,6 +461,9 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { state.f = state.block; var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, ' ').length; + var difference = Math.floor((indentation - state.indentation) / 4) * 4; + if (difference > 4) difference = 4; + indentation = state.indentation + difference; state.indentationDiff = indentation - state.indentation; state.indentation = indentation; if (indentation > 0) { return null; } diff --git a/mode/markdown/test.js b/mode/markdown/test.js index e0269dcc40..7572db510b 100644 --- a/mode/markdown/test.js +++ b/mode/markdown/test.js @@ -20,6 +20,36 @@ MT.testMode( 'comment', 'foo' ] ); +// Code blocks using 4 spaces with internal indentation +MT.testMode( + 'codeBlocksUsing4SpacesIndentation', + ' bar\n hello\n world\n foo\nbar', + [ + null, ' ', + 'comment', 'bar', + null, ' ', + 'comment', 'hello', + null, ' ', + 'comment', 'world', + null, ' ', + 'comment', 'foo', + null, 'bar' + ] +); +// Code blocks using 4 spaces with internal indentation +MT.testMode( + 'codeBlocksUsing4SpacesIndentation', + ' foo\n bar\n hello\n world', + [ + null, ' foo', + null, ' ', + 'comment', 'bar', + null, ' ', + 'comment', 'hello', + null, ' ', + 'comment', 'world' + ] +); // Code blocks using 1 tab (regardless of CodeMirror.indentWithTabs value) MT.testMode( @@ -595,6 +625,24 @@ MT.testMode( 'comment', 'hello' ] ); +// Code with internal indentation +MT.testMode( + 'listCodeIndentation', + '* foo\n\n bar\n hello\n world\n foo\n bar', + [ + 'string', '* foo', + null, ' ', + 'comment', 'bar', + null, ' ', + 'comment', 'hello', + null, ' ', + 'comment', 'world', + null, ' ', + 'comment', 'foo', + null, ' ', + 'string', 'bar' + ] +); // Code followed by text MT.testMode( 'listCodeText', From 95383b66c818b09fb1ac13ccad752bac1c253e7a Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 4 Oct 2012 11:57:25 +0200 Subject: [PATCH 078/135] [simplehint util] Support completeSingle option To turn off the behavior where it'll always complete when only a single option is left. --- lib/util/simple-hint.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/util/simple-hint.js b/lib/util/simple-hint.js index 1a937a1c19..317b8ec148 100644 --- a/lib/util/simple-hint.js +++ b/lib/util/simple-hint.js @@ -25,7 +25,10 @@ editor.replaceRange(str, result.from, result.to); } // When there is only one completion, use it directly. - if (completions.length == 1) {insert(completions[0]); return true;} + if (options.completeSingle && completions.length == 1) { + insert(completions[0]); + return true; + } // Build the select widget var complete = document.createElement("div"); @@ -92,6 +95,7 @@ }; CodeMirror.simpleHint.defaults = { closeOnBackspace: true, - closeOnTokenChange: false + closeOnTokenChange: false, + completeSingle: true }; })(); From 525f15ce350283d54e2b55564f39b711c8758260 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 4 Oct 2012 12:52:09 +0200 Subject: [PATCH 079/135] [keymap/vim] Stop using .forEach on arrays --- keymap/vim.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index b6a41850ef..bcfab9cd6c 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -318,13 +318,12 @@ }; // standard mode switching - iterList(["d", "t", "T", "f", "F", "c", "r"], - function (ch) { - CodeMirror.keyMap.vim[toCombo(ch)] = function (cm) { - cm.setOption("keyMap", "vim-prefix-" + ch); - emptyBuffer(); - }; - }); + iterList(["d", "t", "T", "f", "F", "c", "r"], function (ch) { + CodeMirror.keyMap.vim[toCombo(ch)] = function (cm) { + cm.setOption("keyMap", "vim-prefix-" + ch); + emptyBuffer(); + }; + }); function addCountBindings(keyMap) { // Add bindings for number keys @@ -645,7 +644,7 @@ }; // Map our movement actions each operator and non-operational movement - motionList.forEach(function(key, index, array) { + iterList(motionList, function(key, index, array) { CodeMirror.keyMap['vim-prefix-d'][key] = function(cm) { // Get our selected range var start = cm.getCursor(); @@ -695,7 +694,7 @@ }); var nums = [1,2,3,4,5,6,7,8,9]; - nums.forEach(function(key, index, array) { + iterList(nums, function(key, index, array) { CodeMirror.keyMap['vim'][key] = function (cm) { reptTimes = (reptTimes * 10) + key; }; @@ -713,7 +712,7 @@ // Create our keymaps for each operator and make xa and xi where x is an operator // change to the corrosponding keymap var operators = ['d', 'y', 'c']; - operators.forEach(function(key, index, array) { + iterList(operators, function(key, index, array) { CodeMirror.keyMap['vim-prefix-'+key+'a'] = { auto: 'vim', nofallthrough: true, style: "fat-cursor" }; From 5250736f2ea06074d596f703511806d04bea86e7 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 4 Oct 2012 13:15:12 +0200 Subject: [PATCH 080/135] Set pre elements to overflow: visible Some sites set them to auto, which messes up our cursor (and probably more) --- lib/codemirror.css | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/codemirror.css b/lib/codemirror.css index 05ad0ed013..41b8d09e13 100644 --- a/lib/codemirror.css +++ b/lib/codemirror.css @@ -80,6 +80,7 @@ word-wrap: normal; line-height: inherit; color: inherit; + overflow: visible; } .CodeMirror-wrap pre { From c68ab00979113c1ad9b3ce07a7e2969d59526648 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 15 Oct 2012 10:04:02 +0200 Subject: [PATCH 081/135] [util/simple-hint] Align completion dropdown with completed word Add alignWithWord option to be able to turn that off. --- lib/util/simple-hint.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/util/simple-hint.js b/lib/util/simple-hint.js index 317b8ec148..0ce25f9650 100644 --- a/lib/util/simple-hint.js +++ b/lib/util/simple-hint.js @@ -44,7 +44,7 @@ } sel.firstChild.selected = true; sel.size = Math.min(10, completions.length); - var pos = editor.cursorCoords(); + var pos = options.alignWithWord ? editor.charCoords(result.from) : editor.cursorCoords(); complete.style.left = pos.x + "px"; complete.style.top = pos.yBot + "px"; document.body.appendChild(complete); @@ -96,6 +96,7 @@ CodeMirror.simpleHint.defaults = { closeOnBackspace: true, closeOnTokenChange: false, - completeSingle: true + completeSingle: true, + alignWithWord: true }; })(); From bcc832b1843542d08da8ed4ed220455f762e49e8 Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Thu, 4 Oct 2012 23:09:02 -0400 Subject: [PATCH 082/135] [css] Fix class matching. This basically matches the CSS3 spec (except it doesn't match non-ascii characters). --- mode/css/css.js | 2 +- mode/css/test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mode/css/css.js b/mode/css/css.js index 5e3e233edb..87d5d7401e 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -228,7 +228,7 @@ CodeMirror.defineMode("css", function(config) { else if (/[,+>*\/]/.test(ch)) { return ret(null, "select-op"); } - else if (ch == "." && stream.match(/^\w+/)) { + else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) { return ret("qualifier", type); } else if (ch == ":") { diff --git a/mode/css/test.js b/mode/css/test.js index 4e2d0e8e56..fd6a4b8aa8 100644 --- a/mode/css/test.js +++ b/mode/css/test.js @@ -217,9 +217,9 @@ MT.testMode( MT.testMode( 'classSelector', - '.foo { }', + '.foo-bar_hello { }', [ - 'qualifier', '.foo', + 'qualifier', '.foo-bar_hello', null, ' { }' ] ); From 8c7ca5eaab16b8358d6dc86688dfe648f663f7fc Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 15 Oct 2012 16:18:08 +0200 Subject: [PATCH 083/135] Don't mutated makedSpans arrays And add marker changes to the undo history, so that un/redoing doesn't cause 'ghost' markers to appear. Closes #882 --- lib/codemirror.js | 47 +++++++++++++++++++++++++++++------------------ test/test.js | 17 +++++++++++------ 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index bcbd0b727e..6702234b75 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1508,17 +1508,21 @@ window.CodeMirror = (function() { function TextMarker(type, style) { this.lines = []; this.type = type; if (style) this.style = style; } TextMarker.prototype.clear = operation(function() { - var min = Infinity, max = -Infinity; + var min, max, seen = {}; for (var i = 0; i < this.lines.length; ++i) { - var line = this.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this, true); - if (span.from != null || span.to != null) { - var lineN = lineNo(line); - min = Math.min(min, lineN); max = Math.max(max, lineN); - } + var line = this.lines[i], lineN = lineNo(line); + seen[lineN] = newHL(line.text, line.markedSpans); + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null) min = lineN; + if (span.to != null) max = lineN; + line.markedSpans = removeMarkedSpan(line.markedSpans, span); } - if (min != Infinity) + if (min != null) { changes.push({from: min, to: max + 1}); + var old = []; + for (var i = min; i <= max; ++i) old.push(seen[i]); + history.addChange(min, old.length, old, true); + } this.lines.length = 0; }); TextMarker.prototype.find = function() { @@ -1541,24 +1545,27 @@ window.CodeMirror = (function() { var marker = new TextMarker("range", className); if (options) for (var opt in options) if (options.hasOwnProperty(opt)) marker[opt] = options[opt]; - var curLine = from.line; + var curLine = from.line, old = []; doc.iter(curLine, to.line + 1, function(line) { var span = {from: curLine == from.line ? from.ch : null, to: curLine == to.line ? to.ch : null, marker: marker}; - (line.markedSpans || (line.markedSpans = [])).push(span); + old.push(newHL(line.text, line.markedSpans)); + line.markedSpans = (line.markedSpans || []).concat([span]); marker.lines.push(line); ++curLine; }); changes.push({from: from.line, to: to.line + 1}); + history.addChange(from.line, old.length, old, true); return marker; } function setBookmark(pos) { pos = clipPos(pos); var marker = new TextMarker("bookmark"), line = getLine(pos.line); + history.addChange(pos.line, 1, [newHL(line.text, line.markedSpans)], true); var span = {from: pos.ch, to: pos.ch, marker: marker}; - (line.markedSpans || (line.markedSpans = [])).push(span); + line.markedSpans = (line.markedSpans || []).concat([span]); marker.lines.push(line); return marker; } @@ -2355,16 +2362,20 @@ window.CodeMirror = (function() { this.from = from; this.to = to; this.marker = marker; } - function getMarkedSpanFor(spans, marker, del) { + function getMarkedSpanFor(spans, marker) { if (spans) for (var i = 0; i < spans.length; ++i) { var span = spans[i]; - if (span.marker == marker) { - if (del) spans.splice(i, 1); - return span; - } + if (span.marker == marker) return span; } } + function removeMarkedSpan(spans, span) { + var r; + for (var i = 0; i < spans.lenght; ++i) + if (spans[i] == span) (r || (r = [])).push(spans[i]); + return r; + } + function markedSpansBefore(old, startCh, endCh) { if (old) for (var i = 0, nw; i < old.length; ++i) { var span = old[i], marker = span.marker; @@ -2873,12 +2884,12 @@ window.CodeMirror = (function() { this.closed = false; } History.prototype = { - addChange: function(start, added, old) { + addChange: function(start, added, old, minor) { this.undone.length = 0; var time = +new Date, cur = lst(this.done), last = cur && lst(cur); var dtime = time - this.time; - if (this.compound && cur && !this.closed) { + if (cur && !this.closed && (this.compound || minor)) { cur.push({start: start, added: added, old: old}); } else if (dtime > 400 || !last || this.closed || last.start > start + old.length || last.start + last.added < start) { diff --git a/test/test.js b/test/test.js index bf72734045..9fb4016a4b 100644 --- a/test/test.js +++ b/test/test.js @@ -287,16 +287,21 @@ testCM("markTextMultiLine", function(cm) { }); testCM("markTextUndo", function(cm) { - var marker1 = cm.markText({line: 0, ch: 1}, {line: 0, ch: 3}, "CodeMirror-matchingbracket"); - var marker2 = cm.markText({line: 0, ch: 0}, {line: 2, ch: 1}, "CodeMirror-matchingbracket"); - var bookmark = cm.setBookmark({line: 1, ch: 5}); - cm.replaceRange("foo", {line: 0, ch: 2}); - cm.replaceRange("bar\baz\bug\n", {line: 2, ch: 0}, {line: 3, ch: 0}); + var marker1, marker2, bookmark; + cm.compoundChange(function(){ + marker1 = cm.markText({line: 0, ch: 1}, {line: 0, ch: 3}, "CodeMirror-matchingbracket"); + marker2 = cm.markText({line: 0, ch: 0}, {line: 2, ch: 1}, "CodeMirror-matchingbracket"); + bookmark = cm.setBookmark({line: 1, ch: 5}); + }); + cm.compoundChange(function(){ + cm.replaceRange("foo", {line: 0, ch: 2}); + cm.replaceRange("bar\baz\bug\n", {line: 2, ch: 0}, {line: 3, ch: 0}); + }); cm.setValue(""); eq(marker1.find(), null); eq(marker2.find(), null); eq(bookmark.find(), null); cm.undo(); eqPos(bookmark.find(), {line: 1, ch: 5}); - cm.undo(); cm.undo(); + cm.undo(); var m1Pos = marker1.find(), m2Pos = marker2.find(); eqPos(m1Pos.from, {line: 0, ch: 1}); eqPos(m1Pos.to, {line: 0, ch: 3}); eqPos(m2Pos.from, {line: 0, ch: 0}); eqPos(m2Pos.to, {line: 2, ch: 1}); From 82702d5306d602da3ddcc4bde07ecda327c64f29 Mon Sep 17 00:00:00 2001 From: ks-ifware Date: Thu, 20 Sep 2012 17:03:02 -0300 Subject: [PATCH 084/135] Fixed multi-line Lua comment recognition Multi-line Lua comments start with "--[[", not "--[" --- mode/lua/lua.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/lua/lua.js b/mode/lua/lua.js index 60e84a9264..97fb2c6f96 100644 --- a/mode/lua/lua.js +++ b/mode/lua/lua.js @@ -64,7 +64,7 @@ CodeMirror.defineMode("lua", function(config, parserConfig) { function normal(stream, state) { var ch = stream.next(); if (ch == "-" && stream.eat("-")) { - if (stream.eat("[")) + if (stream.eat("[") && stream.eat("[")) return (state.cur = bracketed(readBracket(stream), "comment"))(stream, state); stream.skipToEnd(); return "comment"; From ebcd76efe758635a33b7c9ccf03740f9b010c453 Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Sat, 13 Oct 2012 16:21:30 -0300 Subject: [PATCH 085/135] Fix typo in manual. --- doc/manual.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual.html b/doc/manual.html index 09fb1c74c5..fe10b4c036 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -1120,7 +1120,7 @@

    Writing CodeMirror Modes

    state.

    If you want your mode to provide smart indentation - (though the indentLine + (through the indentLine method and the indentAuto and newlineAndIndent commands, which keys can be bound to), you must define From fdf78f757208674d0594fe0ddaa0f1b08bbc1d82 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 15 Oct 2012 21:45:57 +0200 Subject: [PATCH 086/135] Fix typo (lenght) --- lib/codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 6702234b75..13d7096471 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2371,7 +2371,7 @@ window.CodeMirror = (function() { function removeMarkedSpan(spans, span) { var r; - for (var i = 0; i < spans.lenght; ++i) + for (var i = 0; i < spans.length; ++i) if (spans[i] == span) (r || (r = [])).push(spans[i]); return r; } From 5ea1eb983e17c1273921a49890a4fd51653ac185 Mon Sep 17 00:00:00 2001 From: ComFreek Date: Mon, 15 Oct 2012 18:22:44 +0200 Subject: [PATCH 087/135] Merged JavaScript and TypeScript mode as marijnh suggested --- mode/javascript/index.html | 17 ++++++--- mode/javascript/javascript.js | 40 +++++++++++++++++++-- mode/javascript/typescript.html | 61 +++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 mode/javascript/typescript.html diff --git a/mode/javascript/index.html b/mode/javascript/index.html index 206df3fca1..86d3dde3fe 100644 --- a/mode/javascript/index.html +++ b/mode/javascript/index.html @@ -69,10 +69,19 @@

    CodeMirror: JavaScript mode

    }); -

    JavaScript mode supports a single configuration - option, json, which will set the mode to expect JSON - data rather than a JavaScript program.

    +

    + JavaScript mode supports a two configuration + options: +

      +
    • json which will set the mode to expect JSON data rather than a JavaScript program.
    • +
    • + typescript which will activate additional syntax highlighting and some other things for TypeScript code. +
      + Click here for the demo which also provides an extra theme: typescript.html +
    • +
    +

    -

    MIME types defined: text/javascript, application/json.

    +

    MIME types defined: text/javascript, application/json, text/typescript, application/typescript.

    diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js index 5b377db7d1..cbb7bc46ca 100644 --- a/mode/javascript/javascript.js +++ b/mode/javascript/javascript.js @@ -1,6 +1,11 @@ -CodeMirror.defineMode("javascript", function(config, parserConfig) { +/** + * The TypeScript extensions are (C) Copyright 2012 by ComFreek + */ + +CodeMirror.defineMode("javascript", function (config, parserConfig) { var indentUnit = config.indentUnit; var jsonMode = parserConfig.json; + var isTS = parserConfig.typescript; // Tokenizer @@ -8,7 +13,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { function kw(type) {return {type: type, style: "keyword"};} var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); var operator = kw("operator"), atom = {type: "atom", style: "atom"}; - return { + + var jsKeywords = { "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "var": kw("var"), "const": kw("var"), "let": kw("var"), @@ -17,6 +23,34 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { "in": operator, "typeof": operator, "instanceof": operator, "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom }; + + // Extend the 'normal' keywords with the TypeScript language extensions + if (isTS) { + var tsKeywords = { + // object-like things + "interface": kw("interface"), + "class": kw("class"), + "extends": kw("extends"), + "constructor": kw("constructor"), + + // scope modifiers + "public": kw("public"), + "private": kw("private"), + "protected": kw("protected"), + "static": kw("static"), + + "super": kw("super"), + + // types + "string": type, "number": type, "bool": type, "any": type + }; + + for (var attr in tsKeywords) { + jsKeywords[attr] = tsKeywords[attr]; + } + } + + return jsKeywords; }(); var isOperatorChar = /[+\-*&%=<>!?|]/; @@ -360,3 +394,5 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { CodeMirror.defineMIME("text/javascript", "javascript"); CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); +CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); +CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); diff --git a/mode/javascript/typescript.html b/mode/javascript/typescript.html new file mode 100644 index 0000000000..ba7496b90c --- /dev/null +++ b/mode/javascript/typescript.html @@ -0,0 +1,61 @@ + + + + + CodeMirror: JavaScript mode using TypeScript extension + + + + + + + +

    CodeMirror: JavaScript mode using TypeScript extension

    + +
    + + + +

    + JavaScript mode supports a two configuration + options: +

      +
    • json which will set the mode to expect JSON data rather than a JavaScript program.
    • +
    • typescript which will activate additional syntax highlighting and some other things for TypeScript code.
    • +
    +

    + +

    + This sample also uses the TypeBox.css theme. +

    + +

    MIME types defined: text/javascript, application/json, text/typescript, application/typescript.

    + + From b3c1c795f5618fe14d23f9f845b06d1ae1809546 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 16 Oct 2012 11:50:21 +0200 Subject: [PATCH 088/135] Make typescript mode recognize type declarations --- mode/javascript/index.html | 4 +--- mode/javascript/javascript.js | 30 ++++++++++++++++++++++-------- mode/javascript/typescript.html | 19 +++---------------- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/mode/javascript/index.html b/mode/javascript/index.html index 86d3dde3fe..a9fb381fdf 100644 --- a/mode/javascript/index.html +++ b/mode/javascript/index.html @@ -75,9 +75,7 @@

    CodeMirror: JavaScript mode

    • json which will set the mode to expect JSON data rather than a JavaScript program.
    • - typescript which will activate additional syntax highlighting and some other things for TypeScript code. -
      - Click here for the demo which also provides an extra theme: typescript.html + typescript which will activate additional syntax highlighting and some other things for TypeScript code (demo).

    diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js index cbb7bc46ca..eb258818b0 100644 --- a/mode/javascript/javascript.js +++ b/mode/javascript/javascript.js @@ -2,7 +2,9 @@ * The TypeScript extensions are (C) Copyright 2012 by ComFreek */ -CodeMirror.defineMode("javascript", function (config, parserConfig) { +// TODO actually recognize syntax of TypeScript constructs + +CodeMirror.defineMode("javascript", function(config, parserConfig) { var indentUnit = config.indentUnit; var jsonMode = parserConfig.json; var isTS = parserConfig.typescript; @@ -25,7 +27,8 @@ CodeMirror.defineMode("javascript", function (config, parserConfig) { }; // Extend the 'normal' keywords with the TypeScript language extensions - if (isTS) { + if (isTS) { + var type = {type: "variable", style: "variable-3"}; var tsKeywords = { // object-like things "interface": kw("interface"), @@ -42,12 +45,12 @@ CodeMirror.defineMode("javascript", function (config, parserConfig) { "super": kw("super"), // types - "string": type, "number": type, "bool": type, "any": type + "string": type, "number": type, "bool": type, "any": type }; - for (var attr in tsKeywords) { - jsKeywords[attr] = tsKeywords[attr]; - } + for (var attr in tsKeywords) { + jsKeywords[attr] = tsKeywords[attr]; + } } return jsKeywords; @@ -309,8 +312,19 @@ CodeMirror.defineMode("javascript", function (config, parserConfig) { if (type == "}") return cont(); return pass(statement, block); } + function maybetype(type) { + if (type == ":") return cont(typedef); + return pass(); + } + function typedef(type) { + if (type == "variable"){cx.marked = "variable-3"; return cont();} + return pass(); + } function vardef1(type, value) { - if (type == "variable"){register(value); return cont(vardef2);} + if (type == "variable") { + register(value); + return isTS ? cont(maybetype, vardef2) : cont(vardef2); + } return cont(); } function vardef2(type, value) { @@ -340,7 +354,7 @@ CodeMirror.defineMode("javascript", function (config, parserConfig) { if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext); } function funarg(type, value) { - if (type == "variable") {register(value); return cont();} + if (type == "variable") {register(value); return isTS ? cont(maybetype) : cont();} } // Interface diff --git a/mode/javascript/typescript.html b/mode/javascript/typescript.html index ba7496b90c..58315e7ac7 100644 --- a/mode/javascript/typescript.html +++ b/mode/javascript/typescript.html @@ -2,7 +2,7 @@ - CodeMirror: JavaScript mode using TypeScript extension + CodeMirror: TypeScript mode @@ -10,7 +10,7 @@ -

    CodeMirror: JavaScript mode using TypeScript extension

    +

    CodeMirror: TypeScript mode

    + + + +

    MIME type defined: text/x-z80.

    + + diff --git a/mode/z80/z80.js b/mode/z80/z80.js new file mode 100644 index 0000000000..c026790dc7 --- /dev/null +++ b/mode/z80/z80.js @@ -0,0 +1,113 @@ +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; + 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; + + return {startState: function() + { + return {context: 0}; + }, token: function(stream, state) + { + if (!stream.column()) + state.context = 0; + + if (stream.eatSpace()) + return null; + + var w; + + if (stream.eatWhile(/\w/)) + { + w = stream.current(); + + if (stream.indentation()) + { + if (state.context == 1 && variables1.test(w)) + return 'variable-2'; + + if (state.context == 2 && variables2.test(w)) + return 'variable-3'; + + if (keywords1.test(w)) + { + state.context = 1; + return 'keyword'; + } + else if (keywords2.test(w)) + { + state.context = 2; + return 'keyword'; + } + else if (keywords3.test(w)) + { + state.context = 3; + return 'keyword'; + } + + if (errors.test(w)) + return 'error'; + } + else if (numbers.test(w)) + { + return 'number'; + } + else + { + return null; + } + } + else if (stream.eat(';')) + { + stream.skipToEnd(); + return 'comment'; + } + else if (stream.eat('"')) + { + while (w = stream.next()) + { + if (w == '"') + break; + + if (w == '\\') + stream.next(); + } + + return 'string'; + } + else if (stream.eat('\'')) + { + if (stream.match(/\\?.'/)) + return 'number'; + } + else if (stream.eat('.') || stream.sol() && stream.eat('#')) + { + state.context = 4; + + if (stream.eatWhile(/\w/)) + return 'def'; + } + else if (stream.eat('$')) + { + if (stream.eatWhile(/[\da-f]/i)) + return 'number'; + } + else if (stream.eat('%')) + { + if (stream.eatWhile(/[01]/)) + return 'number'; + } + else + { + stream.next(); + } + + return null; + }}; +}); + +CodeMirror.defineMIME("text/x-z80", "z80"); From 97258823702524e1f31c116dd04c6eefe11a20f5 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 12 Nov 2012 10:25:12 +0100 Subject: [PATCH 124/135] Remove ambiance-light theme, simply add an extra file that can be used to disable box-shadow Issue #974 --- theme/ambiance-light.css | 77 --------------------------------------- theme/ambiance-mobile.css | 6 +++ theme/ambiance.css | 2 +- 3 files changed, 7 insertions(+), 78 deletions(-) delete mode 100644 theme/ambiance-light.css create mode 100644 theme/ambiance-mobile.css diff --git a/theme/ambiance-light.css b/theme/ambiance-light.css deleted file mode 100644 index 9a74021e1f..0000000000 --- a/theme/ambiance-light.css +++ /dev/null @@ -1,77 +0,0 @@ -/* ambiance theme for code-mirror */ - -/* Color scheme */ - -.cm-s-ambiance .cm-keyword { color: #cda869; } -.cm-s-ambiance .cm-atom { color: #CF7EA9; } -.cm-s-ambiance .cm-number { color: #78CF8A; } -.cm-s-ambiance .cm-def { color: #aac6e3; } -.cm-s-ambiance .cm-variable { color: #ffb795; } -.cm-s-ambiance .cm-variable-2 { color: #eed1b3; } -.cm-s-ambiance .cm-variable-3 { color: #faded3; } -.cm-s-ambiance .cm-property { color: #eed1b3; } -.cm-s-ambiance .cm-operator {color: #fa8d6a;} -.cm-s-ambiance .cm-comment { color: #555; font-style:italic; } -.cm-s-ambiance .cm-string { color: #8f9d6a; } -.cm-s-ambiance .cm-string-2 { color: #9d937c; } -.cm-s-ambiance .cm-meta { color: #D2A8A1; } -.cm-s-ambiance .cm-error { color: #AF2018; } -.cm-s-ambiance .cm-qualifier { color: yellow; } -.cm-s-ambiance .cm-builtin { color: #9999cc; } -.cm-s-ambiance .cm-bracket { color: #24C2C7; } -.cm-s-ambiance .cm-tag { color: #fee4ff } -.cm-s-ambiance .cm-attribute { color: #9B859D; } -.cm-s-ambiance .cm-header {color: blue;} -.cm-s-ambiance .cm-quote { color: #24C2C7; } -.cm-s-ambiance .cm-hr { color: pink; } -.cm-s-ambiance .cm-link { color: #F4C20B; } -.cm-s-ambiance .cm-special { color: #FF9D00; } - -.cm-s-ambiance .CodeMirror-matchingbracket { color: #0f0; } -.cm-s-ambiance .CodeMirror-nonmatchingbracket { color: #f22; } - -.cm-s-ambiance .CodeMirror-selected { - background: rgba(255, 255, 255, 0.15); -} -.CodeMirror-focused .cm-s-ambiance .CodeMirror-selected { - background: rgba(255, 255, 255, 0.10); -} - -/* Editor styling */ - -.cm-s-ambiance { - line-height: 1.40em; - font-family: Monaco, Menlo,"Andale Mono","lucida console","Courier New",monospace !important; - color: #E6E1DC; - background-color: #202020; -} - -.cm-s-ambiance .CodeMirror-gutter { - background: #3D3D3D; - padding: 0 5px; - text-shadow: #333 1px 1px; - border-right: 1px solid #4D4D4D; - box-shadow: 0 10px 20px black; -} - -.cm-s-ambiance .CodeMirror-gutter .CodeMirror-gutter-text { - text-shadow: 0px 1px 1px #4d4d4d; - color: #222; -} - -.cm-s-ambiance .CodeMirror-lines { - -} - -.cm-s-ambiance .CodeMirror-lines .CodeMirror-cursor { - border-left: 1px solid #7991E8; -} - -.cm-s-ambiance .activeline { - background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.031); -} - -.cm-s-ambiance, -.cm-s-ambiance .CodeMirror-gutter { - background-image: url("data:image/png;base64,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"); -} diff --git a/theme/ambiance-mobile.css b/theme/ambiance-mobile.css new file mode 100644 index 0000000000..d60d19bd33 --- /dev/null +++ b/theme/ambiance-mobile.css @@ -0,0 +1,6 @@ +.CodeMirror .cm-s-ambiance { + -webkit-box-shadow: none; + -moz-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; +} diff --git a/theme/ambiance.css b/theme/ambiance.css index ef5f8d09ba..3b93f9c7a5 100644 --- a/theme/ambiance.css +++ b/theme/ambiance.css @@ -1,4 +1,4 @@ -/* ambiance theme for code-mirror */ +/* ambiance theme for codemirror */ /* Color scheme */ From e4d0c6151b7aa7c730972d280e3ab89eb6d659f0 Mon Sep 17 00:00:00 2001 From: Mighty Guava Date: Sun, 11 Nov 2012 04:23:51 -0500 Subject: [PATCH 125/135] [vim keymap] Lots of fixes for navigation. Go to line with Shift+G works again. Updated navigation behavior to match closer to vim. dw/de at end of line will no longer delete newline. --- keymap/vim.js | 237 +++++++++++++++++++++++++++++++------------------- 1 file changed, 148 insertions(+), 89 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index bcfab9cd6c..c2e74b8270 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -17,7 +17,7 @@ // Entering insert mode: // i, I, a, A, o, O // s -// ce, cb (without support for number of actions like c3e - TODO) +// ce, cb // cc // S, C TODO // cf, cF, ct, cT @@ -26,7 +26,7 @@ // x, X // J // dd, D -// de, db (without support for number of actions like d3e - TODO) +// de, db // df, dF, dt, dT // // Yanking and pasting: @@ -48,18 +48,25 @@ // (function() { - var count = ""; var sdir = "f"; var buf = ""; var yank = 0; var mark = []; - var reptTimes = 0; + var repeatCount = 0; + function isLine(cm, line) { return line >= 0 && line < cm.lineCount(); } function emptyBuffer() { buf = ""; } function pushInBuffer(str) { buf += str; } - function pushCountDigit(digit) { return function(cm) {count += digit;}; } - function popCount() { var i = parseInt(count, 10); count = ""; return i || 1; } + function pushRepeatCountDigit(digit) {return function(cm) {repeatCount = (repeatCount * 10) + digit}; } + function getCountOrOne() { + var i = repeatCount; + return i || 1; + } + function clearCount() { + repeatCount = 0; + } function iterTimes(func) { - for (var i = 0, c = popCount(); i < c; ++i) func(i, i == c - 1); + for (var i = 0, c = getCountOrOne(); i < c; ++i) func(i, i == c - 1); + clearCount(); } function countTimes(func) { if (typeof func == "string") func = CodeMirror.commands[func]; @@ -93,48 +100,104 @@ } var word = [/\w/, /[^\w\s]/], bigWord = [/\S/]; - function findWord(line, pos, dir, regexps) { - var stop = 0, next = -1; - if (dir > 0) { stop = line.length; next = 0; } - var start = stop, end = stop; - // Find bounds of next one. - outer: for (; pos != stop; pos += dir) { - for (var i = 0; i < regexps.length; ++i) { - if (regexps[i].test(line.charAt(pos + next))) { - start = pos; - for (; pos != stop; pos += dir) { - if (!regexps[i].test(line.charAt(pos + next))) break; + // Finds a word on the given line, and continue searching the next line if it can't find one. + function findWord(cm, lineNum, pos, dir, regexps) { + var line = cm.getLine(lineNum); + while (true) { + var stop = (dir > 0) ? line.length : -1; + var wordStart = stop, wordEnd = stop; + // Find bounds of next word. + for (; pos != stop; pos += dir) { + for (var i = 0; i < regexps.length; ++i) { + if (regexps[i].test(line.charAt(pos))) { + wordStart = pos; + // Advance to end of word. + for (; pos != stop && regexps[i].test(line.charAt(pos)); pos += dir) {} + wordEnd = (dir > 0) ? pos : pos + 1; + return { + from: Math.min(wordStart, wordEnd), + to: Math.max(wordStart, wordEnd), + line: lineNum}; } - end = pos; - break outer; } } + // Advance to next/prev line. + lineNum += dir; + if (!isLine(cm, lineNum)) return null; + line = cm.getLine(lineNum); + pos = (dir > 0) ? 0 : line.length; } - return {from: Math.min(start, end), to: Math.max(start, end)}; } - function moveToWord(cm, regexps, dir, times, where) { + /** + * @param {boolean} cm CodeMirror object. + * @param {regexp} regexps Regular expressions for word characters. + * @param {number} dir Direction, +/- 1. + * @param {number} times Number of times to advance word. + * @param {string} where Go to "start" or "end" of word, 'e' vs 'w'. + * @param {boolean} yank Whether we are finding words to yank. If true, + * do not go to the next line to look for the last word. This is to + * prevent deleting new line on 'dw' at the end of a line. + */ + function moveToWord(cm, regexps, dir, times, where, yank) { var cur = cm.getCursor(); - + if (yank) { + where = 'start'; + } for (var i = 0; i < times; i++) { - var line = cm.getLine(cur.line), startCh = cur.ch, word; + var startCh = cur.ch, startLine = cur.line, word; while (true) { - // If we're at start/end of line, start on prev/next respectivly - if (cur.ch == line.length && dir > 0) { - cur.line++; - cur.ch = 0; - line = cm.getLine(cur.line); - } else if (cur.ch == 0 && dir < 0) { - cur.line--; - cur.ch = line.length; - line = cm.getLine(cur.line); + // Search and advance. + word = findWord(cm, cur.line, cur.ch, dir, regexps); + if (word) { + if (yank && times == 1 && dir == 1 && cur.line != word.line) { + // Stop at end of line of last word. Don't want to delete line return + // for dw if the last deleted word is at the end of a line. + cur.ch = cm.getLine(cur.line).length; + break; + } else { + // Move to the word we just found. If by moving to the word we end up + // in the same spot, then move an extra character and search again. + cur.line = word.line; + if (dir > 0 && where == 'end') { + // 'e' + if (startCh != word.to - 1 || startLine != word.line) { + cur.ch = word.to - 1; + break; + } else { + cur.ch = word.to; + } + } else if (dir > 0 && where == 'start') { + // 'w' + if (startCh != word.from || startLine != word.line) { + cur.ch = word.from; + break; + } else { + cur.ch = word.to; + } + } else if (dir < 0 && where == 'end') { + // 'ge' + if (startCh != word.to || startLine != word.line) { + cur.ch = word.to; + break; + } else { + cur.ch = word.from - 1; + } + } else if (dir < 0 && where == 'start') { + // 'b' + if (startCh != word.from || startLine != word.line) { + cur.ch = word.from; + break; + } else { + cur.ch = word.from - 1; + } + } + } + } else { + // No more words to be found. Move to end of document. + for (; isLine(cm, cur.line + dir); cur.line += dir) {} + cur.ch = (dir > 0) ? cm.getLine(cur.line).length : 0; + break; } - if (!line) break; - - // On to the actual searching - word = findWord(line, cur.ch, dir, regexps); - cur.ch = word[where == "end" ? "to" : "from"]; - if (startCh == cur.ch && word.from != word.to) cur.ch = word[dir < 0 ? "from" : "to"]; - else break; } } return cur; @@ -220,7 +283,7 @@ function enterInsertMode(cm) { // enter insert mode: switch mode and cursor - popCount(); + clearCount(); cm.setOption("keyMap", "vim-insert"); } @@ -238,7 +301,8 @@ var map = CodeMirror.keyMap.vim = { // Pipe (|); TODO: should be *screen* chars, so need a util function to turn tabs into spaces? "'|'": function(cm) { - cm.setCursor(cm.getCursor().line, popCount() - 1, true); + cm.setCursor(cm.getCursor().line, getCountOrOne() - 1, true); + clearCount(); }, "A": function(cm) { cm.setCursor(cm.getCursor().line, cm.getCursor().ch+1, true); @@ -300,8 +364,8 @@ if (fn) sdir != "r" ? CodeMirror.commands.findPrev(cm) : fn.findNext(cm); }, "Shift-G": function(cm) { - count == "" ? cm.setCursor(cm.lineCount()) : cm.setCursor(parseInt(count, 10)-1); - popCount(); + (repeatCount == 0) ? cm.setCursor(cm.lineCount()) : cm.setCursor(repeatCount - 1); + clearCount(); CodeMirror.commands.goLineStart(cm); }, "':'": function(cm) { @@ -325,15 +389,6 @@ }; }); - function addCountBindings(keyMap) { - // Add bindings for number keys - keyMap["0"] = function(cm) { - count.length > 0 ? pushCountDigit("0")(cm) : CodeMirror.commands.goLineStart(cm); - }; - for (var i = 1; i < 10; ++i) keyMap[i] = pushCountDigit(i); - } - addCountBindings(CodeMirror.keyMap.vim); - // main num keymap // Add bindings that are influenced by number keys iterObj({ @@ -401,9 +456,12 @@ }); CodeMirror.keyMap["vim-prefix-g"] = { - "E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, word, -1, 1, "start"));}), - "Shift-E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, bigWord, -1, 1, "start"));}), - "G": function (cm) { cm.setCursor({line: 0, ch: cm.getCursor().ch});}, + "E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, word, -1, 1, "end"));}), + "Shift-E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, bigWord, -1, 1, "end"));}), + "G": function (cm) { + cm.setCursor({line: repeatCount - 1, ch: cm.getCursor().ch}); + clearCount(); + }, auto: "vim", nofallthrough: true, style: "fat-cursor" }; @@ -428,8 +486,6 @@ }, nofallthrough: true, style: "fat-cursor" }; - // FIXME - does not work for bindings like "d3e" - addCountBindings(CodeMirror.keyMap["vim-prefix-d"]); CodeMirror.keyMap["vim-prefix-c"] = { "B": function (cm) { @@ -593,10 +649,10 @@ var motionList = ['B', 'E', 'J', 'K', 'H', 'L', 'W', 'Shift-W', "'^'", "'$'", "'%'", 'Esc']; motions = { - 'B': function(cm, times) { return moveToWord(cm, word, -1, times); }, - 'Shift-B': function(cm, times) { return moveToWord(cm, bigWord, -1, times); }, - 'E': function(cm, times) { return moveToWord(cm, word, 1, times, 'end'); }, - 'Shift-E': function(cm, times) { return moveToWord(cm, bigWord, 1, times, 'end'); }, + 'B': function(cm, times, yank) { return moveToWord(cm, word, -1, times, 'start', yank); }, + 'Shift-B': function(cm, times, yank) { return moveToWord(cm, bigWord, -1, times, 'start', yank); }, + 'E': function(cm, times, yank) { return moveToWord(cm, word, 1, times, 'end', yank); }, + 'Shift-E': function(cm, times, yank) { return moveToWord(cm, bigWord, 1, times, 'end', yank); }, 'J': function(cm, times) { var cur = cm.getCursor(); return {line: cur.line+times, ch : cur.ch}; @@ -616,8 +672,8 @@ var cur = cm.getCursor(); return {line: cur.line, ch: cur.ch+times}; }, - 'W': function(cm, times) { return moveToWord(cm, word, 1, times); }, - 'Shift-W': function(cm, times) { return moveToWord(cm, bigWord, 1, times); }, + 'W': function(cm, times, yank) { return moveToWord(cm, word, 1, times, 'start', yank); }, + 'Shift-W': function(cm, times, yank) { return moveToWord(cm, bigWord, 1, times, 'start', yank); }, "'^'": function(cm, times) { var cur = cm.getCursor(); var line = cm.getLine(cur.line).split(''); @@ -637,7 +693,7 @@ "'%'": function(cm) { return findMatchedSymbol(cm, cm.getCursor()); }, "Esc" : function(cm) { cm.setOption('vim'); - reptTimes = 0; + repeatCount = 0; return cm.getCursor(); } @@ -648,7 +704,7 @@ CodeMirror.keyMap['vim-prefix-d'][key] = function(cm) { // Get our selected range var start = cm.getCursor(); - var end = motions[key](cm, reptTimes ? reptTimes : 1); + var end = motions[key](cm, repeatCount ? repeatCount : 1, true); // Set swap var if range is of negative length if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true; @@ -658,56 +714,59 @@ cm.replaceRange("", swap ? end : start, swap ? start : end); // And clean up - reptTimes = 0; + repeatCount = 0; cm.setOption("keyMap", "vim"); }; CodeMirror.keyMap['vim-prefix-c'][key] = function(cm) { var start = cm.getCursor(); - var end = motions[key](cm, reptTimes ? reptTimes : 1); + var end = motions[key](cm, repeatCount ? repeatCount : 1, true); if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true; pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end)); cm.replaceRange("", swap ? end : start, swap ? start : end); - reptTimes = 0; + repeatCount = 0; cm.setOption('keyMap', 'vim-insert'); }; CodeMirror.keyMap['vim-prefix-y'][key] = function(cm) { var start = cm.getCursor(); - var end = motions[key](cm, reptTimes ? reptTimes : 1); + var end = motions[key](cm, repeatCount ? repeatCount : 1, true); if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true; pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end)); - reptTimes = 0; + repeatCount = 0; cm.setOption("keyMap", "vim"); }; CodeMirror.keyMap['vim'][key] = function(cm) { - var cur = motions[key](cm, reptTimes ? reptTimes : 1); + var cur = motions[key](cm, repeatCount ? repeatCount : 1); cm.setCursor(cur.line, cur.ch); - reptTimes = 0; + repeatCount = 0; }; }); - var nums = [1,2,3,4,5,6,7,8,9]; - iterList(nums, function(key, index, array) { - CodeMirror.keyMap['vim'][key] = function (cm) { - reptTimes = (reptTimes * 10) + key; - }; - CodeMirror.keyMap['vim-prefix-d'][key] = function (cm) { - reptTimes = (reptTimes * 10) + key; - }; - CodeMirror.keyMap['vim-prefix-y'][key] = function (cm) { - reptTimes = (reptTimes * 10) + key; - }; - CodeMirror.keyMap['vim-prefix-c'][key] = function (cm) { - reptTimes = (reptTimes * 10) + key; + function addCountBindings(keyMapName) { + // Add bindings for number keys + keyMap = CodeMirror.keyMap[keyMapName]; + keyMap["0"] = function(cm) { + if (repeatCount > 0) { + pushRepeatCountDigit(0)(cm); + } else { + CodeMirror.commands.goLineStart(cm); + } }; - }); + for (var i = 1; i < 10; ++i) { + keyMap[i] = pushRepeatCountDigit(i); + } + } + addCountBindings('vim'); + addCountBindings('vim-prefix-d'); + addCountBindings('vim-prefix-y'); + addCountBindings('vim-prefix-c'); // Create our keymaps for each operator and make xa and xi where x is an operator // change to the corrosponding keymap @@ -721,12 +780,12 @@ }; CodeMirror.keyMap['vim-prefix-'+key]['A'] = function(cm) { - reptTimes = 0; + repeatCount = 0; cm.setOption('keyMap', 'vim-prefix-' + key + 'a'); }; CodeMirror.keyMap['vim-prefix-'+key]['I'] = function(cm) { - reptTimes = 0; + repeatCount = 0; cm.setOption('keyMap', 'vim-prefix-' + key + 'i'); }; }); From 7b6c49b55139ea2121727c1adaa999f7d6f84e81 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 12 Nov 2012 12:44:37 +0100 Subject: [PATCH 126/135] Remove jsdares from real-world use list again --- doc/realworld.html | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/realworld.html b/doc/realworld.html index 4393ed51ff..5633e5e3d7 100644 --- a/doc/realworld.html +++ b/doc/realworld.html @@ -46,7 +46,6 @@

    { } CodeMi
  • ICEcoder (web IDE)
  • Joomla plugin
  • jsbin.com (JS playground)
  • -
  • jsdares (interactive learning)
  • JSHint (JS linter)
  • kl1p (paste service)
  • Light Table (experimental IDE)
  • From 9a40f56e40427903c01992bfe35270c52b20a699 Mon Sep 17 00:00:00 2001 From: Matt Sacks Date: Mon, 12 Nov 2012 08:02:53 -0800 Subject: [PATCH 127/135] Refactor the Shift-D keymapping for vim Before, Shift-D would delete the entire line with the vim keymapping set. Vim's binding for this deletes from the cursor up til the end of the line, so that's been fixed. Also, allow for count iterations on Shift-D. --- keymap/vim.js | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index c2e74b8270..ad7b46302f 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -324,16 +324,26 @@ }, "G": function(cm) { cm.setOption("keyMap", "vim-prefix-g");}, "Shift-D": function(cm) { - // commented out verions works, but I left original, cause maybe - // I don't know vim enouth to see what it does - /* var cur = cm.getCursor(); - var f = {line: cur.line, ch: cur.ch}, t = {line: cur.line}; - pushInBuffer(cm.getRange(f, t)); - */ + var cursor = cm.getCursor(); + var lineN = cursor.line; + var line = cm.getLine(lineN); + cm.setLine(lineN, line.slice(0, cursor.ch)); + emptyBuffer(); - mark["Shift-D"] = cm.getCursor(false).line; - cm.setCursor(cm.getCursor(true).line); - delTillMark(cm,"Shift-D"); mark = []; + pushInBuffer(line.slice(cursor.ch)); + + if (repeatCount > 1) { + // we've already done it once + --repeatCount; + // the lines dissapear (ie, cursor stays on the same lineN), + // so only incremenet once + ++lineN; + + iterTimes(function(i) { + pushInBuffer(cm.getLine(lineN)); + cm.removeLine(lineN); + }); + } }, "S": function (cm) { From 8e5c9b4635d7c1f2df8d649e396764d9f03cfeb9 Mon Sep 17 00:00:00 2001 From: Jan Keromnes Date: Tue, 13 Nov 2012 14:45:39 +0800 Subject: [PATCH 128/135] [vim] `mark` should be a hashmap, not an array --- keymap/vim.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index ad7b46302f..1ff6d6fc8a 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -51,7 +51,7 @@ var sdir = "f"; var buf = ""; var yank = 0; - var mark = []; + var mark = {}; var repeatCount = 0; function isLine(cm, line) { return line >= 0 && line < cm.lineCount(); } function emptyBuffer() { buf = ""; } @@ -352,13 +352,13 @@ })(cm); enterInsertMode(cm); }, - "M": function(cm) {cm.setOption("keyMap", "vim-prefix-m"); mark = [];}, + "M": function(cm) {cm.setOption("keyMap", "vim-prefix-m"); mark = {};}, "Y": function(cm) {cm.setOption("keyMap", "vim-prefix-y"); emptyBuffer(); yank = 0;}, "Shift-Y": function(cm) { emptyBuffer(); mark["Shift-D"] = cm.getCursor(false).line; cm.setCursor(cm.getCursor(true).line); - yankTillMark(cm,"Shift-D"); mark = []; + yankTillMark(cm,"Shift-D"); mark = {}; }, "/": function(cm) {var f = CodeMirror.commands.find; f && f(cm); sdir = "f";}, "'?'": function(cm) { From 4a3ec5a82bdc298c67803a5fd36f66acd44a0457 Mon Sep 17 00:00:00 2001 From: Jan Keromnes Date: Tue, 13 Nov 2012 15:10:30 +0800 Subject: [PATCH 129/135] [vim] fix cursor jump in `Shift-Y` Also refactor `countTimes` and get rid of confusing global `yank`. --- keymap/vim.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 1ff6d6fc8a..7cc8d67460 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -50,7 +50,6 @@ (function() { var sdir = "f"; var buf = ""; - var yank = 0; var mark = {}; var repeatCount = 0; function isLine(cm, line) { return line >= 0 && line < cm.lineCount(); } @@ -70,7 +69,7 @@ } function countTimes(func) { if (typeof func == "string") func = CodeMirror.commands[func]; - return function(cm) { iterTimes(function () { func(cm); }); }; + return function(cm) { iterTimes(function (i, last) { func(cm, i, last); }); }; } function iterObj(o, f) { @@ -220,7 +219,7 @@ var l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l; cm.setCursor(start); for (var c = start; c <= end; c++) { - pushInBuffer("\n"+cm.getLine(start)); + pushInBuffer("\n" + cm.getLine(start)); cm.removeLine(start); } } @@ -232,7 +231,7 @@ } var l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l; for (var c = start; c <= end; c++) { - pushInBuffer("\n"+cm.getLine(c)); + pushInBuffer("\n" + cm.getLine(c)); } cm.setCursor(start); } @@ -339,7 +338,7 @@ // so only incremenet once ++lineN; - iterTimes(function(i) { + iterTimes(function() { pushInBuffer(cm.getLine(lineN)); cm.removeLine(lineN); }); @@ -353,12 +352,10 @@ enterInsertMode(cm); }, "M": function(cm) {cm.setOption("keyMap", "vim-prefix-m"); mark = {};}, - "Y": function(cm) {cm.setOption("keyMap", "vim-prefix-y"); emptyBuffer(); yank = 0;}, + "Y": function(cm) {cm.setOption("keyMap", "vim-prefix-y"); emptyBuffer();}, "Shift-Y": function(cm) { emptyBuffer(); - mark["Shift-D"] = cm.getCursor(false).line; - cm.setCursor(cm.getCursor(true).line); - yankTillMark(cm,"Shift-D"); mark = {}; + iterTimes(function(i) { pushInBuffer("\n" + cm.getLine(cm.getCursor().line + i)); }); }, "/": function(cm) {var f = CodeMirror.commands.find; f && f(cm); sdir = "f";}, "'?'": function(cm) { @@ -477,7 +474,7 @@ CodeMirror.keyMap["vim-prefix-d"] = { "D": countTimes(function(cm) { - pushInBuffer("\n"+cm.getLine(cm.getCursor().line)); + pushInBuffer("\n" + cm.getLine(cm.getCursor().line)); cm.removeLine(cm.getCursor().line); cm.setOption("keyMap", "vim"); }), @@ -537,10 +534,10 @@ mark[m] = cm.getCursor().line; }; CodeMirror.keyMap["vim-prefix-d'"][m] = function(cm) { - delTillMark(cm,m); + delTillMark(cm, m); }; CodeMirror.keyMap["vim-prefix-y'"][m] = function(cm) { - yankTillMark(cm,m); + yankTillMark(cm, m); }; CodeMirror.keyMap["vim-prefix-r"][m] = function (cm) { var cur = cm.getCursor(); @@ -574,8 +571,8 @@ setupPrefixBindingForKey("Space"); CodeMirror.keyMap["vim-prefix-y"] = { - "Y": countTimes(function(cm) { - pushInBuffer("\n"+cm.getLine(cm.getCursor().line+yank)); yank++; + "Y": countTimes(function(cm, i, last) { + pushInBuffer("\n" + cm.getLine(cm.getCursor().line + i)); cm.setOption("keyMap", "vim"); }), "'": function(cm) {cm.setOption("keyMap", "vim-prefix-y'"); emptyBuffer();}, From fe63c99c1c9f71c850aca1e04189bf979623273a Mon Sep 17 00:00:00 2001 From: Matt Sacks Date: Mon, 12 Nov 2012 09:23:11 -0800 Subject: [PATCH 130/135] Add text-objects ' and " Adds function findBeginningAndEnd to find identical symbols on the same line --- keymap/vim.js | 63 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 7cc8d67460..6f3372f2bb 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -651,6 +651,63 @@ return {start: start, end: end}; } + // takes in a symbol and a cursor and tries to simulate text objects that have + // identical opening and closing symbols + // TODO support across multiple lines + function findBeginningAndEnd(cm, symb, inclusive) { + var cur = cm.getCursor(); + var line = cm.getLine(cur.line); + var chars = line.split(''); + var start = undefined; + var end = undefined; + var firstIndex = chars.indexOf(symb); + + // the decision tree is to always look backwards for the beginning first, + // but if the cursor is in front of the first instance of the symb, + // then move the cursor forward + if (cur.ch < firstIndex) { + cur.ch = firstIndex; + cm.setCursor(cur.line, firstIndex+1); + } + // otherwise if the cursor is currently on the closing symbol + else if (firstIndex < cur.ch && chars[cur.ch] == symb) { + end = cur.ch; // assign end to the current cursor + --cur.ch; // make sure to look backwards + } + + // if we're currently on the symbol, we've got a start + if (chars[cur.ch] == symb && end == null) + start = cur.ch + 1; // assign start to ahead of the cursor + else { + // go backwards to find the start + for (var i = cur.ch; i > -1 && start == null; i--) + if (chars[i] == symb) start = i + 1; + } + + // look forwards for the end symbol + if (start != null && end == null) { + for (var i = start, len = chars.length; i < len && end == null; i++) { + if (chars[i] == symb) end = i; + } + } + + // nothing found + // FIXME still enters insert mode + if (start == null || end == null) return { + start: cur, end: cur + }; + + // include the symbols + if (inclusive) { + --start; ++end; + } + + return { + start: {line: cur.line, ch: start}, + end: {line: cur.line, ch: end} + }; + } + // These are our motion commands to be used for navigation and selection with // certian other commands. All should return a cursor object. var motionList = ['B', 'E', 'J', 'K', 'H', 'L', 'W', 'Shift-W', "'^'", "'$'", "'%'", 'Esc']; @@ -805,7 +862,7 @@ // Create our text object functions. They work similar to motions but they // return a start cursor as well - var textObjectList = ['W', 'Shift-[', 'Shift-9', '[']; + var textObjectList = ['W', 'Shift-[', 'Shift-9', '[', "'", "Shift-'"]; var textObjects = { 'W': function(cm, inclusive) { var cur = cm.getCursor(); @@ -820,7 +877,9 @@ }, 'Shift-[': function(cm, inclusive) { return selectCompanionObject(cm, '}', inclusive); }, 'Shift-9': function(cm, inclusive) { return selectCompanionObject(cm, ')', inclusive); }, - '[': function(cm, inclusive) { return selectCompanionObject(cm, ']', inclusive); } + '[': function(cm, inclusive) { return selectCompanionObject(cm, ']', inclusive); }, + "'": function(cm, inclusive) { return findBeginningAndEnd(cm, "'", inclusive); }, + "Shift-'": function(cm, inclusive) { return findBeginningAndEnd(cm, '"', inclusive); } }; // One function to handle all operation upon text objects. Kinda funky but it works From 505ab98007df1189faf06944419b69b87e2a1702 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 14 Nov 2012 08:11:20 +0100 Subject: [PATCH 131/135] Add Codepen to real-world uses --- doc/realworld.html | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/realworld.html b/doc/realworld.html index 5633e5e3d7..d58a4f73b3 100644 --- a/doc/realworld.html +++ b/doc/realworld.html @@ -27,6 +27,7 @@

    { } CodeMi
  • Cargo Collective (creative publishing platform)
  • Codebug (PHP Xdebug front-end)
  • CodeMirror2-GWT (Google Web Toolkit wrapper)
  • +
  • Codepen (gallery of animations)
  • Codev (collaborative IDE)
  • Collaborative CodeMirror demo (CodeMirror + operational transforms)
  • CKWNC (UML editor)
  • From 46d075dc7dd3b6dc487c666bdb7c48315913986a Mon Sep 17 00:00:00 2001 From: Jan Keromnes Date: Wed, 14 Nov 2012 08:32:39 +0800 Subject: [PATCH 132/135] [vim] refactor motions --- keymap/vim.js | 88 ++++++++++++++++++++------------------------------- 1 file changed, 34 insertions(+), 54 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 6f3372f2bb..afd5ec6e03 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -399,9 +399,6 @@ // main num keymap // Add bindings that are influenced by number keys iterObj({ - "Left": "goColumnLeft", "Right": "goColumnRight", - "Down": "goLineDown", "Up": "goLineUp", "Backspace": "goCharLeft", - "Space": "goCharRight", "X": function(cm) {CodeMirror.commands.delCharRight(cm);}, "P": function(cm) { var cur = cm.getCursor().line; @@ -708,67 +705,50 @@ }; } - // These are our motion commands to be used for navigation and selection with - // certian other commands. All should return a cursor object. - var motionList = ['B', 'E', 'J', 'K', 'H', 'L', 'W', 'Shift-W', "'^'", "'$'", "'%'", 'Esc']; - - motions = { - 'B': function(cm, times, yank) { return moveToWord(cm, word, -1, times, 'start', yank); }, - 'Shift-B': function(cm, times, yank) { return moveToWord(cm, bigWord, -1, times, 'start', yank); }, - 'E': function(cm, times, yank) { return moveToWord(cm, word, 1, times, 'end', yank); }, - 'Shift-E': function(cm, times, yank) { return moveToWord(cm, bigWord, 1, times, 'end', yank); }, - 'J': function(cm, times) { - var cur = cm.getCursor(); - return {line: cur.line+times, ch : cur.ch}; - }, - - 'K': function(cm, times) { - var cur = cm.getCursor(); - return {line: cur.line-times, ch: cur.ch}; - }, - - 'H': function(cm, times) { - var cur = cm.getCursor(); - return {line: cur.line, ch: cur.ch-times}; - }, + function offsetCursor(cm, line, ch) { + var cur = cm.getCursor(); return {line: cur.line + line, ch: cur.ch + ch}; + } - 'L': function(cm, times) { - var cur = cm.getCursor(); - return {line: cur.line, ch: cur.ch+times}; - }, - 'W': function(cm, times, yank) { return moveToWord(cm, word, 1, times, 'start', yank); }, - 'Shift-W': function(cm, times, yank) { return moveToWord(cm, bigWord, 1, times, 'start', yank); }, + // These are the motion commands we use for navigation and selection with + // certain other commands. All should return a cursor object. + var motions = { + "J": function(cm, times) { return offsetCursor(cm, times, 0); }, + "Down": function(cm, times) { return offsetCursor(cm, times, 0); }, + "K": function(cm, times) { return offsetCursor(cm, -times, 0); }, + "Up": function(cm, times) { return offsetCursor(cm, -times, 0); }, + "L": function(cm, times) { return offsetCursor(cm, 0, times); }, + "Right": function(cm, times) { return offsetCursor(cm, 0, times); }, + "Space": function(cm, times) { return offsetCursor(cm, 0, times); }, + "H": function(cm, times) { return offsetCursor(cm, 0, -times); }, + "Left": function(cm, times) { return offsetCursor(cm, 0, -times); }, + "Backspace": function(cm, times) { return offsetCursor(cm, 0, -times); }, + "B": function(cm, times, yank) { return moveToWord(cm, word, -1, times, 'start', yank); }, + "Shift-B": function(cm, times, yank) { return moveToWord(cm, bigWord, -1, times, 'start', yank); }, + "E": function(cm, times, yank) { return moveToWord(cm, word, 1, times, 'end', yank); }, + "Shift-E": function(cm, times, yank) { return moveToWord(cm, bigWord, 1, times, 'end', yank); }, + "W": function(cm, times, yank) { return moveToWord(cm, word, 1, times, 'start', yank); }, + "Shift-W": function(cm, times, yank) { return moveToWord(cm, bigWord, 1, times, 'start', yank); }, "'^'": function(cm, times) { - var cur = cm.getCursor(); - var line = cm.getLine(cur.line).split(''); - - // Empty line :o - if (line.length == 0) return cur; - - for (var index = 0; index < line.length; index++) { - if (line[index].match(/[^\s]/)) return {line: cur.line, ch: index}; + var cur = cm.getCursor(), line = cm.getLine(cur.line).split(''); + for (var i = 0; i < line.length; i++) { + if (line[i].match(/[^\s]/)) return {line: cur.line, ch: index}; } + return cur; }, "'$'": function(cm) { - var cur = cm.getCursor(); - var line = cm.getLine(cur.line); - return {line: cur.line, ch: line.length}; + var cur = cm.getCursor(), ch = cm.getLine(cur.line).length; + return {line: cur.line, ch: ch}; }, "'%'": function(cm) { return findMatchedSymbol(cm, cm.getCursor()); }, - "Esc" : function(cm) { - cm.setOption('vim'); - repeatCount = 0; - - return cm.getCursor(); - } + "Esc" : function(cm) { cm.setOption("keyMap", "vim"); repeatCount = 0; return cm.getCursor(); } }; // Map our movement actions each operator and non-operational movement - iterList(motionList, function(key, index, array) { + iterObj(motions, function(key, motion) { CodeMirror.keyMap['vim-prefix-d'][key] = function(cm) { // Get our selected range var start = cm.getCursor(); - var end = motions[key](cm, repeatCount ? repeatCount : 1, true); + var end = motion(cm, repeatCount ? repeatCount : 1, true); // Set swap var if range is of negative length if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true; @@ -784,7 +764,7 @@ CodeMirror.keyMap['vim-prefix-c'][key] = function(cm) { var start = cm.getCursor(); - var end = motions[key](cm, repeatCount ? repeatCount : 1, true); + var end = motion(cm, repeatCount ? repeatCount : 1, true); if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true; pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end)); @@ -796,7 +776,7 @@ CodeMirror.keyMap['vim-prefix-y'][key] = function(cm) { var start = cm.getCursor(); - var end = motions[key](cm, repeatCount ? repeatCount : 1, true); + var end = motion(cm, repeatCount ? repeatCount : 1, true); if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true; pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end)); @@ -806,7 +786,7 @@ }; CodeMirror.keyMap['vim'][key] = function(cm) { - var cur = motions[key](cm, repeatCount ? repeatCount : 1); + var cur = motion(cm, repeatCount ? repeatCount : 1); cm.setCursor(cur.line, cur.ch); repeatCount = 0; From 7f010298589acb6c6270b58f4152b5362addf1b0 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Wed, 14 Nov 2012 01:38:16 -0500 Subject: [PATCH 133/135] Fix off-by-one error for de. I accidentally introduced this fixing other off-by-one errors. Will seriously think about unit tests. --- keymap/vim.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/keymap/vim.js b/keymap/vim.js index afd5ec6e03..10eb590b15 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -199,6 +199,10 @@ } } } + if (where == 'end' && yank) { + // Include the last character of the word for actions. + cur.ch++; + } return cur; } function joinLineNext(cm) { From 38e68eddd9b7f3ac8c45eefcafb19c90a5155c94 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 14 Nov 2012 10:29:34 +0100 Subject: [PATCH 134/135] Don't verify that typeof form.submit is function before replacing it That's apparently not a reliable thing to test for. Issue #927 --- lib/codemirror.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 05e86c5375..571ea9c26c 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2236,15 +2236,13 @@ window.CodeMirror = (function() { if (textarea.form) { // Deplorable hack to make the submit method do the right thing. var rmSubmit = connect(textarea.form, "submit", save, true); - if (typeof textarea.form.submit == "function") { - var realSubmit = textarea.form.submit; - textarea.form.submit = function wrappedSubmit() { - save(); - textarea.form.submit = realSubmit; - textarea.form.submit(); - textarea.form.submit = wrappedSubmit; - }; - } + var realSubmit = textarea.form.submit; + textarea.form.submit = function wrappedSubmit() { + save(); + textarea.form.submit = realSubmit; + textarea.form.submit(); + textarea.form.submit = wrappedSubmit; + }; } textarea.style.display = "none"; From be52d3763c5f54c1996c5b2f76aea1978d448e86 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 14 Nov 2012 10:39:11 +0100 Subject: [PATCH 135/135] [htmlmixed mode] Fix typo bug --- mode/htmlmixed/htmlmixed.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/htmlmixed/htmlmixed.js b/mode/htmlmixed/htmlmixed.js index 4652848296..9a15360dbd 100644 --- a/mode/htmlmixed/htmlmixed.js +++ b/mode/htmlmixed/htmlmixed.js @@ -22,7 +22,7 @@ CodeMirror.defineMode("htmlmixed", function(config) { var close = cur.search(pat), m; if (close > -1) stream.backUp(cur.length - close); else if (m = cur.match(/<\/?$/)) { - stream.backUp(cur[0].length); + stream.backUp(cur.length); if (!stream.match(pat, false)) stream.match(cur[0]); } return style;