From f175e8d455717416cf3c1a54c831896bb37ef78c Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 29 Aug 2012 13:37:25 +0200 Subject: [PATCH 01/93] 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 02/93] 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 03/93] 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 04/93] 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 05/93] 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 06/93] 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 07/93] 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 08/93] 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 09/93] 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 10/93] 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 11/93] [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 12/93] 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 13/93] [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 14/93] 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 15/93] 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 16/93] 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 17/93] 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 18/93] [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 19/93] [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 20/93] [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 23/93] 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 24/93] 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 25/93] 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 26/93] 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 27/93] 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 28/93] 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 29/93] [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 30/93] [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 31/93] 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 32/93] 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 33/93] 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 34/93] 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 35/93] [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 36/93] 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 37/93] 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 38/93] 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 39/93] 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 40/93] 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 41/93] [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 42/93] [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 43/93] 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 44/93] 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 45/93] 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 46/93] 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 47/93] [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 48/93] [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 49/93] 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 50/93] 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 51/93] [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 52/93] [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 53/93] [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 54/93] 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 55/93] 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 56/93] [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 57/93] 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 77/93] [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 78/93] [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 79/93] [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 80/93] 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 81/93] [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 82/93] [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 83/93] 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 84/93] 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 85/93] 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 86/93] 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 87/93] 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 88/93] 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

    + + + + + + + + + + + + + + + + + + + + diff --git a/demo/activeline.html b/demo/activeline.html deleted file mode 100644 index a42ce97ba8..0000000000 --- a/demo/activeline.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - CodeMirror: Active Line Demo - - - - - - - - -

    CodeMirror: Active Line Demo

    - -
    - - - -

    Styling the current cursor line.

    - - - diff --git a/demo/changemode.html b/demo/changemode.html deleted file mode 100644 index a3d42c04bf..0000000000 --- a/demo/changemode.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - CodeMirror: Mode-Changing Demo - - - - - - - - - -

    CodeMirror: Mode-Changing demo

    - -
    - -

    On changes to the content of the above editor, a (crude) script -tries to auto-detect the language used, and switches the editor to -either JavaScript or Scheme mode based on that.

    - - - - diff --git a/demo/closetag.html b/demo/closetag.html deleted file mode 100644 index c405a3c322..0000000000 --- a/demo/closetag.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - CodeMirror: Close-Tag Demo - - - - - - - - - - - - -

    Close-Tag Demo

    -
      -
    • Type an html tag. When you type '>' or '/', the tag will auto-close/complete. Block-level tags will indent.
    • -
    • There are options for disabling tag closing or customizing the list of tags to indent.
    • -
    • Works with "text/html" (based on htmlmixed.js or xml.js) mode.
    • -
    • View source for key binding details.
    • -

      - -
      - - - - - diff --git a/demo/complete.html b/demo/complete.html deleted file mode 100644 index 3b522ec25e..0000000000 --- a/demo/complete.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - CodeMirror: Autocomplete Demo - - - - - - - - - - -

      CodeMirror: Autocomplete demo

      - -
      - -

      Press ctrl-space to activate autocompletion. See -the code (here -and here) to figure out -how it works.

      - - - - diff --git a/demo/emacs.html b/demo/emacs.html deleted file mode 100644 index b37a46b048..0000000000 --- a/demo/emacs.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - CodeMirror: Emacs bindings demo - - - - - - - - - -

      CodeMirror: Emacs bindings demo

      - -
      - -

      The emacs keybindings are enabled by -including keymap/emacs.js and setting -the keyMap option to "emacs". Because -CodeMirror's internal API is quite different from Emacs, they are only -a loose approximation of actual emacs bindings, though.

      - -

      Also note that a lot of browsers disallow certain keys from being -captured. For example, Chrome blocks both Ctrl-W and Ctrl-N, with the -result that idiomatic use of Emacs keys will constantly close your tab -or open a new window.

      - - - - - diff --git a/demo/folding.html b/demo/folding.html deleted file mode 100644 index 39a6a60e15..0000000000 --- a/demo/folding.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - - CodeMirror: Code Folding Demo - - - - - - - - - - -

      CodeMirror: Code Folding Demo

      - -

      Demonstration of code folding using the code - in foldcode.js. - Press ctrl-q or click on the gutter to fold a block, again - to unfold.

      -
      -
      JavaScript:
      -
      HTML:
      -
      - - - diff --git a/demo/formatting.html b/demo/formatting.html deleted file mode 100644 index b9e800d9cf..0000000000 --- a/demo/formatting.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - CodeMirror: Formatting Demo - - - - - - - - - - - - -

      CodeMirror: Formatting demo

      - -
      - -

      Select a piece of code and click one of the links below to apply automatic formatting to the selected text or comment/uncomment the selected text. Note that the formatting behavior depends on the current block's mode. - - - - - - -
      - - Autoformat Selected - - - - Comment Selected - - - - Uncomment Selected - -
      -

      - - - - diff --git a/demo/fullscreen.html b/demo/fullscreen.html deleted file mode 100644 index 69541674d7..0000000000 --- a/demo/fullscreen.html +++ /dev/null @@ -1,147 +0,0 @@ - - - - - CodeMirror: Full Screen Editing - - - - - - - - - -

      CodeMirror: Full Screen Editing

      - -
      - - -

      Press F11 when cursor is in the editor to toggle full screen editing. Esc can also be used to exit full screen editing.

      - - diff --git a/demo/loadmode.html b/demo/loadmode.html deleted file mode 100644 index 8813ea859b..0000000000 --- a/demo/loadmode.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - CodeMirror: Lazy Mode Loading Demo - - - - - - - - -

      CodeMirror: Lazy Mode Loading

      - -
      -

      - - - - diff --git a/demo/marker.html b/demo/marker.html deleted file mode 100644 index 7bc6c6defe..0000000000 --- a/demo/marker.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - CodeMirror: Breakpoint Demo - - - - - - - - -

      CodeMirror: Breakpoint demo

      - -
      - -

      Click the line-number gutter to add or remove 'breakpoints'.

      - - - - - diff --git a/demo/matchhighlighter.html b/demo/matchhighlighter.html deleted file mode 100644 index 5eef08d8de..0000000000 --- a/demo/matchhighlighter.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - CodeMirror: Match Highlighter Demo - - - - - - - - - -

      CodeMirror: Match Highlighter Demo

      - -
      - - - -

      Highlight matches of selected text on select

      - - - diff --git a/demo/multiplex.html b/demo/multiplex.html deleted file mode 100644 index 25fffd3294..0000000000 --- a/demo/multiplex.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - CodeMirror: Multiplexing Parser Demo - - - - - - - - - -

      CodeMirror: Multiplexing Parser Demo

      - -
      - - - -

      Demonstration of a multiplexing mode, which, at certain - boundary strings, switches to one or more inner modes. The out - (HTML) mode does not get fed the content of the << - >> blocks. See - the manual and - the source for more - information.

      - - - diff --git a/demo/mustache.html b/demo/mustache.html deleted file mode 100644 index c2ce331077..0000000000 --- a/demo/mustache.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - CodeMirror: Overlay Parser Demo - - - - - - - - - -

      CodeMirror: Overlay Parser Demo

      - -
      - - - -

      Demonstration of a mode that parses HTML, highlighting - the Mustache templating - directives inside of it by using the code - in overlay.js. View - source to see the 15 lines of code needed to accomplish this.

      - - - diff --git a/demo/preview.html b/demo/preview.html deleted file mode 100644 index f356891200..0000000000 --- a/demo/preview.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - - CodeMirror: HTML5 preview - - - - - - - - - - -

      CodeMirror: HTML5 preview

      - - - - - diff --git a/demo/resize.html b/demo/resize.html deleted file mode 100644 index f0c9750acf..0000000000 --- a/demo/resize.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - CodeMirror: Autoresize Demo - - - - - - - - -

      CodeMirror: Autoresize demo

      - -
      - -

      By setting a few CSS properties, CodeMirror can be made to -automatically resize to fit its content.

      - - - - - diff --git a/demo/runmode.html b/demo/runmode.html deleted file mode 100644 index 53ac04f917..0000000000 --- a/demo/runmode.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - CodeMirror: Mode Runner Demo - - - - - - - -

      CodeMirror: Mode Runner Demo

      - -
      - -
      
      -
      -    
      -
      -    

      Running a CodeMirror mode outside of the editor. - The CodeMirror.runMode function, defined - in lib/runmode.js takes the following arguments:

      - -
      -
      text (string)
      -
      The document to run through the highlighter.
      -
      mode (mode spec)
      -
      The mode to use (must be loaded as normal).
      -
      output (function or DOM node)
      -
      If this is a function, it will be called for each token with - two arguments, the token's text and the token's style class (may - be null for unstyled tokens). If it is a DOM node, - the tokens will be converted to span elements as in - an editor, and inserted into the node - (through innerHTML).
      -
      - - - diff --git a/demo/search.html b/demo/search.html deleted file mode 100644 index 219c80572e..0000000000 --- a/demo/search.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - CodeMirror: Search/Replace Demo - - - - - - - - - - - - -

      CodeMirror: Search/Replace Demo

      - -
      - - - -

      Demonstration of primitive search/replace functionality. The - keybindings (which can be overridden by custom keymaps) are:

      -
      -
      Ctrl-F / Cmd-F
      Start searching
      -
      Ctrl-G / Cmd-G
      Find next
      -
      Shift-Ctrl-G / Shift-Cmd-G
      Find previous
      -
      Shift-Ctrl-F / Cmd-Option-F
      Replace
      -
      Shift-Ctrl-R / Shift-Cmd-Option-F
      Replace all
      -
      -

      Searching is enabled by - including lib/util/search.js - and lib/util/searchcursor.js. - For good-looking input dialogs, you also want to include - lib/util/dialog.js - and lib/util/dialog.css.

      - - diff --git a/demo/theme.html b/demo/theme.html deleted file mode 100644 index f8471202fa..0000000000 --- a/demo/theme.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - CodeMirror: Theme Demo - - - - - - - - - - - - - - - - - - - - - -

      CodeMirror: Theme demo

      - -
      - -

      Select a theme: -

      - - - - diff --git a/demo/vim.html b/demo/vim.html deleted file mode 100644 index 6813cb37fb..0000000000 --- a/demo/vim.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - CodeMirror: Vim bindings demo - - - - - - - - - - - -

      CodeMirror: Vim bindings demo

      - -
      - -

      The vim keybindings are enabled by -including keymap/vim.js and setting -the keyMap option to "vim". Because -CodeMirror's internal API is quite different from Vim, they are only -a loose approximation of actual vim bindings, though.

      - - - - - diff --git a/demo/visibletabs.html b/demo/visibletabs.html deleted file mode 100644 index ddc12fb46a..0000000000 --- a/demo/visibletabs.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - CodeMirror: Visible tabs demo - - - - - - - - -

      CodeMirror: Visible tabs demo

      - -
      - -

      Tabs inside the editor are spans with the -class cm-tab, and can be styled. - - - - - diff --git a/demo/xmlcomplete.html b/demo/xmlcomplete.html deleted file mode 100644 index baa9aa22f1..0000000000 --- a/demo/xmlcomplete.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - CodeMirror: XML Autocomplete Demo - - - - - - - - - - - -

      CodeMirror: XML Autocomplete demo

      - -
      - -

      Type '<' or space inside tag or - press ctrl-space to activate autocompletion. See - the code (here - and here) to figure out how - it works.

      - - - - diff --git a/doc/baboon.png b/doc/baboon.png deleted file mode 100644 index 55d97f70b817ff2b78ebb409bf34a5147fe40d07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23299 zcmXtA2RM~|*nf^qWM`JWx9se!jBH8STe3ywi4vlWP|C$=|2+k1}Z{KtL&)_oF8uIf@zuu>ohLUlz?%M3wK;qdQRG7|WEstIZd{y`L| zdBuVZenpVE#KZ5&{q(K}A_$=c{x4Jtp~C?DN0uOM>mYMqx1bQm09Pa=Bt+cP=T@Mz zqo1p|Z-D!Yb!Aor;Y6-zX;_55T+IoIV6Yth6df`|pO&Jo_lj4Y`xQxw`h63kq1gdQ2wV+9`BOW&{MU9$qEjO0G3}ueiaHZu{OP#N93A zj(hr--M@--A-;+i12@JmElh@s^Kc6?Ka}c5EC_LD^53!yzKpLNvkN39CVucnp|=A4 zQnDM1`awivv9(bXJxWAFh8d6X$|f*LYK@7BDYx)&?q?1pqM^ulOt`Nxm4B`79;*@R z$SXFC0e&T!aV4oZe<4&SRF++~At%Q17J4DywdM~P^*fl$&f;7VQ?2dC*aJbqvBUt^ zRTk$qg!NUqX%!*5rzBe_ySGB!^|i~lxw}Z!Q$1dU5zP~@T%7N4YxU>Y#qJ|i5YhKi zE!N)BC(rLocVpuPjeWF*&m>j9MVv4!yFSPf6>qb*c`*ZnOLrwag(z~+t#b2fB(Em% z`t8}L*hC8#W@p+^rLbren$Y-M*O-Wfu#4k+GASE=ohI|ZX_fDR z)I4#%h{DX|d2CTLGlO%LN$I=NY6<_oplt;PllQ%E zFXlG6FY8$!k#~=IBA@Cswd(mq?KL_o-z8SNCq0O{xL%F3QXAu|yEUp~VZopjw1}Sn z`l2xE_@KnDRq5r+PHN^$!OJeaV@SU=(y(;YOUw`l zx;;U#Pye5Y;21*>tYFFm-bM&|RG$cmm%2l4oP0`-EX0@SkxVc! zh{jx}V|ysIqn!H~=lZYxl9Cd)s3_%mr6BcR|J@#_^)&xcYEG)Es%lc&dG~Q*Vxr&L zPuZit{b~(<(*)vnE%6^dXnx7L#Pn}@_~NsLZ#kDr%s&-nO>1-il+aJrCvjf2MNaRz zP79SAT|g=h7l;&g`;D&4nwij`@u&F=sU#gn9qAY`NqmCCIkfEjyyY=kd7f3?C+TP1 zt&e?uXRs$M(q~&%kS1qGjd zD5;hhcbg=~2>L~ZhTIUGU0;99WFJ9GNJ6O|6eJT98%umR@^@=1<=L~6|NF3P@;>#508_2O(1z5!-ShcT%XXO zIS@m>0)YUL!hDP_3x1_ZBndEnNN`03y1iX3?BDVOyVkH*mSt9(J3DbTH5aQ4=9h=d z6z;4ti{EU>zql;#J1O(4f zPq;Thl>g+Wk_G1vWSK5?;432q#SRext z$4{R~b8~a~|1vN#a&d8?9-civctu}7vCcx~ik=>sRh3=xowc7C`#x}>7AISqOIL&E za=(b&9XUkRW|}{t_@K-kX79ttz;GXfh$51d=;`UV#0r-#%{*YT!<1FECAxjheswiR zsl@!vS!HEq!}-3Ro{nngPeF0VW7W=|zkSQJQL$roRvI-53sV{#8hT|_HjTrfpQxQM zUv&R=zi?(~xJ=#$tA2d&&xmoKmG*W`<;am_w-=&7!q(Dg=@)?9r%4@1U?nCF@1RvD z;+5W>oSckLOw@#3WR>;0|L&cnjK_E6+qc)(22~>VOniSt&3|EGVC-psswgAFkQaTT zc(A)s6(}+{KhLeAa^BO^Q_sKvUv`3)2nh-I6TKJNYFxAjK7z$8E3`sVt@h51r z1BsyUNi&p*ii(O)O453CPV9S$DUb5$(O-}6Xtm7o;j(N0k`6OOJ{YeB1_j-Roel^L zOwGwjhFcnF!K4lASsk$-&=$Onpx;`SabLVRG&Z^t@A0mvH=&3(qRSq4_KxYhm!e3A z)>E!}ZVHOm4#-cGLT8pqu~`S4a;P^RBjqf1q0_{*w?@yNoWyB9Q0tH(oEosLrG&1WC^ zk8y5xmWE?|e7x4Z|H1*(qtaWWl@`VNjOlZ%X(c6sHjM#%b8~ZlM%)v>d@=l4?M#|{ zK_weLGQSP=<-vMidtpIANt*?z?cT!CT!=e&Oa-yJGeTX|K$`e8)dI1c4XsQRAJO{Dy&rT~eOv^uS-K)qe<4%mHU01_|Mc72dyp|2tIF9xE3&x51(->7oaYVSm5MQ)Nc(Z@IR$T>hL% z{VZ_Eb?ly0`QjFWE)&bE_`mnNNoJRx{9H;0ic%4ug2LGEvjt|Qi>uYmC>4X%Hp!Jo&IMat>E_W6$k1F3Ljjdn1zJ?UTD9&fatwi(`CUtfQ{ z)tM@GLG(MJdfRzsOfPReN+AP#I)b#3rigOd3!?*(JNM3zT|3a7CKgnqNYvvrK1Hnl z%JvRcKFScemOUW^RnG&K&P4*&#WXau;ri(B#YMe@goLm;XnDOBxi?(a9#T*uCJv{Rq$+#Jp6AwSx#L1 z@oF-U?&B2wV(FDIySsCYt?yqOKL3#Q=|%ZJ^~MEj7g8N#+I?O|xm-50&Xc(%j+^n< zC*y)PCRGOMSXqUexWYn1P5k|(#(A7SJ(g{bUHe&QaP8Z{zm@&LdY)U#hPc~E*Bu9M zhSY281b#I-Z01EriR)wn+)ae&5RsL($itxM_PyVaB6|8#Uc7kp@+JGgzIVr)yr|v~ zB*Aq4TTaTu@54y0gbYY`D-k>sbA3*6 zQy~TJL~li$btS>x6{x7I)Gye=k-g*Hsjt26XWYI#yC-bqXO@HT}GZE%MvH{!6}~8lk79)zi}>6MYf^s3*-t%NOZ=xM=QeEfacgACj&9N66#M z^c#m$hZ-v5gHly)K%| z?%(f_Fp-Q3-n)0NA^KDm;gpqSl=b?7shQiPJ$H^@_;G8+6(b{UKR-z*^>>FV?b|N^ zp7?$!3Vl_~rdBdz?K1bl6IYu6$!>8JPEAkU4e%0Nta9$k%E}VUvU4_dUd17FHg0bG zXE{`p_Vz+MKYf~kn=GeD*m1UjAYa74Y?Xn5k~N$OSwgUet}UT|xLM`A$q?uS&wT>S zD8qMFHL}EQwHE060C~TQK8<2m@Iy$cFLb607~@t~%P%s_bSHnjPfp)E`$F24qV0I+ z#^e_C{6uJ1E0LMGxo4qVnvX~E7szYbC2BA`DX|wI28O|1NwS8XC&yy5sLZ zaOuN{b{V~&Wekt?=2 zr0Uxk{+_w996mV1WHT`)as54!Z|lFz9vR&y7i2wF)%I|NqINCJUUffRPT<^T?U#EE zzxDI;L+cbuWg4Z;WhGU6rHp@X2Xty(<3Fwi zT8is36*>8T(eMX)0woSK;q}Fbgx|3*!zD6WPEQWQ zs*icNxo3B-iI`bkKzVq2-Y_qUcr;Dj_)*+YjuRGiqVt-KP4>9I>uck@tC~Guz7$=4 zcy{1-*CWq$Y@=&i^m`|jiPGUTif{IVk(sHf8g_PeKgzC2*t~Zk=i}oeuY3|OC?s^n z1bf(ia!_Vyhf3A>NHq~HMUel7oum^=e$?LdL+Md=dU~FENAu2iE*O1BB?ci5r@hNQ zHN(zrBtZw;o`Zw$T@7kjU*+X-%q=XWOW{v??^7WLI=X_a{+^x$X=&;B_!0$)JhQ1> zjVokDyhMYyPu(p1{v=i=0c0thJHsU>m$|D1B=iy z{r&xejt_!?Y0;cl4A2Vs`Z10w{a1;Q)6MrCMn8LfLK&)Cm78PFA?jLP$PUXvrHt9? z>&WdzS~>4Ahs|c$TO*vx%ItsVzkdAsLjGTyh^S~M5J3dlCw(&0Q!v-GAtUr;)8n(b zT5Bb-9GlC8pw!L@{YX3}5$fQP&5_W&?(-V$Q z|K0xOUGX@^PZ{Zy>JICzh>uy5@>n&XVN`k*xbV$Q503j^TpM>n}0)&j>a}_Iu~(54GG4oRkS!e`-5@j*jj_R>e)$!vby} z`KbO}75`0~#*gyr^oP3}x}%l0ea@lxdgIQZza~{{1c($kI44oFt$1G#rIPZ|(`tevSX@;y~fj>UfPy%5SHpB~$x_?g9Og-;5;b9I6q|M#`@{4@O#K zKcwc^vbzkGw|7=VSeU!}S!u+mXrwHmALeDaPif2UWO!4;_}Z`f^s=%ttT=^sX95XE z(BM}d_nfX_G&y$m7XIGxiNRt@l}&ebcbj?MSsiIFqMN#*HMTq@?iFAdZ^j@!y61!W;EIEw0P~ zbF&*NM7_h*E4E>Ohkz~gej#R#?axS0O?C9EzYRFV$kiN$ql)sS>}Do3>BAm5{n_5` zge~6O`Q=N9K-+zAkC4^qpvPqtBvDx(I^hfJzrw6#u z-*HJQb7iT!qg<{1_@^*d-6xugIuyg-dEOmm#4#%THd(@8t?yK>#0$QtLj<{cZkEXq z5QhKacP@Y+=aKsBw>#r$U&|#6J*ECBcyg#8Q|hROX2mJae;XdQTsZvJ(<8N#aZ)#G z$5ZW*W$V>rcY1?G{wf`TQ1H>if<8I?gNIf*pPn4MZUNhmmzb(|nZLz?9&y^JQ``bX zJp`pNcvQyy8++pfb)ufg6_c~VRhv!$F2-8asAoyBDS3HM3U!|lAlQisVlnfGb5hj? zTU%Rrinq77mv8>&DiAFaMBxFOYCDg-PrbwW+A>4RflAv;es=UT%&g<1W!Iimc^T;J zSlK5GRosw=zQY3|4zfA!o;UrGTCMg@9mRiqM40dZkyj43dsqcD)lEgGq+=lZ)gc>p zG_+^g70{rx5FjrzwgVpZ#o;3t<`}GR|`EOA|gWjGh0KdR?Upn z)YQ`NI(+I@<>n>;ZhgP`5>uEv6r^Xew=!kR%4HN36h2pcsQKcMWSMn?oOp$+bL>WB znpF~rJ6xQc2rv{f3W~wo&zyWJ{mRXU9CCkrjrN$h;5t_0PHpp_cLG2MM}Rc^E5Hrp z_FG@2wiXuhc#X)EUn7@gc9u0f=gnTma)?GF-oO5%vV3cy0r0W=35SFrK}n1xtBePy z3#WrY3o1gqo{qxmet$oq&!{cRaPR2l%a?+eF1?6vnAz{FY>c{-)7r=z!shhdmxE(4 zP4n_)&+WwlnaG2n12~CGrzabyCEAb9g@uKNg_+semk&FpH!3_jI@)w8dHP3w+rWn1olVIDTTjEr01!&Xc_pl^KH4ua?uUflP<70Qo(j|^w;=NuX(A( z9$Y8?-F4eZ_iHQz9x~SN98e(CbvF+hkij945?~|TWTSl$^Ma6-BtNyQS8$9&Gwu|3 zwYE1VVN@+cUmL)ymX?Kyi8_#~82j_lr^g|`XK)>{c*Gd8jurZA>*XbUyb*m`_`NB3 zN%-PwcjG;Eil8cPy*#)dti#R&1==n9pB|qt%hIi|`a9R7(?gp&PlkT3xlt#T@c1!5 z?8g%hwb!3N>phx4`S|$M-u>&w^_MZj=BBN!kfP%A$%wy3S9+HRiytZ8ohhG|GwE#8 z3e|LAvo9Qf?|R0QHZZ>H&&;QUCq+fP1PzxjhV8B!nZGuCWvq7mR4I75NWQ18*yl`P z)S*1PveEMN@86n8Nyx^e&Oe2a^_Tg#K<;BN9rm(Sh3na-{O@a2ygIc(l2&WjKWIm~ zPhY+?GpY7|Vjh4$cC$KnYe7rRu^NR`!(gd-N)Q+qrvmOp_ilw&&JqIqyjlOvB z{P}Z|f8RZ1W>;?f$9`EF)ID2Olv-3P{&|c7lP?dywG@()O7)p)`x=r|fZMwIQb7ZH zERVdrd=I6Q_Bmf)Uws3EAu2i147hy>cDyKux7cny+Lc6;JQWAN_SV?gSkAoG^mKZ! zA2|ERQIadw0~GmC@m0MkFOIEhavm3?$D&`gGw8it6g>mM{bZA3G@e zVO(bN#9vA`4)v>AyO^Ad!W6o?y586Z+N)QudQ7#cRNici0r?5@>C-1s6)1b}KpWB0 z(QyK?<#S)(On(s?v=BT|DY2j0BOsKD*y<6mZVD0toXy4mOYe&CwRiTNN76_rl1r

      #6335mO-CwW+nmUD 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 diff --git a/doc/baboon_vector.svg b/doc/baboon_vector.svg deleted file mode 100644 index dc1667af91..0000000000 --- a/doc/baboon_vector.svg +++ /dev/null @@ -1,153 +0,0 @@ - - - -image/svg+xml \ No newline at end of file diff --git a/doc/compress.html b/doc/compress.html deleted file mode 100644 index a0fcb5796c..0000000000 --- a/doc/compress.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - CodeMirror: Compression Helper - - - - - -

      { } CodeMirror

      - -
      - -
      -/* Script compression
      -   helper */
      -
      -
      - -

      To optimize loading CodeMirror, especially when including a - bunch of different modes, it is recommended that you combine and - minify (and preferably also gzip) the scripts. This page makes - those first two steps very easy. Simply select the version and - scripts you need in the form below, and - click Compress to download the minified script - file.

      - -
      - -

      Version:

      - -

      - -

      - with UglifyJS -

      - -

      Custom code to add to the compressed file:

      -
      - - - - - diff --git a/doc/docs.css b/doc/docs.css deleted file mode 100644 index f48e3b72a6..0000000000 --- a/doc/docs.css +++ /dev/null @@ -1,165 +0,0 @@ -body { - font-family: Droid Sans, Arial, sans-serif; - line-height: 1.5; - max-width: 64.3em; - margin: 3em auto; - padding: 0 1em; -} - -h1 { - letter-spacing: -3px; - font-size: 3.23em; - font-weight: bold; - margin: 0; -} - -h2 { - font-size: 1.23em; - font-weight: bold; - margin: .5em 0; - letter-spacing: -1px; -} - -h3 { - font-size: 1em; - font-weight: bold; - margin: .4em 0; -} - -pre { - background-color: #eee; - -moz-border-radius: 6px; - -webkit-border-radius: 6px; - border-radius: 6px; - padding: 1em; -} - -pre.code { - margin: 0 1em; -} - -.grey { - background-color: #eee; - border-radius: 6px; - margin-bottom: 1.65em; - margin-top: 0.825em; - padding: 0.825em 1.65em; - position: relative; -} - -img.logo { - position: absolute; - right: -1em; - bottom: 4px; - max-width: 23.6875em; /* Scale image down with text to prevent clipping */ -} - -.grey > pre { - background:none; - border-radius:0; - padding:0; - margin:0; - font-size:2.2em; - line-height:1.2em; -} - -a:link, a:visited, .quasilink { - color: #df0019; - cursor: pointer; - text-decoration: none; -} - -a:hover, .quasilink:hover { - color: #800004; -} - -h1 a:link, h1 a:visited, h1 a:hover { - color: black; -} - -ul { - margin: 0; - padding-left: 1.2em; -} - -a.download { - color: white; - background-color: #df0019; - width: 100%; - display: block; - text-align: center; - font-size: 1.23em; - font-weight: bold; - text-decoration: none; - -moz-border-radius: 6px; - -webkit-border-radius: 6px; - border-radius: 6px; - padding: .5em 0; - margin-bottom: 1em; -} - -a.download:hover { - background-color: #bb0010; -} - -.rel { - margin-bottom: 0; -} - -.rel-note { - color: #777; - font-size: .9em; - margin-top: .1em; -} - -.logo-braces { - color: #df0019; - position: relative; - top: -4px; -} - -.blk { - float: left; -} - -.left { - width: 37em; - padding-right: 6.53em; - padding-bottom: 1em; -} - -.left1 { - width: 15.24em; - padding-right: 6.45em; -} - -.left2 { - width: 15.24em; -} - -.right { - width: 20.68em; -} - -.leftbig { - width: 42.44em; - padding-right: 6.53em; -} - -.rightsmall { - width: 15.24em; -} - -.clear:after { - visibility: hidden; - display: block; - font-size: 0; - content: " "; - clear: both; - height: 0; -} -.clear { display: inline-block; } -/* start commented backslash hack \*/ -* html .clear { height: 1%; } -.clear { display: block; } -/* close commented backslash hack */ diff --git a/doc/internals.html b/doc/internals.html deleted file mode 100644 index d859419864..0000000000 --- a/doc/internals.html +++ /dev/null @@ -1,497 +0,0 @@ - - - - - CodeMirror: Internals - - - - - - -

      { } CodeMirror

      - -
      - -
      -/* (Re-) Implementing A Syntax-
      -   Highlighting Editor in JavaScript */
      -
      -
      - -
      - -

      - Topic: JavaScript, code editor implementation
      - Author: Marijn Haverbeke
      - Date: March 2nd 2011 (updated November 13th 2011) -

      - -

      This is a followup to -my Brutal Odyssey to the -Dark Side of the DOM Tree story. That one describes the -mind-bending process of implementing (what would become) CodeMirror 1. -This one describes the internals of CodeMirror 2, a complete rewrite -and rethink of the old code base. I wanted to give this piece another -Hunter Thompson copycat subtitle, but somehow that would be out of -place—the process this time around was one of straightforward -engineering, requiring no serious mind-bending whatsoever.

      - -

      So, what is wrong with CodeMirror 1? I'd estimate, by mailing list -activity and general search-engine presence, that it has been -integrated into about a thousand systems by now. The most prominent -one, since a few weeks, -being Google -code's project hosting. It works, and it's being used widely. - -

      Still, I did not start replacing it because I was bored. CodeMirror -1 was heavily reliant on designMode -or contentEditable (depending on the browser). Neither of -these are well specified (HTML5 tries -to specify -their basics), and, more importantly, they tend to be one of the more -obscure and buggy areas of browser functionality—CodeMirror, by using -this functionality in a non-typical way, was constantly running up -against browser bugs. WebKit wouldn't show an empty line at the end of -the document, and in some releases would suddenly get unbearably slow. -Firefox would show the cursor in the wrong place. Internet Explorer -would insist on linkifying everything that looked like a URL or email -address, a behaviour that can't be turned off. Some bugs I managed to -work around (which was often a frustrating, painful process), others, -such as the Firefox cursor placement, I gave up on, and had to tell -user after user that they were known problems, but not something I -could help.

      - -

      Also, there is the fact that designMode (which seemed -to be less buggy than contentEditable in Webkit and -Firefox, and was thus used by CodeMirror 1 in those browsers) requires -a frame. Frames are another tricky area. It takes some effort to -prevent getting tripped up by domain restrictions, they don't -initialize synchronously, behave strangely in response to the back -button, and, on several browsers, can't be moved around the DOM -without having them re-initialize. They did provide a very nice way to -namespace the library, though—CodeMirror 1 could freely pollute the -namespace inside the frame.

      - -

      Finally, working with an editable document means working with -selection in arbitrary DOM structures. Internet Explorer (8 and -before) has an utterly different (and awkward) selection API than all -of the other browsers, and even among the different implementations of -document.selection, details about how exactly a selection -is represented vary quite a bit. Add to that the fact that Opera's -selection support tended to be very buggy until recently, and you can -imagine why CodeMirror 1 contains 700 lines of selection-handling -code.

      - -

      And that brings us to the main issue with the CodeMirror 1 -code base: The proportion of browser-bug-workarounds to real -application code was getting dangerously high. By building on top of a -few dodgy features, I put the system in a vulnerable position—any -incompatibility and bugginess in these features, I had to paper over -with my own code. Not only did I have to do some serious stunt-work to -get it to work on older browsers (as detailed in the -previous story), things -also kept breaking in newly released versions, requiring me to come up -with new scary hacks in order to keep up. This was starting -to lose its appeal.

      - -

      General Approach

      - -

      What CodeMirror 2 does is try to sidestep most of the hairy hacks -that came up in version 1. I owe a lot to the -ACE editor for inspiration on how to -approach this.

      - -

      I absolutely did not want to be completely reliant on key events to -generate my input. Every JavaScript programmer knows that key event -information is horrible and incomplete. Some people (most awesomely -Mihai Bazon with Ymacs) have been able -to build more or less functioning editors by directly reading key -events, but it takes a lot of work (the kind of never-ending, fragile -work I described earlier), and will never be able to properly support -things like multi-keystoke international character -input. [see below for caveat]

      - -

      So what I do is focus a hidden textarea, and let the browser -believe that the user is typing into that. What we show to the user is -a DOM structure we built to represent his document. If this is updated -quickly enough, and shows some kind of believable cursor, it feels -like a real text-input control.

      - -

      Another big win is that this DOM representation does not have to -span the whole document. Some CodeMirror 1 users insisted that they -needed to put a 30 thousand line XML document into CodeMirror. Putting -all that into the DOM takes a while, especially since, for some -reason, an editable DOM tree is slower than a normal one on most -browsers. If we have full control over what we show, we must only -ensure that the visible part of the document has been added, and can -do the rest only when needed. (Fortunately, the onscroll -event works almost the same on all browsers, and lends itself well to -displaying things only as they are scrolled into view.)

      - -

      Input

      - -

      ACE uses its hidden textarea only as a text input shim, and does -all cursor movement and things like text deletion itself by directly -handling key events. CodeMirror's way is to let the browser do its -thing as much as possible, and not, for example, define its own set of -key bindings. One way to do this would have been to have the whole -document inside the hidden textarea, and after each key event update -the display DOM to reflect what's in that textarea.

      - -

      That'd be simple, but it is not realistic. For even medium-sized -document the editor would be constantly munging huge strings, and get -terribly slow. What CodeMirror 2 does is put the current selection, -along with an extra line on the top and on the bottom, into the -textarea.

      - -

      This means that the arrow keys (and their ctrl-variations), home, -end, etcetera, do not have to be handled specially. We just read the -cursor position in the textarea, and update our cursor to match it. -Also, copy and paste work pretty much for free, and people get their -native key bindings, without any special work on my part. For example, -I have emacs key bindings configured for Chrome and Firefox. There is -no way for a script to detect this. [no longer the case]

      - -

      Of course, since only a small part of the document sits in the -textarea, keys like page up and ctrl-end won't do the right thing. -CodeMirror is catching those events and handling them itself.

      - -

      Selection

      - -

      Getting and setting the selection range of a textarea in modern -browsers is trivial—you just use the selectionStart -and selectionEnd properties. On IE you have to do some -insane stuff with temporary ranges and compensating for the fact that -moving the selection by a 'character' will treat \r\n as a single -character, but even there it is possible to build functions that -reliably set and get the selection range.

      - -

      But consider this typical case: When I'm somewhere in my document, -press shift, and press the up arrow, something gets selected. Then, if -I, still holding shift, press the up arrow again, the top of my -selection is adjusted. The selection remembers where its head -and its anchor are, and moves the head when we shift-move. -This is a generally accepted property of selections, and done right by -every editing component built in the past twenty years.

      - -

      But not something that the browser selection APIs expose.

      - -

      Great. So when someone creates an 'upside-down' selection, the next -time CodeMirror has to update the textarea, it'll re-create the -selection as an 'upside-up' selection, with the anchor at the top, and -the next cursor motion will behave in an unexpected way—our second -up-arrow press in the example above will not do anything, since it is -interpreted in exactly the same way as the first.

      - -

      No problem. We'll just, ehm, detect that the selection is -upside-down (you can tell by the way it was created), and then, when -an upside-down selection is present, and a cursor-moving key is -pressed in combination with shift, we quickly collapse the selection -in the textarea to its start, allow the key to take effect, and then -combine its new head with its old anchor to get the real -selection.

      - -

      In short, scary hacks could not be avoided entirely in CodeMirror -2.

      - -

      And, the observant reader might ask, how do you even know that a -key combo is a cursor-moving combo, if you claim you support any -native key bindings? Well, we don't, but we can learn. The editor -keeps a set known cursor-movement combos (initialized to the -predictable defaults), and updates this set when it observes that -pressing a certain key had (only) the effect of moving the cursor. -This, of course, doesn't work if the first time the key is used was -for extending an inverted selection, but it works most of the -time.

      - -

      Intelligent Updating

      - -

      One thing that always comes up when you have a complicated internal -state that's reflected in some user-visible external representation -(in this case, the displayed code and the textarea's content) is -keeping the two in sync. The naive way is to just update the display -every time you change your state, but this is not only error prone -(you'll forget), it also easily leads to duplicate work on big, -composite operations. Then you start passing around flags indicating -whether the display should be updated in an attempt to be efficient -again and, well, at that point you might as well give up completely.

      - -

      I did go down that road, but then switched to a much simpler model: -simply keep track of all the things that have been changed during an -action, and then, only at the end, use this information to update the -user-visible display.

      - -

      CodeMirror uses a concept of operations, which start by -calling a specific set-up function that clears the state and end by -calling another function that reads this state and does the required -updating. Most event handlers, and all the user-visible methods that -change state are wrapped like this. There's a method -called operation that accepts a function, and returns -another function that wraps the given function as an operation.

      - -

      It's trivial to extend this (as CodeMirror does) to detect nesting, -and, when an operation is started inside an operation, simply -increment the nesting count, and only do the updating when this count -reaches zero again.

      - -

      If we have a set of changed ranges and know the currently shown -range, we can (with some awkward code to deal with the fact that -changes can add and remove lines, so we're dealing with a changing -coordinate system) construct a map of the ranges that were left -intact. We can then compare this map with the part of the document -that's currently visible (based on scroll offset and editor height) to -determine whether something needs to be updated.

      - -

      CodeMirror uses two update algorithms—a full refresh, where it just -discards the whole part of the DOM that contains the edited text and -rebuilds it, and a patch algorithm, where it uses the information -about changed and intact ranges to update only the out-of-date parts -of the DOM. When more than 30 percent (which is the current heuristic, -might change) of the lines need to be updated, the full refresh is -chosen (since it's faster to do than painstakingly finding and -updating all the changed lines), in the other case it does the -patching (so that, if you scroll a line or select another character, -the whole screen doesn't have to be -re-rendered). [the full-refresh -algorithm was dropped, it wasn't really faster than the patching -one]

      - -

      All updating uses innerHTML rather than direct DOM -manipulation, since that still seems to be by far the fastest way to -build documents. There's a per-line function that combines the -highlighting, marking, and -selection info for that line into a snippet of HTML. The patch updater -uses this to reset individual lines, the refresh updater builds an -HTML chunk for the whole visible document at once, and then uses a -single innerHTML update to do the refresh.

      - -

      Parsers can be Simple

      - -

      When I wrote CodeMirror 1, I -thought interruptable -parsers were a hugely scary and complicated thing, and I used a -bunch of heavyweight abstractions to keep this supposed complexity -under control: parsers -were iterators -that consumed input from another iterator, and used funny -closure-resetting tricks to copy and resume themselves.

      - -

      This made for a rather nice system, in that parsers formed strictly -separate modules, and could be composed in predictable ways. -Unfortunately, it was quite slow (stacking three or four iterators on -top of each other), and extremely intimidating to people not used to a -functional programming style.

      - -

      With a few small changes, however, we can keep all those -advantages, but simplify the API and make the whole thing less -indirect and inefficient. CodeMirror -2's mode API uses explicit state -objects, and makes the parser/tokenizer a function that simply takes a -state and a character stream abstraction, advances the stream one -token, and returns the way the token should be styled. This state may -be copied, optionally in a mode-defined way, in order to be able to -continue a parse at a given point. Even someone who's never touched a -lambda in his life can understand this approach. Additionally, far -fewer objects are allocated in the course of parsing now.

      - -

      The biggest speedup comes from the fact that the parsing no longer -has to touch the DOM though. In CodeMirror 1, on an older browser, you -could see the parser work its way through the document, -managing some twenty lines in each 50-millisecond time slice it got. It -was reading its input from the DOM, and updating the DOM as it went -along, which any experienced JavaScript programmer will immediately -spot as a recipe for slowness. In CodeMirror 2, the parser usually -finishes the whole document in a single 100-millisecond time slice—it -manages some 1500 lines during that time on Chrome. All it has to do -is munge strings, so there is no real reason for it to be slow -anymore.

      - -

      What Gives?

      - -

      Given all this, what can you expect from CodeMirror 2?

      - -
        - -
      • Small. the base library is -some 45k when minified -now, 17k when gzipped. It's smaller than -its own logo.
      • - -
      • Lightweight. CodeMirror 2 initializes very -quickly, and does almost no work when it is not focused. This means -you can treat it almost like a textarea, have multiple instances on a -page without trouble.
      • - -
      • Huge document support. Since highlighting is -really fast, and no DOM structure is being built for non-visible -content, you don't have to worry about locking up your browser when a -user enters a megabyte-sized document.
      • - -
      • Extended API. Some things kept coming up in the -mailing list, such as marking pieces of text or lines, which were -extremely hard to do with CodeMirror 1. The new version has proper -support for these built in.
      • - -
      • Tab support. Tabs inside editable documents were, -for some reason, a no-go. At least six different people announced they -were going to add tab support to CodeMirror 1, none survived (I mean, -none delivered a working version). CodeMirror 2 no longer removes tabs -from your document.
      • - -
      • Sane styling. iframe nodes aren't -really known for respecting document flow. Now that an editor instance -is a plain div element, it is much easier to size it to -fit the surrounding elements. You don't even have to make it scroll if -you do not want to.
      • - -
      - -

      On the downside, a CodeMirror 2 instance is not a native -editable component. Though it does its best to emulate such a -component as much as possible, there is functionality that browsers -just do not allow us to hook into. Doing select-all from the context -menu, for example, is not currently detected by CodeMirror.

      - -

      [Updates from November 13th 2011] Recently, I've made -some changes to the codebase that cause some of the text above to no -longer be current. I've left the text intact, but added markers at the -passages that are now inaccurate. The new situation is described -below.

      - -

      Content Representation

      - -

      The original implementation of CodeMirror 2 represented the -document as a flat array of line objects. This worked well—splicing -arrays will require the part of the array after the splice to be -moved, but this is basically just a simple memmove of a -bunch of pointers, so it is cheap even for huge documents.

      - -

      However, I recently added line wrapping and code folding (line -collapsing, basically). Once lines start taking up a non-constant -amount of vertical space, looking up a line by vertical position -(which is needed when someone clicks the document, and to determine -the visible part of the document during scrolling) can only be done -with a linear scan through the whole array, summing up line heights as -you go. Seeing how I've been going out of my way to make big documents -fast, this is not acceptable.

      - -

      The new representation is based on a B-tree. The leaves of the tree -contain arrays of line objects, with a fixed minimum and maximum size, -and the non-leaf nodes simply hold arrays of child nodes. Each node -stores both the amount of lines that live below them and the vertical -space taken up by these lines. This allows the tree to be indexed both -by line number and by vertical position, and all access has -logarithmic complexity in relation to the document size.

      - -

      I gave line objects and tree nodes parent pointers, to the node -above them. When a line has to update its height, it can simply walk -these pointers to the top of the tree, adding or subtracting the -difference in height from each node it encounters. The parent pointers -also make it cheaper (in complexity terms, the difference is probably -tiny in normal-sized documents) to find the current line number when -given a line object. In the old approach, the whole document array had -to be searched. Now, we can just walk up the tree and count the sizes -of the nodes coming before us at each level.

      - -

      I chose B-trees, not regular binary trees, mostly because they -allow for very fast bulk insertions and deletions. When there is a big -change to a document, it typically involves adding, deleting, or -replacing a chunk of subsequent lines. In a regular balanced tree, all -these inserts or deletes would have to be done separately, which could -be really expensive. In a B-tree, to insert a chunk, you just walk -down the tree once to find where it should go, insert them all in one -shot, and then break up the node if needed. This breaking up might -involve breaking up nodes further up, but only requires a single pass -back up the tree. For deletion, I'm somewhat lax in keeping things -balanced—I just collapse nodes into a leaf when their child count goes -below a given number. This means that there are some weird editing -patterns that may result in a seriously unbalanced tree, but even such -an unbalanced tree will perform well, unless you spend a day making -strangely repeating edits to a really big document.

      - -

      Keymaps

      - -

      Above, I claimed that directly catching key -events for things like cursor movement is impractical because it -requires some browser-specific kludges. I then proceeded to explain -some awful hacks that were needed to make it -possible for the selection changes to be detected through the -textarea. In fact, the second hack is about as bad as the first.

      - -

      On top of that, in the presence of user-configurable tab sizes and -collapsed and wrapped lines, lining up cursor movement in the textarea -with what's visible on the screen becomes a nightmare. Thus, I've -decided to move to a model where the textarea's selection is no longer -depended on.

      - -

      So I moved to a model where all cursor movement is handled by my -own code. This adds support for a goal column, proper interaction of -cursor movement with collapsed lines, and makes it possible for -vertical movement to move through wrapped lines properly, instead of -just treating them like non-wrapped lines.

      - -

      The key event handlers now translate the key event into a string, -something like Ctrl-Home or Shift-Cmd-R, and -use that string to look up an action to perform. To make keybinding -customizable, this lookup goes through -a table, using a scheme that -allows such tables to be chained together (for example, the default -Mac bindings fall through to a table named 'emacsy', which defines -basic Emacs-style bindings like Ctrl-F, and which is also -used by the custom Emacs bindings).

      - -

      A new -option extraKeys -allows ad-hoc keybindings to be defined in a much nicer way than what -was possible with the -old onKeyEvent -callback. You simply provide an object mapping key identifiers to -functions, instead of painstakingly looking at raw key events.

      - -

      Built-in commands map to strings, rather than functions, for -example "goLineUp" is the default action bound to the up -arrow key. This allows new keymaps to refer to them without -duplicating any code. New commands can be defined by assigning to -the CodeMirror.commands object, which maps such commands -to functions.

      - -

      The hidden textarea now only holds the current selection, with no -extra characters around it. This has a nice advantage: polling for -input becomes much, much faster. If there's a big selection, this text -does not have to be read from the textarea every time—when we poll, -just noticing that something is still selected is enough to tell us -that no new text was typed.

      - -

      The reason that cheap polling is important is that many browsers do -not fire useful events on IME (input method engine) input, which is -the thing where people inputting a language like Japanese or Chinese -use multiple keystrokes to create a character or sequence of -characters. Most modern browsers fire input when the -composing is finished, but many don't fire anything when the character -is updated during composition. So we poll, whenever the -editor is focused, to provide immediate updates of the display.

      - -
      - -
       
      - - diff --git a/doc/manual.html b/doc/manual.html deleted file mode 100644 index 1c60eeeb3a..0000000000 --- a/doc/manual.html +++ /dev/null @@ -1,1231 +0,0 @@ - - - - - CodeMirror: User Manual - - - - - - -

      { } CodeMirror

      - -
      - -
      -/* User manual and
      -   reference guide */
      -
      -
      - -
      - -

      Overview

      - -

      CodeMirror is a code-editor component that can be embedded in - Web pages. The code library provides only the editor - component, no accompanying buttons, auto-completion, or other IDE - functionality. It does provide a rich API on top of which such - functionality can be straightforwardly implemented. See - the add-ons included in the distribution, - and - the CodeMirror - UI project, for reusable implementations of extra features.

      - -

      CodeMirror works with language-specific modes. Modes are - JavaScript programs that help color (and optionally indent) text - written in a given language. The distribution comes with a number - of modes (see the mode/ directory), and it isn't hard - to write new ones for other languages.

      - -

      Basic Usage

      - -

      The easiest way to use CodeMirror is to simply load the script - and style sheet found under lib/ in the distribution, - plus a mode script from one of the mode/ directories - and a theme stylesheet from theme/. (See - also the compression helper.) For - example:

      - -
      <script src="lib/codemirror.js"></script>
      -<link rel="stylesheet" href="../lib/codemirror.css">
      -<script src="mode/javascript/javascript.js"></script>
      - -

      Having done this, an editor instance can be created like - this:

      - -
      var myCodeMirror = CodeMirror(document.body);
      - -

      The editor will be appended to the document body, will start - empty, and will use the mode that we loaded. To have more control - over the new editor, a configuration object can be passed - to CodeMirror as a second argument:

      - -
      var myCodeMirror = CodeMirror(document.body, {
      -  value: "function myScript(){return 100;}\n",
      -  mode:  "javascript"
      -});
      - -

      This will initialize the editor with a piece of code already in - it, and explicitly tell it to use the JavaScript mode (which is - useful when multiple modes are loaded). - See below for a full discussion of the - configuration options that CodeMirror accepts.

      - -

      In cases where you don't want to append the editor to an - element, and need more control over the way it is inserted, the - first argument to the CodeMirror function can also - be a function that, when given a DOM element, inserts it into the - document somewhere. This could be used to, for example, replace a - textarea with a real editor:

      - -
      var myCodeMirror = CodeMirror(function(elt) {
      -  myTextArea.parentNode.replaceChild(elt, myTextArea);
      -}, {value: myTextArea.value});
      - -

      However, for this use case, which is a common way to use - CodeMirror, the library provides a much more powerful - shortcut:

      - -
      var myCodeMirror = CodeMirror.fromTextArea(myTextArea);
      - -

      This will, among other things, ensure that the textarea's value - is updated when the form (if it is part of a form) is submitted. - See the API reference for a full - description of this method.

      - -

      Configuration

      - -

      Both the CodeMirror function and - its fromTextArea method take as second (optional) - argument an object containing configuration options. Any option - not supplied like this will be taken - from CodeMirror.defaults, an object containing the - default options. You can update this object to change the defaults - on your page.

      - -

      Options are not checked in any way, so setting bogus option - values is bound to lead to odd errors.

      - -

      These are the supported options:

      - -
      -
      value (string)
      -
      The starting value of the editor.
      - -
      mode (string or object)
      -
      The mode to use. When not given, this will default to the - first mode that was loaded. It may be a string, which either - simply names the mode or is - a MIME type - associated with the mode. Alternatively, it may be an object - containing configuration options for the mode, with - a name property that names the mode (for - example {name: "javascript", json: true}). The demo - pages for each mode contain information about what configuration - parameters the mode supports. You can ask CodeMirror which modes - and MIME types are loaded with - the CodeMirror.listModes - and CodeMirror.listMIMEs functions.
      - -
      theme (string)
      -
      The theme to style the editor with. You must make sure the - CSS file defining the corresponding .cm-s-[name] - styles is loaded (see - the theme directory in the - distribution). The default is "default", for which - colors are included in codemirror.css. It is - possible to use multiple theming classes at once—for - example "foo bar" will assign both - the cm-s-foo and the cm-s-bar classes - to the editor.
      - -
      indentUnit (integer)
      -
      How many spaces a block (whatever that means in the edited - language) should be indented. The default is 2.
      - -
      smartIndent (boolean)
      -
      Whether to use the context-sensitive indentation that the - mode provides (or just indent the same as the line before). - Defaults to true.
      - -
      tabSize (integer)
      -
      The width of a tab character. Defaults to 4.
      - -
      indentWithTabs (boolean)
      -
      Whether, when indenting, the first N*tabSize - spaces should be replaced by N tabs. Default is false.
      - -
      electricChars (boolean)
      -
      Configures whether the editor should re-indent the current - line when a character is typed that might change its proper - indentation (only works if the mode supports indentation). - Default is true.
      - -
      autoClearEmptyLines (boolean)
      -
      When turned on (default is off), this will - automatically clear lines consisting only of whitespace when the - cursor leaves them. This is mostly useful to prevent auto - indentation from introducing trailing whitespace in a file.
      - -
      keyMap (string)
      -
      Configures the keymap to use. The default - is "default", which is the only keymap defined - in codemirror.js itself. Extra keymaps are found in - the keymap directory. See - the section on keymaps for more - information.
      - -
      extraKeys (object)
      -
      Can be used to specify extra keybindings for the editor, - alongside the ones defined - by keyMap. Should be - either null, or a valid keymap value.
      - -
      lineWrapping (boolean)
      -
      Whether CodeMirror should scroll or wrap for long lines. - Defaults to false (scroll).
      - -
      lineNumbers (boolean)
      -
      Whether to show line numbers to the left of the editor.
      - -
      firstLineNumber (integer)
      -
      At which number to start counting lines. Default is 1.
      - -
      lineNumberFormatter (function(integer))
      -
      A function used to format line numbers. The function is passed the current line number. Default prints the line number verbatim.
      - -
      gutter (boolean)
      -
      Can be used to force a 'gutter' (empty space on the left of - the editor) to be shown even when no line numbers are active. - This is useful for setting markers.
      - -
      fixedGutter (boolean)
      -
      When enabled (off by default), this will make the gutter - stay visible when the document is scrolled horizontally.
      - -
      readOnly (boolean)
      -
      This disables editing of the editor content by the user. If - the special value "nocursor" is given (instead of - simply true), focusing of the editor is also - disallowed.
      - -
      onChange (function)
      -
      When given, this function will be called every time the - content of the editor is changed. It will be given the editor - instance as first argument, and an {from, to, text, next} - object containing information about the changes - that occurred as second argument. from - and to are the positions (in the pre-change - coordinate system) where the change started and - ended (for example, it might be {ch:0, line:18} if the - position is at the beginning of line #19). text - is an array of strings representing the text that replaced the changed - range (split by line). If multiple changes happened during a single - operation, the object will have a next property pointing to - another change object (which may point to another, etc).
      - -
      onCursorActivity (function)
      -
      Will be called when the cursor or selection moves, or any - change is made to the editor content.
      - -
      onViewportChange (function)
      -
      When given, will be called whenever - the view port of the editor changes - (due to scrolling, editing, or any other factor). It will be - passed three arguments, the editor instance, the start of the - viewport, and its end.
      - -
      onGutterClick (function)
      -
      When given, will be called whenever the editor gutter (the - line-number area) is clicked. Will be given the editor instance - as first argument, the (zero-based) number of the line that was - clicked as second argument, and the raw mousedown - event object as third argument.
      - -
      onFocus, onBlur (function)
      -
      The given functions will be called whenever the editor is - focused or unfocused.
      - -
      onScroll (function)
      -
      When given, will be called whenever the editor is - scrolled.
      - -
      onUpdate (function)
      -
      Will be called whenever CodeMirror updates its DOM display.
      - -
      matchBrackets (boolean)
      -
      Determines whether brackets are matched whenever the cursor - is moved next to a bracket.
      - -
      cursorBlinkRate (number)
      -
      Half-period in milliseconds used for cursor blinking. The default blink - rate is 530ms.
      - -
      workTime, workDelay (number)
      -
      Highlighting is done by a pseudo background-thread that will - work for workTime milliseconds, and then use - timeout to sleep for workDelay milliseconds. The - defaults are 200 and 300, you can change these options to make - the highlighting more or less aggressive.
      - -
      pollInterval (number)
      -
      Indicates how quickly CodeMirror should poll its input - textarea for changes. Most input is captured by events, but some - things, like IME input on some browsers, doesn't generate events - that allow CodeMirror to properly detect it. Thus, it polls. - Default is 100 milliseconds.
      - -
      undoDepth (integer)
      -
      The maximum number of undo levels that the editor stores. - Defaults to 40.
      - -
      tabindex (integer)
      -
      The tab - index to assign to the editor. If not given, no tab index - will be assigned.
      - -
      autofocus (boolean)
      -
      Can be used to make CodeMirror focus itself on - initialization. Defaults to off. - When fromTextArea is - used, and no explicit value is given for this option, it will be - set to true when either the source textarea is focused, or it - has an autofocus attribute and no other element is - focused.
      - -
      dragDrop (boolean)
      -
      Controls whether drag-and-drop is enabled. On by default.
      - -
      onDragEvent (function)
      -
      When given, this will be called when the editor is handling - a dragenter, dragover, - or drop event. It will be passed the editor instance - and the event object as arguments. The callback can choose to - handle the event itself, in which case it should - return true to indicate that CodeMirror should not - do anything further.
      - -
      onKeyEvent (function)
      -
      This provides a rather low-level hook into CodeMirror's key - handling. If provided, this function will be called on - every keydown, keyup, - and keypress event that CodeMirror captures. It - will be passed two arguments, the editor instance and the key - event. This key event is pretty much the raw key event, except - that a stop() method is always added to it. You - could feed it to, for example, jQuery.Event to - further normalize it.
      This function can inspect the key - event, and handle it if it wants to. It may return true to tell - CodeMirror to ignore the event. Be wary that, on some browsers, - stopping a keydown does not stop - the keypress from firing, whereas on others it - does. If you respond to an event, you should probably inspect - its type property and only do something when it - is keydown (or keypress for actions - that need character data).
      -
      - -

      Keymaps

      - -

      Keymaps are ways to associate keys with functionality. A keymap - is an object mapping strings that identify the keys to functions - that implement their functionality.

      - -

      Keys are identified either by name or by character. - The CodeMirror.keyNames object defines names for - common keys and associates them with their key codes. Examples of - names defined here are Enter, F5, - and Q. These can be prefixed - with Shift-, Cmd-, Ctrl-, - and Alt- (in that order!) to specify a modifier. So - for example, Shift-Ctrl-Space would be a valid key - identifier.

      - -

      Alternatively, a character can be specified directly by - surrounding it in single quotes, for example '$' - or 'q'. Due to limitations in the way browsers fire - key events, these may not be prefixed with modifiers.

      - -

      The CodeMirror.keyMap object associates keymaps - with names. User code and keymap definitions can assign extra - properties to this object. Anywhere where a keymap is expected, a - string can be given, which will be looked up in this object. It - also contains the "default" keymap holding the - default bindings.

      - -

      The values of properties in keymaps can be either functions of - a single argument (the CodeMirror instance), strings, or - false. Such strings refer to properties of the - CodeMirror.commands object, which defines a number of - common commands that are used by the default keybindings, and maps - them to functions. If the property is set to false, - CodeMirror leaves handling of the key up to the browser. A key - handler function may throw CodeMirror.Pass to indicate - that it has decided not to handle the key, and other handlers (or - the default behavior) should be given a turn.

      - -

      Keys mapped to command names that start with the - characters "go" (which should be used for - cursor-movement actions) will be fired even when an - extra Shift modifier is present (i.e. "Up": - "goLineUp" matches both up and shift-up). This is used to - easily implement shift-selection.

      - -

      Keymaps can defer to each other by defining - a fallthrough property. This indicates that when a - key is not found in the map itself, one or more other maps should - be searched. It can hold either a single keymap or an array of - keymaps.

      - -

      When a keymap contains a nofallthrough property - set to true, keys matched against that map will be - ignored if they don't match any of the bindings in the map (no - further child maps will be tried, and the default effect of - inserting a character will not occur).

      - -

      Customized Styling

      - -

      Up to a certain extent, CodeMirror's look can be changed by - modifying style sheet files. The style sheets supplied by modes - simply provide the colors for that mode, and can be adapted in a - very straightforward way. To style the editor itself, it is - possible to alter or override the styles defined - in codemirror.css.

      - -

      Some care must be taken there, since a lot of the rules in this - file are necessary to have CodeMirror function properly. Adjusting - colors should be safe, of course, and with some care a lot of - other things can be changed as well. The CSS classes defined in - this file serve the following roles:

      - -
      -
      CodeMirror
      -
      The outer element of the editor. This should be used for the - editor width, borders and positioning. Can also be used to set - styles that should hold for everything inside the editor (such - as font and font size), or to set a background.
      - -
      CodeMirror-scroll
      -
      This determines the editor's height, and whether the editor - scrolls (overflow: auto + fixed height). By - default, it does. Giving this height: auto; overflow: - visible; will cause the editor to resize to fit its - content.
      - -
      CodeMirror-focused
      -
      Whenever the editor is focused, the top element gets this - class. This is used to hide the cursor and give the selection a - different color when the editor is not focused.
      - -
      CodeMirror-gutter
      -
      Use this for giving a background or a border to the editor - gutter. Don't set any padding here, - use CodeMirror-gutter-text for that. By default, - the gutter is 'fluid', meaning it will adjust its width to the - maximum line number or line marker width. You can also set a - fixed width if you want.
      - -
      CodeMirror-gutter-text
      -
      Used to style the actual line numbers. For the numbers to - line up, you must make sure that the font in the gutter is the - same as the one in the rest of the editor, so you should - probably only set font style and size in - the CodeMirror class.
      - -
      CodeMirror-lines
      -
      The visible lines. If this has vertical - padding, CodeMirror-gutter should have the same - padding.
      - -
      CodeMirror-cursor
      -
      The cursor is a block element that is absolutely positioned. - You can make it look whichever way you want.
      - -
      CodeMirror-selected
      -
      The selection is represented by span elements - with this class.
      - -
      CodeMirror-matchingbracket, - CodeMirror-nonmatchingbracket
      -
      These are used to style matched (or unmatched) brackets.
      -
      - -

      So note carefully that, in order to resize the - editor, you should set a width on - the wrapper - (class CodeMirror) element, and a height on - the scroller - (class CodeMirror-scroll) element.

      - -

      The actual lines, as well as the cursor, are represented - by pre elements. By default no text styling (such as - bold) that might change line height is applied. If you do want - such effects, you'll have to give CodeMirror pre a - fixed height.

      - -

      If your page's style sheets do funky things to - all div or pre elements (you probably - shouldn't do that), you'll have to define rules to cancel these - effects out again for elements under the CodeMirror - class.

      - -

      Themes are also simply CSS files, which define colors for - various syntactic elements. See the files in - the theme directory.

      - -

      Programming API

      - -

      A lot of CodeMirror features are only available through its API. - This has the disadvantage that you need to do work to enable them, - and the advantage that CodeMirror will fit seamlessly into your - application.

      - -

      Whenever points in the document are represented, the API uses - objects with line and ch properties. - Both are zero-based. CodeMirror makes sure to 'clip' any positions - passed by client code so that they fit inside the document, so you - shouldn't worry too much about sanitizing your coordinates. If you - give ch a value of null, or don't - specify it, it will be replaced with the length of the specified - line.

      - -
      -
      getValue() → string
      -
      Get the current editor content. You can pass it an optional - argument to specify the string to be used to separate lines - (defaults to "\n").
      -
      setValue(string)
      -
      Set the editor content.
      - -
      getSelection() → string
      -
      Get the currently selected code.
      -
      replaceSelection(string)
      -
      Replace the selection with the given string.
      - -
      setSize(width, height)
      -
      Programatically set the size of the editor (overriding the - applicable CSS - rules). width and height height - can be either numbers (interpreted as pixels) or CSS units - ("100%", for example). You can - pass null for either of them to indicate that that - dimension should not be changed.
      -
      focus()
      -
      Give the editor focus.
      -
      scrollTo(x, y)
      -
      Scroll the editor to a given (pixel) position. Both - arguments may be left as null - or undefined to have no effect.
      -
      getScrollInfo()
      -
      Get an {x, y, width, height} object that - represents the current scroll position and scrollable area size - of the editor.
      - -
      setOption(option, value)
      -
      Change the configuration of the editor. option - should the name of an option, - and value should be a valid value for that - option.
      -
      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 - coordinates of the cursor. If mode - is "local", they will be relative to the top-left - corner of the editable document. If it is "page" or - not given, they are relative to the top-left corner of the - page. yBot is the coordinate of the bottom of the - cursor. start is a boolean indicating whether you - want the start or the end of the selection.
      -
      charCoords(pos, mode) → object
      -
      Like cursorCoords, but returns the position of - an arbitrary characters. pos should be - a {line, ch} object.
      -
      coordsChar(object) → pos
      -
      Given an {x, y} object (in page coordinates), - returns the {line, ch} position that corresponds to - it.
      - -
      undo()
      -
      Undo one edit (if any undo events are stored).
      -
      redo()
      -
      Redo one undone edit.
      -
      historySize() → object
      -
      Returns an object with {undo, redo} properties, - both of which hold integers, indicating the amount of stored - undo and redo operations.
      -
      clearHistory()
      -
      Clears the editor's undo history.
      -
      getHistory() → object
      -
      Get a (JSON-serializeable) representation of the undo history.
      -
      setHistory(object)
      -
      Replace the editor's undo history with the one provided, - which must be a value as returned - by getHistory. Note that - this will have entirely undefined results if the editor content - isn't also the same as it was when getHistory was - called.
      - -
      indentLine(line, dir)
      -
      Reset the given line's indentation to the indentation - prescribed by the mode. If the second argument is given, - indentation will be increased (if dir is true) or - decreased (if false) by an indent - unit instead.
      - -
      getTokenAt(pos) → object
      -
      Retrieves information about the token the current mode found - before the given position (a {line, ch} object). The - returned object has the following properties: -
      -
      start
      The character (on the given line) at which the token starts.
      -
      end
      The character at which the token ends.
      -
      string
      The token's string.
      -
      className
      The class the mode assigned - to the token. (Can be null when no class was assigned.)
      -
      state
      The mode's state at the end of this token.
      -
      - -
      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 options - 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 - as it is being edited, at the given position. A bookmark has two - methods find() and clear(). The first - returns the current position of the bookmark, if it is still in - the document, and the second explicitly removes the - bookmark.
      - -
      findMarksAt(pos) → array
      -
      Returns an array of all the bookmarks and marked ranges - present at the given position.
      - -
      setMarker(line, text, className) → lineHandle
      -
      Add a gutter marker for the given line. Gutter markers are - shown in the line-number area (instead of the number for this - line). Both text and className are - optional. Setting text to a Unicode character like - ● tends to give a nice effect. To put a picture in the gutter, - set text to a space and className to - something that sets a background image. If you - specify text, the given text (which may contain - HTML) will, by default, replace the line number for that line. - If this is not what you want, you can include the - string %N% in the text, which will be replaced by - the line number.
      -
      clearMarker(line)
      -
      Clears a marker created - with setMarker. line can be either a - number or a handle returned by setMarker (since a - number may now refer to a different line if something was added - or deleted).
      -
      setLineClass(line, className, backgroundClassName) → lineHandle
      -
      Set a CSS class name for the given line. line - can be a number or a line handle (as returned - by setMarker or this - function). className will be used to style the text - for the line, and backgroundClassName to style its - background (which lies behind the selection). - Pass null to clear the classes for a line.
      -
      hideLine(line) → lineHandle
      -
      Hide the given line (either by number or by handle). Hidden - lines don't show up in the editor, and their numbers are skipped - when line numbers are enabled. - Deleting a region around them does delete them, and coping a - region around will include them in the copied text.
      -
      showLine(line) → lineHandle
      -
      The inverse of hideLine—re-shows a previously - hidden line, by number or by handle.
      - -
      onDeleteLine(line, func)
      -
      Register a function that should be called when the line is - deleted from the document.
      - -
      lineInfo(line) → object
      -
      Returns the line number, text content, and marker status of - the given line, which can be either a number or a handle - returned by setMarker. The returned object has the - structure {line, handle, text, markerText, markerClass, - lineClass, bgClass}.
      - -
      getLineHandle(num) → lineHandle
      -
      Fetches the line handle for the given line number.
      - -
      getViewport() → object
      -
      Returns a {from, to} object indicating the - start (inclusive) and end (exclusive) of the currently displayed - part of the document. In big documents, when most content is - scrolled out of view, CodeMirror will only render the visible - part, and a margin around it. See also - the onViewportChange - option.
      - -
      addWidget(pos, node, scrollIntoView)
      -
      Puts node, which should be an absolutely - positioned DOM node, into the editor, positioned right below the - given {line, ch} position. - When scrollIntoView is true, the editor will ensure - that the entire node is visible (if possible). To remove the - widget again, simply use DOM methods (move it somewhere else, or - call removeChild on its parent).
      - -
      matchBrackets()
      -
      Force matching-bracket-highlighting to happen.
      - -
      lineCount() → number
      -
      Get the number of lines in the editor.
      - -
      getCursor(start) → object
      -
      start is a boolean indicating whether the start - or the end of the selection must be retrieved. If it is not - given, the current cursor pos, i.e. the side of the selection - that would move if you pressed an arrow key, is chosen. - A {line, ch} object will be returned.
      -
      somethingSelected() → boolean
      -
      Return true if any text is selected.
      -
      setCursor(pos)
      -
      Set the cursor position. You can either pass a - single {line, ch} object, or the line and the - character as two separate parameters.
      -
      setSelection(start, end)
      -
      Set the selection range. start - and end should be {line, ch} objects.
      - -
      getLine(n) → string
      -
      Get the content of line n.
      -
      setLine(n, text)
      -
      Set the content of line n.
      -
      removeLine(n)
      -
      Remove the given line from the document.
      - -
      getRange(from, to) → string -
      Get the text between the given points in the editor, which - should be {line, ch} objects. An optional third - argument can be given to indicate the line separator string to - use (defaults to "\n").
      -
      replaceRange(string, from, to)
      -
      Replace the part of the document between from - and to with the given string. from - and to must be {line, ch} - objects. to can be left off to simply insert the - string at position from.
      - -
      posFromIndex(index) → object
      -
      Calculates and returns a {line, ch} object for a - zero-based index who's value is relative to the start of the - editor's text. If the index is out of range of the text then - the returned object is clipped to start or end of the text - respectively.
      -
      indexFromPos(object) → number
      -
      The reverse of posFromIndex.
      -
      - -

      The following are more low-level methods:

      - -
      -
      operation(func) → result
      -
      CodeMirror internally buffers changes and only updates its - DOM structure after it has finished performing some operation. - If you need to perform a lot of operations on a CodeMirror - instance, you can call this method with a function argument. It - will call the function, buffering up all changes, and only doing - the expensive update after the function returns. This can be a - lot faster. The return value from this method will be the return - value of your function.
      - -
      compoundChange(func) → result
      -
      Will call the given function (and return its result), - combining all changes made while that function executes into a - single undo event.
      - -
      refresh()
      -
      If your code does something to change the size of the editor - element (window resizes are already listened for), or unhides - it, you should probably follow up by calling this method to - ensure CodeMirror is still looking as intended.
      - -
      getInputField() → textarea
      -
      Returns the hidden textarea used to read input.
      -
      getWrapperElement() → node
      -
      Returns the DOM node that represents the editor, and - controls its width. Remove this from your tree to delete an - editor instance. Set it's width style when - resizing.
      -
      getScrollerElement() → node
      -
      Returns the DOM node that is responsible for the vertical - sizing and horizontal scrolling of the editor. You can change - the height style of this element to resize an - editor. (You might have to call - the refresh method - afterwards.)
      -
      getGutterElement() → node
      -
      Fetches the DOM node that represents the editor gutter.
      - -
      getStateAfter(line) → state
      -
      Returns the mode's parser state, if any, at the end of the - given line number. If no line number is given, the state at the - end of the document is returned. This can be useful for storing - parsing errors in the state, or getting other kinds of - contextual information for a line.
      -
      - -

      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 three - additional methods:

      - -
      -
      save()
      -
      Copy the content of the editor into the textarea.
      - -
      toTextArea()
      -
      Remove the editor, and restore the original textarea (with - the editor's current content).
      - -
      getTextArea() → textarea
      -
      Returns the textarea that the instance was based on.
      -
      - -

      If you want to define extra methods in terms - of the CodeMirror API, it is possible to - use CodeMirror.defineExtension(name, value). This - will cause the given value (usually a method) to be added to all - CodeMirror instances created from then on.

      - -

      If your extention just needs to run some - code whenever a CodeMirror instance is initialized, - use CodeMirror.defineInitHook. Give it a function as - its only argument, and from then on, that function will be called - (with the instance as argument) whenever a new CodeMirror instance - is initialized.

      - -

      Add-ons

      - -

      The lib/util directory in the distribution - contains a number of reusable components that implement extra - editor functionality. In brief, they are:

      - -
      -
      dialog.js
      -
      Provides a very simple way to query users for text input. - Adds an openDialog method to CodeMirror instances, - which can be called with an HTML fragment that provides the - prompt (should include an input tag), and a - callback function that is called when text has been entered. - Depends on lib/util/dialog.css.
      -
      searchcursor.js
      -
      Adds the getSearchCursor(query, start, caseFold) → - cursor method to CodeMirror instances, which can be used - to implement search/replace functionality. query - can be a regular expression or a string (only strings will match - across lines—if they contain newlines). start - provides the starting position of the search. It can be - a {line, ch} object, or can be left off to default - to the start of the document. caseFold is only - relevant when matching a string. It will cause the search to be - case-insensitive. A search cursor has the following methods: -
      -
      findNext(), findPrevious() → boolean
      -
      Search forward or backward from the current position. - The return value indicates whether a match was found. If - matching a regular expression, the return value will be the - array returned by the match method, in case you - want to extract matched groups.
      -
      from(), to() → object
      -
      These are only valid when the last call - to findNext or findPrevious did - not return false. They will return {line, ch} - objects pointing at the start and end of the match.
      -
      replace(text)
      -
      Replaces the currently found match with the given text - and adjusts the cursor position to reflect the - replacement.
      -
      - - -
      Implements the search commands. CodeMirror has keys bound to - these by default, but will not do anything with them unless an - implementation is provided. Depends - on searchcursor.js, and will make use - of openDialog when - available to make prompting for search queries less ugly.
      -
      foldcode.js
      -
      Helps with code folding. - See the demo for an example. - Call CodeMirror.newFoldFunction with a range-finder - helper function to create a function that will, when applied to - a CodeMirror instance and a line number, attempt to fold or - unfold the block starting at the given line. A range-finder is a - language-specific function that also takes an instance and a - line number, and returns an end line for the block, or null if - no block is started on that line. This file - provides CodeMirror.braceRangeFinder, which finds - blocks in brace languages (JavaScript, C, Java, - etc), CodeMirror.indentRangeFinder, for languages - where indentation determines block structure (Python, Haskell), - and CodeMirror.tagRangeFinder, for XML-style - languages.
      -
      runmode.js
      -
      Can be used to run a CodeMirror mode over text without - actually opening an editor instance. - See the demo for an - example.
      -
      overlay.js
      -
      Mode combinator that can be used to extend a mode with an - 'overlay' — a secondary mode is run over the stream, along with - the base mode, and can color specific pieces of text without - interfering with the base mode. - Defines CodeMirror.overlayMode, which is used to - create such a mode. See this - demo for a detailed example.
      -
      multiplex.js
      -
      Mode combinator that can be used to easily 'multiplex' - between several modes. - Defines CodeMirror.multiplexingMode which, when - given as first argument a mode object, and as other arguments - any number of {open, close, mode [, delimStyle]} - objects, will return a mode object that starts parsing using the - mode passed as first argument, but will switch to another mode - as soon as it encounters a string that occurs in one of - the open fields of the passed objects. When in a - sub-mode, it will go back to the top mode again when - the close string is encountered. - When delimStyle is specified, it will be the token - style returned for the delimiter tokens. The outer mode will not - see the content between the delimiters. - See this demo for an - example.
      -
      simple-hint.js
      -
      Provides a framework for showing autocompletion hints. - Defines CodeMirror.simpleHint, which takes a - CodeMirror instance and a hinting function, and pops up a widget - that allows the user to select a completion. Hinting functions - are function that take an editor instance, and return - a {list, from, to} object, where list - is an array of strings (the completions), and from - and to give the start and end of the token that is - being completed. Depends - on lib/util/simple-hint.css.
      -
      javascript-hint.js
      -
      Defines CodeMirror.javascriptHint - and CodeMirror.coffeescriptHint, which are simple - hinting functions for the JavaScript and CoffeeScript - modes.
      -
      match-highlighter.js
      -
      Adds a matchHighlight method to CodeMirror - instances that can be called (typically from - a onCursorActivity - handler) to highlight all instances of a currently selected word - with the a classname given as a first argument to the method. - 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 - the demo.
      -
      loadmode.js
      -
      Defines a CodeMirror.requireMode(modename, - callback) function that will try to load a given mode and - call the callback when it succeeded. You'll have to - set CodeMirror.modeURL to a string that mode paths - can be constructed from, for - example "mode/%N/%N.js"—the %N's will - be replaced with the mode name. Also - defines CodeMirror.autoLoadMode(instance, mode), - which will ensure the given mode is loaded and cause the given - editor instance to refresh its mode when the loading - succeeded. See the demo.
      -
      - -

      Writing CodeMirror Modes

      - -

      Modes typically consist of a single JavaScript file. This file - defines, in the simplest case, a lexer (tokenizer) for your - language—a function that takes a character stream as input, - advances it past a token, and returns a style for that token. More - advanced modes can also handle indentation for the language.

      - -

      The mode script should - call CodeMirror.defineMode to register itself with - CodeMirror. This function takes two arguments. The first should be - the name of the mode, for which you should use a lowercase string, - preferably one that is also the name of the files that define the - mode (i.e. "xml" is defined xml.js). The - second argument should be a function that, given a CodeMirror - configuration object (the thing passed to - the CodeMirror function) and an optional mode - configuration object (as in - the mode option), returns - a mode object.

      - -

      Typically, you should use this second argument - to defineMode as your module scope function (modes - should not leak anything into the global scope!), i.e. write your - whole mode inside this function.

      - -

      The main responsibility of a mode script is parsing - the content of the editor. Depending on the language and the - amount of functionality desired, this can be done in really easy - or extremely complicated ways. Some parsers can be stateless, - meaning that they look at one element (token) of the code - at a time, with no memory of what came before. Most, however, will - need to remember something. This is done by using a state - object, which is an object that is always passed when - reading a token, and which can be mutated by the tokenizer.

      - -

      Modes that use a state must define - a startState method on their mode object. This is a - function of no arguments that produces a state object to be used - at the start of a document.

      - -

      The most important part of a mode object is - its token(stream, state) method. All modes must - define this method. It should read one token from the stream it is - given as an argument, optionally update its state, and return a - style string, or null for tokens that do not have to - be styled. For your styles, you can either use the 'standard' ones - defined in the themes (without the cm- prefix), or - define your own and have people include a custom CSS file for your - mode.

      - -

      The stream object encapsulates a line of code - (tokens may never span lines) and our current position in that - line. It has the following API:

      - -
      -
      eol() → boolean
      -
      Returns true only if the stream is at the end of the - line.
      -
      sol() → boolean
      -
      Returns true only if the stream is at the start of the - line.
      - -
      peek() → character
      -
      Returns the next character in the stream without advancing - it. Will return an empty string at the end of the line.
      -
      next() → character
      -
      Returns the next character in the stream and advances it. - Also returns undefined when no more characters are - available.
      - -
      eat(match) → character
      -
      match can be a character, a regular expression, - or a function that takes a character and returns a boolean. If - the next character in the stream 'matches' the given argument, - it is consumed and returned. Otherwise, undefined - is returned.
      -
      eatWhile(match) → boolean
      -
      Repeatedly calls eat with the given argument, - until it fails. Returns true if any characters were eaten.
      -
      eatSpace() → boolean
      -
      Shortcut for eatWhile when matching - white-space.
      -
      skipToEnd()
      -
      Moves the position to the end of the line.
      -
      skipTo(ch) → boolean
      -
      Skips to the next occurrence of the given character, if - found on the current line (doesn't advance the stream if the - character does not occur on the line). Returns true if the - character was found.
      -
      match(pattern, consume, caseFold) → boolean
      -
      Act like a - multi-character eat—if consume is true - or not given—or a look-ahead that doesn't update the stream - position—if it is false. pattern can be either a - string or a regular expression starting with ^. - When it is a string, caseFold can be set to true to - make the match case-insensitive. When successfully matching a - regular expression, the returned value will be the array - returned by match, in case you need to extract - matched groups.
      - -
      backUp(n)
      -
      Backs up the stream n characters. Backing it up - further than the start of the current token will cause things to - break, so be careful.
      -
      column() → integer
      -
      Returns the column (taking into account tabs) at which the - current token starts. Can be used to find out whether a token - starts a new line.
      -
      indentation() → integer
      -
      Tells you how far the current line has been indented, in - spaces. Corrects for tab characters.
      - -
      current() → string
      -
      Get the string between the start of the current token and - the current stream position.
      -
      - -

      By default, blank lines are simply skipped when - tokenizing a document. For languages that have significant blank - lines, you can define a blankLine(state) method on - your mode that will get called whenever a blank line is passed - over, so that it can update the parser state.

      - -

      Because state object are mutated, and CodeMirror - needs to keep valid versions of a state around so that it can - restart a parse at any line, copies must be made of state objects. - The default algorithm used is that a new state object is created, - which gets all the properties of the old object. Any properties - which hold arrays get a copy of these arrays (since arrays tend to - be used as mutable stacks). When this is not correct, for example - because a mode mutates non-array properties of its state object, a - mode object should define a copyState method, - which is given a state and should return a safe copy of that - state.

      - -

      If you want your mode to provide smart indentation - (through the indentLine - method and the indentAuto - and newlineAndIndent commands, which keys can be - bound to), you must define - an indent(state, textAfter) method on your mode - object.

      - -

      The indentation method should inspect the given state object, - and optionally the textAfter string, which contains - the text on the line that is being indented, and return an - integer, the amount of spaces to indent. It should usually take - the indentUnit - option into account.

      - -

      Finally, a mode may define - an electricChars property, which should hold a string - containing all the characters that should trigger the behaviour - described for - the electricChars - option.

      - -

      So, to summarize, a mode must provide - a token method, and it may - provide startState, copyState, - compareStates, and indent methods. For - an example of a trivial mode, see - the diff mode, for a more involved - example, see the C-like - mode.

      - -

      Sometimes, it is useful for modes to nest—to have one - mode delegate work to another mode. An example of this kind of - mode is the mixed-mode HTML - mode. To implement such nesting, it is usually necessary to - create mode objects and copy states yourself. To create a mode - object, there are CodeMirror.getMode(options, - parserConfig), where the first argument is a configuration - object as passed to the mode constructor function, and the second - argument is a mode specification as in - the mode option. To copy a - state object, call CodeMirror.copyState(mode, state), - 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. 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 - are intended to be nested an optional argument that provides the - base indentation for the block of code. The JavaScript and CSS - parser do this, for example, to allow JavaScript and CSS code - inside the mixed-mode HTML mode to be properly indented.

      - -

      Finally, it is possible to associate your mode, or a certain - configuration of your mode, with - a MIME type. For - example, the JavaScript mode associates itself - with text/javascript, and its JSON variant - with application/json. To do this, - call CodeMirror.defineMIME(mime, modeSpec), - where modeSpec can be a string or object specifying a - 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.

      - -
      - -
       
      - - - diff --git a/doc/oldrelease.html b/doc/oldrelease.html deleted file mode 100644 index 542f57ae1d..0000000000 --- a/doc/oldrelease.html +++ /dev/null @@ -1,305 +0,0 @@ - - - - - CodeMirror - - - - - - -

      { } CodeMirror

      - -
      - -
      -/* Old release
      -   history */
      -
      -
      - -

      21-11-2011: Version 2.18:

      -

      Fixes TextMarker.clear, which is broken in 2.17.

      - -

      21-11-2011: Version 2.17:

      -
        -
      • Add support for line - wrapping and code - folding.
      • -
      • Add Github-style Markdown mode.
      • -
      • Add Monokai - and Rubyblue themes.
      • -
      • Add setBookmark method.
      • -
      • Move some of the demo code into reusable components - under lib/util.
      • -
      • Make screen-coord-finding code faster and more reliable.
      • -
      • Fix drag-and-drop in Firefox.
      • -
      • Improve support for IME.
      • -
      • Speed up content rendering.
      • -
      • Fix browser's built-in search in Webkit.
      • -
      • Make double- and triple-click work in IE.
      • -
      • Various fixes to modes.
      • -
      - -

      27-10-2011: Version 2.16:

      -
        -
      • Add Perl, Rust, TiddlyWiki, and Groovy modes.
      • -
      • Dragging text inside the editor now moves, rather than copies.
      • -
      • Add a coordsFromIndex method.
      • -
      • API change: setValue now no longer clears history. Use clearHistory for that.
      • -
      • API change: markText now - returns an object with clear and find - methods. Marked text is now more robust when edited.
      • -
      • Fix editing code with tabs in Internet Explorer.
      • -
      - -

      26-09-2011: Version 2.15:

      -

      Fix bug that snuck into 2.14: Clicking the - character that currently has the cursor didn't re-focus the - editor.

      - -

      26-09-2011: Version 2.14:

      - - - -

      23-08-2011: Version 2.13:

      - - -

      25-07-2011: Version 2.12:

      -
        -
      • Add a SPARQL mode.
      • -
      • Fix bug with cursor jumping around in an unfocused editor in IE.
      • -
      • Allow key and mouse events to bubble out of the editor. Ignore widget clicks.
      • -
      • Solve cursor flakiness after undo/redo.
      • -
      • Fix block-reindent ignoring the last few lines.
      • -
      • Fix parsing of multi-line attrs in XML mode.
      • -
      • Use innerHTML for HTML-escaping.
      • -
      • Some fixes to indentation in C-like mode.
      • -
      • Shrink horiz scrollbars when long lines removed.
      • -
      • Fix width feedback loop bug that caused the width of an inner DIV to shrink.
      • -
      - -

      04-07-2011: Version 2.11:

      -
        -
      • Add a Scheme mode.
      • -
      • Add a replace method to search cursors, for cursor-preserving replacements.
      • -
      • Make the C-like mode mode more customizable.
      • -
      • Update XML mode to spot mismatched tags.
      • -
      • Add getStateAfter API and compareState mode API methods for finer-grained mode magic.
      • -
      • Add a getScrollerElement API method to manipulate the scrolling DIV.
      • -
      • Fix drag-and-drop for Firefox.
      • -
      • Add a C# configuration for the C-like mode.
      • -
      • Add full-screen editing and mode-changing demos.
      • -
      - -

      07-06-2011: Version 2.1:

      -

      Add - a theme system - (demo). Note that this is not - backwards-compatible—you'll have to update your styles and - modes!

      - -

      07-06-2011: Version 2.02:

      -
        -
      • Add a Lua mode.
      • -
      • Fix reverse-searching for a regexp.
      • -
      • Empty lines can no longer break highlighting.
      • -
      • Rework scrolling model (the outer wrapper no longer does the scrolling).
      • -
      • Solve horizontal jittering on long lines.
      • -
      • Add runmode.js.
      • -
      • Immediately re-highlight text when typing.
      • -
      • Fix problem with 'sticking' horizontal scrollbar.
      • -
      - -

      26-05-2011: Version 2.01:

      -
        -
      • Add a Smalltalk mode.
      • -
      • Add a reStructuredText mode.
      • -
      • Add a Python mode.
      • -
      • Add a PL/SQL mode.
      • -
      • coordsChar now works
      • -
      • Fix a problem where onCursorActivity interfered with onChange.
      • -
      • Fix a number of scrolling and mouse-click-position glitches.
      • -
      • Pass information about the changed lines to onChange.
      • -
      • Support cmd-up/down on OS X.
      • -
      • Add triple-click line selection.
      • -
      • Don't handle shift when changing the selection through the API.
      • -
      • Support "nocursor" mode for readOnly option.
      • -
      • Add an onHighlightComplete option.
      • -
      • Fix the context menu for Firefox.
      • -
      - -

      28-03-2011: Version 2.0:

      -

      CodeMirror 2 is a complete rewrite that's - faster, smaller, simpler to use, and less dependent on browser - quirks. See this - and this - for more information. - -

      28-03-2011: Version 1.0:

      -
        -
      • Fix error when debug history overflows.
      • -
      • Refine handling of C# verbatim strings.
      • -
      • Fix some issues with JavaScript indentation.
      • -
      - -

      22-02-2011: Version 2.0 beta 2:

      -

      Somewhat more mature API, lots of bugs shaken out. - -

      17-02-2011: Version 0.94:

      -
        -
      • tabMode: "spaces" was modified slightly (now indents when something is selected).
      • -
      • Fixes a bug that would cause the selection code to break on some IE versions.
      • -
      • Disabling spell-check on WebKit browsers now works.
      • -
      - -

      08-02-2011: Version 2.0 beta 1:

      -

      CodeMirror 2 is a complete rewrite of - CodeMirror, no longer depending on an editable frame.

      - -

      19-01-2011: Version 0.93:

      -
        -
      • Added a Regular Expression parser.
      • -
      • Fixes to the PHP parser.
      • -
      • Support for regular expression in search/replace.
      • -
      • Add save method to instances created with fromTextArea.
      • -
      • Add support for MS T-SQL in the SQL parser.
      • -
      • Support use of CSS classes for highlighting brackets.
      • -
      • Fix yet another hang with line-numbering in hidden editors.
      • -
      - -

      17-12-2010: Version 0.92:

      -
        -
      • Make CodeMirror work in XHTML documents.
      • -
      • Fix bug in handling of backslashes in Python strings.
      • -
      • The styleNumbers option is now officially - supported and documented.
      • -
      • onLineNumberClick option added.
      • -
      • More consistent names onLoad and - onCursorActivity callbacks. Old names still work, but - are deprecated.
      • -
      • Add a Freemarker mode.
      • -
      - -

      11-11-2010: Version 0.91:

      -
        -
      • Adds support for Java.
      • -
      • Small additions to the PHP and SQL parsers.
      • -
      • Work around various Webkit issues.
      • -
      • Fix toTextArea to update the code in the textarea.
      • -
      • Add a noScriptCaching option (hack to ease development).
      • -
      • Make sub-modes of HTML mixed mode configurable.
      • -
      - -

      02-10-2010: Version 0.9:

      -
        -
      • Add support for searching backwards.
      • -
      • There are now parsers for Scheme, XQuery, and OmetaJS.
      • -
      • Makes height: "dynamic" more robust.
      • -
      • Fixes bug where paste did not work on OS X.
      • -
      • Add a enterMode and electricChars options to make indentation even more customizable.
      • -
      • Add firstLineNumber option.
      • -
      • Fix bad handling of @media rules by the CSS parser.
      • -
      • Take a new, more robust approach to working around the invisible-last-line bug in WebKit.
      • -
      - -

      22-07-2010: Version 0.8:

      -
        -
      • Add a cursorCoords method to find the screen - coordinates of the cursor.
      • -
      • A number of fixes and support for more syntax in the PHP parser.
      • -
      • Fix indentation problem with JSON-mode JS parser in Webkit.
      • -
      • Add a minification UI.
      • -
      • Support a height: dynamic mode, where the editor's - height will adjust to the size of its content.
      • -
      • Better support for IME input mode.
      • -
      • Fix JavaScript parser getting confused when seeing a no-argument - function call.
      • -
      • Have CSS parser see the difference between selectors and other - identifiers.
      • -
      • Fix scrolling bug when pasting in a horizontally-scrolled - editor.
      • -
      • Support toTextArea method in instances created with - fromTextArea.
      • -
      • Work around new Opera cursor bug that causes the cursor to jump - when pressing backspace at the end of a line.
      • -
      - -

      27-04-2010: Version - 0.67:

      -

      More consistent page-up/page-down behaviour - across browsers. Fix some issues with hidden editors looping forever - when line-numbers were enabled. Make PHP parser parse - "\\" correctly. Have jumpToLine work on - line handles, and add cursorLine function to fetch the - line handle where the cursor currently is. Add new - setStylesheet function to switch style-sheets in a - running editor.

      - -

      01-03-2010: Version - 0.66:

      -

      Adds removeLine method to API. - Introduces the PLSQL parser. - Marks XML errors by adding (rather than replacing) a CSS class, so - that they can be disabled by modifying their style. Fixes several - selection bugs, and a number of small glitches.

      - -

      12-11-2009: Version - 0.65:

      -

      Add support for having both line-wrapping and - line-numbers turned on, make paren-highlighting style customisable - (markParen and unmarkParen config - options), work around a selection bug that Opera - reintroduced in version 10.

      - -

      23-10-2009: Version - 0.64:

      -

      Solves some issues introduced by the - paste-handling changes from the previous release. Adds - setSpellcheck, setTextWrapping, - setIndentUnit, setUndoDepth, - setTabMode, and setLineNumbers to - customise a running editor. Introduces an SQL parser. Fixes a few small - problems in the Python - parser. And, as usual, add workarounds for various newly discovered - browser incompatibilities.

      - -

      31-08-2009: Version -0.63:

      -

      Overhaul of paste-handling (less fragile), fixes for several -serious IE8 issues (cursor jumping, end-of-document bugs) and a number -of small problems.

      - -

      30-05-2009: Version -0.62:

      -

      Introduces Python -and Lua parsers. Add -setParser (on-the-fly mode changing) and -clearHistory methods. Make parsing passes time-based -instead of lines-based (see the passTime option).

      - - diff --git a/doc/reporting.html b/doc/reporting.html deleted file mode 100644 index a616512530..0000000000 --- a/doc/reporting.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - CodeMirror: Reporting Bugs - - - - - - -

      { } CodeMirror

      - -
      - -
      -/* Reporting bugs
      -   effectively */
      -
      -
      - -
      - -

      So you found a problem in CodeMirror. By all means, report it! Bug -reports from users are the main drive behind improvements to -CodeMirror. But first, please read over these points:

      - -
        -
      1. CodeMirror is maintained by volunteers. They don't owe you - anything, so be polite. Reports with an indignant or belligerent - tone tend to be moved to the bottom of the pile.
      2. - -
      3. Include information about the browser in which the - problem occurred. Even if you tested several browsers, and - the problem occurred in all of them, mention this fact in the bug - report. Also include browser version numbers and the operating - system that you're on.
      4. - -
      5. Mention which release of CodeMirror you're using. Preferably, - try also with the current development snapshot, to ensure the - problem has not already been fixed.
      6. - -
      7. Mention very precisely what went wrong. "X is broken" is not a - good bug report. What did you expect to happen? What happened - instead? Describe the exact steps a maintainer has to take to make - the problem occur. We can not fix something that we can not - observe.
      8. - -
      9. If the problem can not be reproduced in any of the demos - included in the CodeMirror distribution, please provide an HTML - document that demonstrates the problem. The best way to do this is - to go to jsbin.com, enter - it there, press save, and include the resulting link in your bug - report.
      10. -
      - -
      - - - diff --git a/doc/upgrade_v2.2.html b/doc/upgrade_v2.2.html deleted file mode 100644 index 7e4d840043..0000000000 --- a/doc/upgrade_v2.2.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - CodeMirror: Upgrading to v2.2 - - - - - -

      { } CodeMirror

      - -
      - -
      -/* Upgrading to
      -   v2.2 */
      -
      -
      - -
      - -

      There are a few things in the 2.2 release that require some care -when upgrading.

      - -

      No more default.css

      - -

      The default theme is now included -in codemirror.css, so -you do not have to included it separately anymore. (It was tiny, so -even if you're not using it, the extra data overhead is negligible.) - -

      Different key customization

      - -

      CodeMirror has moved to a system -where keymaps are used to -bind behavior to keys. This means custom -bindings are now possible.

      - -

      Three options that influenced key -behavior, tabMode, enterMode, -and smartHome, are no longer supported. Instead, you can -provide custom bindings to influence the way these keys act. This is -done through the -new extraKeys -option, which can hold an object mapping key names to functionality. A -simple example would be:

      - -
        extraKeys: {
      -    "Ctrl-S": function(instance) { saveText(instance.getValue()); },
      -    "Ctrl-/": "undo"
      -  }
      - -

      Keys can be mapped either to functions, which will be given the -editor instance as argument, or to strings, which are mapped through -functions through the CodeMirror.commands table, which -contains all the built-in editing commands, and can be inspected and -extended by external code.

      - -

      By default, the Home key is bound to -the "goLineStartSmart" command, which moves the cursor to -the first non-whitespace character on the line. You can set do this to -make it always go to the very start instead:

      - -
        extraKeys: {"Home": "goLineStart"}
      - -

      Similarly, Enter is bound -to "newlineAndIndent" by default. You can bind it to -something else to get different behavior. To disable special handling -completely and only get a newline character inserted, you can bind it -to false:

      - -
        extraKeys: {"Enter": false}
      - -

      The same works for Tab. If you don't want CodeMirror -to handle it, bind it to false. The default behaviour is -to indent the current line more ("indentMore" command), -and indent it less when shift is held ("indentLess"). -There are also "indentAuto" (smart indent) -and "insertTab" commands provided for alternate -behaviors. Or you can write your own handler function to do something -different altogether.

      - -

      Tabs

      - -

      Handling of tabs changed completely. The display width of tabs can -now be set with the tabSize option, and tabs can -be styled by setting CSS rules -for the cm-tab class.

      - -

      The default width for tabs is now 4, as opposed to the 8 that is -hard-wired into browsers. If you are relying on 8-space tabs, make -sure you explicitly set tabSize: 8 in your options.

      - -
      - - - diff --git a/index.html b/index.html deleted file mode 100644 index 7a5dbf7af4..0000000000 --- a/index.html +++ /dev/null @@ -1,473 +0,0 @@ - - - - - CodeMirror - - - - - - -

      { } CodeMirror

      - -
      - -
      -/* In-browser code editing
      -   made bearable */
      -
      -
      - -
      - -

      CodeMirror is a JavaScript component that - provides a code editor in the browser. When a mode is available for - the language you are coding in, it will color your code, and - optionally help with indentation.

      - -

      A rich programming API and a CSS - theming system are available for customizing CodeMirror to fit your - application, and extending it with new functionality.

      - -
      - -

      Usage demos:

      - - - -

      Real-world uses:

      - - - -
      - -

      Getting the code

      - -

      All of CodeMirror is released under a MIT-style license. To get it, you can download - the latest - release or the current development - snapshot as zip files. To create a custom minified script file, - you can use the compression API.

      - -

      We use git for version control. - The main repository can be fetched in this way:

      - -
      git clone http://marijnhaverbeke.nl/git/codemirror
      - -

      CodeMirror can also be found on GitHub at marijnh/CodeMirror. - If you plan to hack on the code and contribute patches, the best way - to do it is to create a GitHub fork, and send pull requests.

      - -

      Documentation

      - -

      The manual is your first stop for - learning how to use this library. It starts with a quick explanation - of how to use the editor, and then describes the API in detail.

      - -

      For those who want to learn more about the code, there is - an overview of the internals available. - The source code - itself is, for the most part, also well commented.

      - -

      Support and bug reports

      - -

      Community discussion, questions, and informal bug reporting is - done on - the CodeMirror - Google group. There is a separate - group, CodeMirror-announce, - which is lower-volume, and is only used for major announcements—new - versions and such. These will be cross-posted to both groups, so you - don't need to subscribe to both.

      - -

      Though bug reports through e-mail are responded to, the preferred - way to report bugs is to use - the Github - issue tracker. Before reporting a - bug, read these pointers. Also, - the issue tracker is for bugs, not requests for help.

      - -

      When none of these seem fitting, you can - simply e-mail the maintainer - directly.

      - -

      Supported browsers

      - -

      The following desktop browsers are able to run CodeMirror:

      - -
        -
      • Firefox 2 or higher
      • -
      • Chrome, any version
      • -
      • Safari 3 or higher
      • -
      • Opera 9 or higher (with some key-handling problems on OS X)
      • -
      • Internet Explorer 7 or higher in standards mode
        - (So not quirks mode. But quasi-standards mode with a - transitional doctype is also flaky. <!doctype - html> is recommended.)
      • -
      - -

      I am not actively testing against every new browser release, and - vendors have a habit of introducing bugs all the time, so I am - relying on the community to tell me when something breaks. - See here for information on how to contact - me.

      - -

      Mobile browsers mostly kind of work, but, because of limitations - and their fundamentally different UI assumptions, show a lot of - quirks that are hard to work around.

      - -

      Commercial support

      - -

      CodeMirror is developed and maintained by me, Marijn Haverbeke, - in my own time. If your company is getting value out of CodeMirror, - please consider purchasing a support contract.

      - -
        -
      • You'll be funding further work on CodeMirror.
      • -
      • You ensure that you get a quick response when you have a - problem, even when I am otherwise busy.
      • -
      - -

      CodeMirror support contracts exist in two - forms—basic at €100 per month, - and premium at €500 per - month. Contact me for further - information.

      - -
      - -
      - - Download the latest release - -

      Support CodeMirror

      - - - - - -

      Reading material

      - - - -

      Releases

      - -

      19-09-2012: Version 2.34:

      - -
        -
      • New mode: Common Lisp.
      • -
      • Fix right-click select-all on most browsers.
      • -
      • Change the way highlighting happens:
          Saves memory and CPU cycles.
          compareStates is no longer needed.
          onHighlightComplete no longer works.
      • -
      • Integrate mode (Markdown, XQuery, CSS, sTex) tests in central testsuite.
      • -
      • Add a CodeMirror.version property.
      • -
      • More robust handling of nested modes in formatting and closetag plug-ins.
      • -
      • Un/redo now preserves marked text and bookmarks.
      • -
      • Full list of patches.
      • -
      - -

      19-09-2012: Version 3.0, beta 1:

      - -

      BETA release, new major version. Only partially - backwards-compatible. See - the upgrading - guide for more information. Major new features are:

      - -
        -
      • Bi-directional text support.
      • -
      • More powerful gutter model.
      • -
      • Support for arbitrary text/widget height.
      • -
      • In-line widgets.
      • -
      • Generalized event handling.
      • -
      - -

      23-08-2012: Version 2.33:

      - -
        -
      • New mode: Sieve.
      • -
      • New getViewPort and onViewportChange API.
      • -
      • Configurable cursor blink rate.
      • -
      • Make binding a key to false disabling handling (again).
      • -
      • Show non-printing characters as red dots.
      • -
      • More tweaks to the scrolling model.
      • -
      • Expanded testsuite. Basic linter added.
      • -
      • Remove most uses of innerHTML. Remove CodeMirror.htmlEscape.
      • -
      • Full list of patches.
      • -
      - -

      23-07-2012: Version 2.32:

      - -

      Emergency fix for a bug where an editor with - line wrapping on IE will break when there is no - scrollbar.

      - -

      20-07-2012: Version 2.31:

      - - - -

      22-06-2012: Version 2.3:

      - -
        -
      • New scrollbar implementation. Should flicker less. Changes DOM structure of the editor.
      • -
      • New theme: vibrant-ink.
      • -
      • Many extensions to the VIM keymap (including text objects).
      • -
      • Add mode-multiplexing utility script.
      • -
      • Fix bug where right-click paste works in read-only mode.
      • -
      • Add a getScrollInfo method.
      • -
      • Lots of other fixes.
      • -
      - -

      23-05-2012: Version 2.25:

      - -
        -
      • New mode: Erlang.
      • -
      • Remove xmlpure mode (use xml.js).
      • -
      • Fix line-wrapping in Opera.
      • -
      • Fix X Windows middle-click paste in Chrome.
      • -
      • Fix bug that broke pasting of huge documents.
      • -
      • Fix backspace and tab key repeat in Opera.
      • -
      - -

      23-04-2012: Version 2.24:

      - -
        -
      • Drop support for Internet Explorer 6.
      • -
      • New - modes: Shell, Tiki - wiki, Pig Latin.
      • -
      • New themes: Ambiance, Blackboard.
      • -
      • More control over drag/drop - with dragDrop - and onDragEvent - options.
      • -
      • Make HTML mode a bit less pedantic.
      • -
      • Add compoundChange API method.
      • -
      • Several fixes in undo history and line hiding.
      • -
      • Remove (broken) support for catchall in key maps, - add nofallthrough boolean field instead.
      • -
      - -

      26-03-2012: Version 2.23:

      - -
        -
      • Change default binding for tab [more] - -
      • -
      • New modes: XQuery and VBScript.
      • -
      • Two new themes: lesser-dark and xq-dark.
      • -
      • Differentiate between background and text styles in setLineClass.
      • -
      • Fix drag-and-drop in IE9+.
      • -
      • Extend charCoords - and cursorCoords with a mode argument.
      • -
      • Add autofocus option.
      • -
      • Add findMarksAt method.
      • -
      - -

      27-02-2012: Version 2.22:

      - - - -

      27-01-2012: Version 2.21:

      - -
        -
      • Added LESS, MySQL, - Go, and Verilog modes.
      • -
      • Add smartIndent - option.
      • -
      • Support a cursor in readOnly-mode.
      • -
      • Support assigning multiple styles to a token.
      • -
      • Use a new approach to drawing the selection.
      • -
      • Add scrollTo method.
      • -
      • Allow undo/redo events to span non-adjacent lines.
      • -
      • Lots and lots of bugfixes.
      • -
      - -

      20-12-2011: Version 2.2:

      - - - -

      Older releases...

      - -
      - -
       
      - -
      - - -
      - - - diff --git a/js/.DS_Store b/js/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ceee7fe40a78aee54b4b826699f1b5b94dd199a9 GIT binary patch literal 6148 zcmeHK%SyvQ6g{IAs)7{X%Pid}_yY)bG>6j zR3Iww*A$SsyN(QF{t8s*ZyzHJIhzX{;0DvP(Rh%J#u@v|VsXl{_pZh(V8i6dFr$A# z?mLs^^$W|E$ui)Ia!-yK;~O$6BOcfLCexVyBVD0{%VK)_7E`VfO^`FL$A;`gqnCH^o?0ri9S`HZIfq;f#xL|P09G&s8E%wV{#SDDYN`# z?6`JC7ds~zHS)F!Tr(0QC()+Hv(pfV>_~Ua=UTZM#-fa%8k?<9K}{x9xsNNYb1O2( zF;wGPMh_=ALKo-61Kc8?pHJ?oqRA3t9qN4iI`bs!YEQn{9~$>;vG%IRSVsk-0#Sia z1!R3lSOn96rAJ*lSm_af*kZF9ujTVVe3F1^z|te{(42QAdRG^Y7|y%1Jqi8NfTc(8 z4i^p|EwM+Kq+bp;mv-;V76C(Y;ox>NiY6^IJ_D+OGud(k~+ zN?~uEOHTIMgyoV&O!}1`)rD2qj`c#e;$0Rs-b>_ym$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIk*Y|peR=07!nd> zJ?rFuhz^DdtcpejM?+vV1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONfXX4j2s96b z!9Xq~BePhcD784hv?w`MAuYcsTOl*ABsH%jGe0jeC#O;&CpE1^At@&@FB{m^h3Xp8 P6srH>UXfvt`~M#R%-A0@ literal 0 HcmV?d00001 diff --git a/js/._.jshintrc b/js/._.jshintrc new file mode 100644 index 0000000000000000000000000000000000000000..b43092fa16c9c9668504512e5ac504d5f698207c GIT binary patch literal 4096 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