diff --git a/.gitignore b/.gitignore index b720b9be07..f91c241f20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ /node_modules /npm-debug.log -test.html +/test*.html .tern-* *~ *.swp diff --git a/AUTHORS b/AUTHORS index 2ca41e67b7..9d62d48e63 100644 --- a/AUTHORS +++ b/AUTHORS @@ -24,6 +24,7 @@ Alexander Solovyov Alexandre Bique alexey-k Alex Piggott +Aliaksei Chapyzhenka Amsul amuntean Amy @@ -82,6 +83,7 @@ Chris Granger Chris Houseknecht Chris Morgan Christian Oyarzun +Christian Petrov Christopher Brown ciaranj CodeAnimal @@ -111,6 +113,7 @@ Deep Thought Devon Carew dignifiedquire Dimage Sapelkin +Dmitry Kiselyov domagoj412 Dominator008 Domizio Demichelis @@ -149,6 +152,7 @@ Gautam Mehta gekkoe Gerard Braad Gergely Hegykozi +Giovanni Calò Glenn Jorde Glenn Ruehle Golevka @@ -235,6 +239,7 @@ Konstantin Lopuhin koops ks-ifware kubelsmieci +KwanEsq Lanfei Lanny Laszlo Vidacs @@ -289,6 +294,7 @@ Michael Lehenbauer Michael Zhou Mighty Guava Miguel Castillo +mihailik Mike Mike Brevoort Mike Diaz @@ -306,6 +312,7 @@ Nathan Williams ndr nerbert nextrevision +ngn nguillaumin Ng Zhi An Nicholas Bollweg @@ -344,6 +351,7 @@ Randall Mason Randy Burden Randy Edmunds Rasmus Erik Voel Jensen +Ray Ratchup Richard van der Meer Richard Z.H. Wang Robert Crossfield @@ -423,5 +431,6 @@ YNH Webdev Yunchi Luo Yuvi Panda Zachary Dremann +Zhang Hao zziuni 魏鹏刚 diff --git a/README.md b/README.md index 42b06f7474..bc6e7f5c71 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # CodeMirror [![Build Status](https://travis-ci.org/codemirror/CodeMirror.svg)](https://travis-ci.org/codemirror/CodeMirror) -[![NPM version](https://img.shields.io/npm/v/codemirror.svg)](https://www.npmjs.org/package/codemirror) +[![NPM version](https://img.shields.io/npm/v/codemirror.svg)](https://www.npmjs.org/package/codemirror) +[Funding status: ![maintainer happiness](https://marijnhaverbeke.nl/fund/status_s.png)](https://marijnhaverbeke.nl/fund/) CodeMirror is a JavaScript component that provides a code editor in the browser. When a mode is available for the language you are coding diff --git a/addon/edit/closebrackets.js b/addon/edit/closebrackets.js index f6b42f02d3..ff4bb3f733 100644 --- a/addon/edit/closebrackets.js +++ b/addon/edit/closebrackets.js @@ -10,6 +10,7 @@ mod(CodeMirror); })(function(CodeMirror) { var DEFAULT_BRACKETS = "()[]{}''\"\""; + var DEFAULT_TRIPLES = "'\""; var DEFAULT_EXPLODE_ON_ENTER = "[]{}"; var SPACE_CHAR_REGEX = /\s/; @@ -19,13 +20,14 @@ if (old != CodeMirror.Init && old) cm.removeKeyMap("autoCloseBrackets"); if (!val) return; - var pairs = DEFAULT_BRACKETS, explode = DEFAULT_EXPLODE_ON_ENTER; + var pairs = DEFAULT_BRACKETS, triples = DEFAULT_TRIPLES, explode = DEFAULT_EXPLODE_ON_ENTER; if (typeof val == "string") pairs = val; else if (typeof val == "object") { if (val.pairs != null) pairs = val.pairs; + if (val.triples != null) triples = val.triples; if (val.explode != null) explode = val.explode; } - var map = buildKeymap(pairs); + var map = buildKeymap(pairs, triples); if (explode) map.Enter = buildExplodeHandler(explode); cm.addKeyMap(map); }); @@ -52,7 +54,7 @@ } } - function buildKeymap(pairs) { + function buildKeymap(pairs, triples) { var map = { name : "autoCloseBrackets", Backspace: function(cm) { @@ -85,7 +87,7 @@ curType = "skipThree"; else curType = "skip"; - } else if (left == right && cur.ch > 1 && + } else if (left == right && cur.ch > 1 && triples.indexOf(left) >= 0 && cm.getRange(Pos(cur.line, cur.ch - 2), cur) == left + left && (cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != left)) { curType = "addFour"; diff --git a/addon/edit/closetag.js b/addon/edit/closetag.js index 369bea30c4..e68d52d9dc 100644 --- a/addon/edit/closetag.js +++ b/addon/edit/closetag.js @@ -131,7 +131,7 @@ function autoCloseSlash(cm) { if (cm.getOption("disableInput")) return CodeMirror.Pass; - autoCloseCurrent(cm, true); + return autoCloseCurrent(cm, true); } CodeMirror.commands.closeTag = function(cm) { return autoCloseCurrent(cm); }; diff --git a/addon/fold/foldgutter.js b/addon/fold/foldgutter.js index 3359476796..199120c726 100644 --- a/addon/fold/foldgutter.js +++ b/addon/fold/foldgutter.js @@ -94,20 +94,26 @@ } function onGutterClick(cm, line, gutter) { - var opts = cm.state.foldGutter.options; + var state = cm.state.foldGutter; + if (!state) return; + var opts = state.options; if (gutter != opts.gutter) return; cm.foldCode(Pos(line, 0), opts.rangeFinder); } function onChange(cm) { - var state = cm.state.foldGutter, opts = cm.state.foldGutter.options; + var state = cm.state.foldGutter; + if (!state) return; + var opts = state.options; state.from = state.to = 0; clearTimeout(state.changeUpdate); state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600); } function onViewportChange(cm) { - var state = cm.state.foldGutter, opts = cm.state.foldGutter.options; + var state = cm.state.foldGutter; + if (!state) return; + var opts = state.options; clearTimeout(state.changeUpdate); state.changeUpdate = setTimeout(function() { var vp = cm.getViewport(); @@ -129,7 +135,9 @@ } function onFold(cm, from) { - var state = cm.state.foldGutter, line = from.line; + var state = cm.state.foldGutter; + if (!state) return; + var line = from.line; if (line >= state.from && line < state.to) updateFoldInfo(cm, line, line + 1); } diff --git a/addon/hint/show-hint.js b/addon/hint/show-hint.js index fda5ffaa16..f544619428 100644 --- a/addon/hint/show-hint.js +++ b/addon/hint/show-hint.js @@ -24,6 +24,18 @@ return cm.showHint(newOpts); }; + var asyncRunID = 0; + function retrieveHints(getter, cm, options, then) { + if (getter.async) { + var id = ++asyncRunID; + getter(cm, function(hints) { + if (asyncRunID == id) then(hints); + }, options); + } else { + then(getter(cm, options)); + } + } + CodeMirror.defineExtension("showHint", function(options) { // We want a single cursor position. if (this.listSelections().length > 1 || this.somethingSelected()) return; @@ -34,10 +46,7 @@ if (!getHints) return; CodeMirror.signal(this, "startCompletion", this); - if (getHints.async) - getHints(this, function(hints) { completion.showHints(hints); }, completion.options); - else - return completion.showHints(getHints(this, completion.options)); + return retrieveHints(getHints, this, completion.options, function(hints) { completion.showHints(hints); }); }); function Completion(cm, options) { @@ -102,11 +111,7 @@ function update() { if (finished) return; CodeMirror.signal(data, "update"); - var getHints = completion.options.hint; - if (getHints.async) - getHints(completion.cm, finishUpdate, completion.options); - else - finishUpdate(getHints(completion.cm, completion.options)); + retrieveHints(completion.options.hint, completion.cm, completion.options, finishUpdate); } function finishUpdate(data_) { data = data_; diff --git a/addon/hint/sql-hint.js b/addon/hint/sql-hint.js index 92c889e139..5b0cc7696f 100644 --- a/addon/hint/sql-hint.js +++ b/addon/hint/sql-hint.js @@ -26,9 +26,26 @@ return CodeMirror.resolveMode(mode).keywords; } + function getText(item) { + return typeof item == "string" ? item : item.text; + } + + function getItem(list, item) { + if (!list.slice) return list[item]; + for (var i = list.length - 1; i >= 0; i--) if (getText(list[i]) == item) + return list[i]; + } + + function shallowClone(object) { + var result = {}; + for (var key in object) if (object.hasOwnProperty(key)) + result[key] = object[key]; + return result; + } + function match(string, word) { var len = string.length; - var sub = word.substr(0, len); + var sub = getText(word).substr(0, len); return string.toUpperCase() === sub.toUpperCase(); } @@ -44,53 +61,81 @@ } } + function cleanName(name) { + // Get rid name from backticks(`) and preceding dot(.) + if (name.charAt(0) == ".") { + name = name.substr(1); + } + return name.replace(/`/g, ""); + } + + function insertBackticks(name) { + var nameParts = getText(name).split("."); + for (var i = 0; i < nameParts.length; i++) + nameParts[i] = "`" + nameParts[i] + "`"; + var escaped = nameParts.join("."); + if (typeof name == "string") return escaped; + name = shallowClone(name); + name.text = escaped; + return name; + } + function nameCompletion(cur, token, result, editor) { - var useBacktick = (token.string.charAt(0) == "`"); - var string = token.string.substr(1); - var prevToken = editor.getTokenAt(Pos(cur.line, token.start)); - if (token.string.charAt(0) == "." || prevToken.string == "."){ - //Suggest colunm names - if (prevToken.string == ".") { - var prevToken = editor.getTokenAt(Pos(cur.line, token.start - 1)); - } - var table = prevToken.string; - //Check if backtick is used in table name. If yes, use it for columns too. - var useBacktickTable = false; - if (table.match(/`/g)) { - useBacktickTable = true; - table = table.replace(/`/g, ""); - } - //Check if table is available. If not, find table by Alias - if (!tables.hasOwnProperty(table)) - table = findTableByAlias(table, editor); - var columns = tables[table]; - if (!columns) return; - - if (useBacktick) { - addMatches(result, string, columns, function(w) {return "`" + w + "`";}); - } - else if(useBacktickTable) { - addMatches(result, string, columns, function(w) {return ".`" + w + "`";}); - } - else { - addMatches(result, string, columns, function(w) {return "." + w;}); + // Try to complete table, colunm names and return start position of completion + var useBacktick = false; + var nameParts = []; + var start = token.start; + var cont = true; + while (cont) { + cont = (token.string.charAt(0) == "."); + useBacktick = useBacktick || (token.string.charAt(0) == "`"); + + start = token.start; + nameParts.unshift(cleanName(token.string)); + + token = editor.getTokenAt(Pos(cur.line, token.start)); + if (token.string == ".") { + cont = true; + token = editor.getTokenAt(Pos(cur.line, token.start)); } } - else { - //Suggest table names or colums in defaultTable - while (token.start && string.charAt(0) == ".") { - token = editor.getTokenAt(Pos(cur.line, token.start - 1)); - string = token.string + string; - } - if (useBacktick) { - addMatches(result, string, tables, function(w) {return "`" + w + "`";}); - addMatches(result, string, defaultTable, function(w) {return "`" + w + "`";}); - } - else { - addMatches(result, string, tables, function(w) {return w;}); - addMatches(result, string, defaultTable, function(w) {return w;}); - } + + // Try to complete table names + var string = nameParts.join("."); + addMatches(result, string, tables, function(w) { + return useBacktick ? insertBackticks(w) : w; + }); + + // Try to complete columns from defaultTable + addMatches(result, string, defaultTable, function(w) { + return useBacktick ? insertBackticks(w) : w; + }); + + // Try to complete columns + string = nameParts.pop(); + var table = nameParts.join("."); + + // Check if table is available. If not, find table by Alias + if (!getItem(tables, table)) + table = findTableByAlias(table, editor); + + var columns = getItem(tables, table); + if (columns && Array.isArray(tables) && columns.columns) + columns = columns.columns; + + if (columns) { + addMatches(result, string, columns, function(w) { + if (typeof w == "string") { + w = table + "." + w; + } else { + w = shallowClone(w); + w.text = table + "." + w.text; + } + return useBacktick ? insertBackticks(w) : w; + }); } + + return start; } function eachWord(lineText, f) { @@ -150,12 +195,10 @@ var lineText = query[i]; eachWord(lineText, function(word) { var wordUpperCase = word.toUpperCase(); - if (wordUpperCase === aliasUpperCase && tables.hasOwnProperty(previousWord)) { - table = previousWord; - } - if (wordUpperCase !== CONS.ALIAS_KEYWORD) { + if (wordUpperCase === aliasUpperCase && getItem(tables, previousWord)) + table = previousWord; + if (wordUpperCase !== CONS.ALIAS_KEYWORD) previousWord = word; - } }); if (table) break; } @@ -165,7 +208,7 @@ CodeMirror.registerHelper("hint", "sql", function(editor, options) { tables = (options && options.tables) || {}; var defaultTableName = options && options.defaultTable; - defaultTable = (defaultTableName && tables[defaultTableName] || []); + defaultTable = (defaultTableName && getItem(tables, defaultTableName)) || []; keywords = keywords || getKeywords(editor); var cur = editor.getCursor(); @@ -185,7 +228,7 @@ search = ""; } if (search.charAt(0) == "." || search.charAt(0) == "`") { - nameCompletion(cur, token, result, editor); + start = nameCompletion(cur, token, result, editor); } else { addMatches(result, search, tables, function(w) {return w;}); addMatches(result, search, defaultTable, function(w) {return w;}); diff --git a/addon/lint/lint.js b/addon/lint/lint.js index 66f187e22e..18eb709016 100644 --- a/addon/lint/lint.js +++ b/addon/lint/lint.js @@ -46,6 +46,7 @@ } var poll = setInterval(function() { if (tooltip) for (var n = node;; n = n.parentNode) { + if (n && n.nodeType == 11) n = n.host; if (n == document.body) return; if (!n) { hide(); break; } } @@ -119,7 +120,7 @@ function startLinting(cm) { var state = cm.state.lint, options = state.options; var passOptions = options.options || options; // Support deprecated passing of `options` property in options - if (options.async) + if (options.async || options.getAnnotations.async) options.getAnnotations(cm.getValue(), updateLinting, passOptions, cm); else updateLinting(cm, options.getAnnotations(cm.getValue(), passOptions, cm)); diff --git a/addon/merge/merge.js b/addon/merge/merge.js index 3e9df42fb7..f1f3aafc49 100644 --- a/addon/merge/merge.js +++ b/addon/merge/merge.js @@ -31,8 +31,6 @@ insert: "CodeMirror-merge-r-inserted", del: "CodeMirror-merge-r-deleted", connect: "CodeMirror-merge-r-connect"}; - if (mv.options.connect == "align") - this.aligners = []; } DiffView.prototype = { @@ -42,7 +40,8 @@ this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: !this.mv.options.allowEditingOriginals}, copyObj(options))); this.diff = getDiff(asString(orig), asString(options.value)); - this.diffOutOfDate = false; + this.chunks = getChunks(this.diff); + this.diffOutOfDate = this.dealigned = false; this.showDifferences = options.showDifferences !== false; this.forceUpdate = registerUpdate(this); @@ -61,16 +60,20 @@ function ensureDiff(dv) { if (dv.diffOutOfDate) { dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue()); + dv.chunks = getChunks(dv.diff); dv.diffOutOfDate = false; CodeMirror.signal(dv.edit, "updateDiff", dv.diff); } } + var updating = false; function registerUpdate(dv) { var edit = {from: 0, to: 0, marked: []}; var orig = {from: 0, to: 0, marked: []}; - var debounceChange; + var debounceChange, updatingFast = false; function update(mode) { + updating = true; + updatingFast = false; if (mode == "full") { if (dv.svg) clear(dv.svg); if (dv.copyButtons) clear(dv.copyButtons); @@ -84,26 +87,38 @@ updateMarks(dv.orig, dv.diff, orig, DIFF_DELETE, dv.classes); } makeConnections(dv); + + if (dv.mv.options.connect == "align") + alignChunks(dv); + updating = false; } - function set(slow) { + function setDealign(fast) { + if (updating) return; + dv.dealigned = true; + set(fast); + } + function set(fast) { + if (updating || updatingFast) return; clearTimeout(debounceChange); - debounceChange = setTimeout(update, slow == true ? 250 : 100); + if (fast === true) updatingFast = true; + debounceChange = setTimeout(update, fast === true ? 20 : 250); } - function change() { + function change(_cm, change) { if (!dv.diffOutOfDate) { dv.diffOutOfDate = true; edit.from = edit.to = orig.from = orig.to = 0; } - set(true); + // Update faster when a line was added/removed + setDealign(change.text.length - 1 != change.to.line - change.from.line); } dv.edit.on("change", change); dv.orig.on("change", change); - dv.edit.on("markerAdded", set); - dv.edit.on("markerCleared", set); - dv.orig.on("markerAdded", set); - dv.orig.on("markerCleared", set); - dv.edit.on("viewportChange", set); - dv.orig.on("viewportChange", set); + dv.edit.on("markerAdded", setDealign); + dv.edit.on("markerCleared", setDealign); + dv.orig.on("markerAdded", setDealign); + dv.orig.on("markerCleared", setDealign); + dv.edit.on("viewportChange", function() { set(false); }); + dv.orig.on("viewportChange", function() { set(false); }); update(); return update; } @@ -134,7 +149,7 @@ } else { var halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen; var mid = editor.lineAtHeight(midY, "local"); - var around = chunkBoundariesAround(dv.diff, mid, type == DIFF_INSERT); + var around = chunkBoundariesAround(dv.chunks, mid, type == DIFF_INSERT); var off = getOffsets(editor, type == DIFF_INSERT ? around.edit : around.orig); var offOther = getOffsets(other, type == DIFF_INSERT ? around.orig : around.edit); var ratio = (midY - off.top) / (off.bot - off.top); @@ -259,19 +274,6 @@ function makeConnections(dv) { if (!dv.showDifferences) return; - var align = dv.mv.options.connect == "align", oldScrollEdit, oldScrollOrig; - if (align) { - if (!dv.orig.curOp) return dv.orig.operation(function() { - makeConnections(dv); - }); - oldScrollEdit = dv.edit.getScrollInfo().top; - oldScrollOrig = dv.orig.getScrollInfo().top; - for (var i = 0; i < dv.aligners.length; i++) - dv.aligners[i].clear(); - dv.aligners.length = 0; - var extraSpaceAbove = {edit: 0, orig: 0}; - } - if (dv.svg) { clear(dv.svg); var w = dv.gap.offsetWidth; @@ -281,34 +283,118 @@ var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport(); var sTopEdit = dv.edit.getScrollInfo().top, sTopOrig = dv.orig.getScrollInfo().top; - iterateChunks(dv.diff, function(topOrig, botOrig, topEdit, botEdit) { - if (topEdit <= vpEdit.to && botEdit >= vpEdit.from && - topOrig <= vpOrig.to && botOrig >= vpOrig.from) - drawConnectorsForChunk(dv, topOrig, botOrig, topEdit, botEdit, sTopOrig, sTopEdit, w); - if (align && (topEdit <= vpEdit.to || topOrig <= vpOrig.to)) { - var above = (botEdit < vpEdit.from && botOrig < vpOrig.from); - alignChunks(dv, topOrig, botOrig, topEdit, botEdit, above && extraSpaceAbove); + for (var i = 0; i < dv.chunks.length; i++) { + var ch = dv.chunks[i]; + if (ch.editFrom <= vpEdit.to && ch.editTo >= vpEdit.from && + ch.origFrom <= vpOrig.to && ch.origTo >= vpOrig.from) + drawConnectorsForChunk(dv, ch, sTopOrig, sTopEdit, w); + } + } + + function getMatchingOrigLine(editLine, chunks) { + var editStart = 0, origStart = 0; + for (var i = 0; i < chunks.length; i++) { + var chunk = chunks[i]; + if (chunk.editTo > editLine && chunk.editFrom <= editLine) return null; + if (chunk.editFrom > editLine) break; + editStart = chunk.editTo; + origStart = chunk.origTo; + } + return origStart + (editLine - editStart); + } + + function findAlignedLines(dv, other) { + var linesToAlign = []; + for (var i = 0; i < dv.chunks.length; i++) { + var chunk = dv.chunks[i]; + linesToAlign.push([chunk.origTo, chunk.editTo, other ? getMatchingOrigLine(chunk.editTo, other.chunks) : null]); + } + if (other) { + for (var i = 0; i < other.chunks.length; i++) { + var chunk = other.chunks[i]; + for (var j = 0; j < linesToAlign.length; j++) { + var align = linesToAlign[j]; + if (align[1] == chunk.editTo) { + j = -1; + break; + } else if (align[1] > chunk.editTo) { + break; + } + } + if (j > -1) + linesToAlign.splice(j - 1, 0, [getMatchingOrigLine(chunk.editTo, dv.chunks), chunk.editTo, chunk.origTo]); } + } + return linesToAlign; + } + + function alignChunks(dv, force) { + if (!dv.dealigned && !force) return; + if (!dv.orig.curOp) return dv.orig.operation(function() { + alignChunks(dv, force); }); - if (align) { - if (extraSpaceAbove.edit) - dv.aligners.push(padBelow(dv.edit, 0, extraSpaceAbove.edit)); - if (extraSpaceAbove.orig) - dv.aligners.push(padBelow(dv.orig, 0, extraSpaceAbove.orig)); - dv.edit.scrollTo(null, oldScrollEdit); - dv.orig.scrollTo(null, oldScrollOrig); + + dv.dealigned = false; + var other = dv.mv.left == dv ? dv.mv.right : dv.mv.left; + if (other) { + ensureDiff(other); + other.dealigned = false; } + var linesToAlign = findAlignedLines(dv, other); + + // Clear old aligners + var aligners = dv.mv.aligners; + for (var i = 0; i < aligners.length; i++) + aligners[i].clear(); + aligners.length = 0; + + var cm = [dv.orig, dv.edit], scroll = []; + if (other) cm.push(other.orig); + for (var i = 0; i < cm.length; i++) + scroll.push(cm[i].getScrollInfo().top); + + for (var ln = 0; ln < linesToAlign.length; ln++) + alignLines(cm, linesToAlign[ln], aligners); + + for (var i = 0; i < cm.length; i++) + cm[i].scrollTo(null, scroll[i]); } - function drawConnectorsForChunk(dv, topOrig, botOrig, topEdit, botEdit, sTopOrig, sTopEdit, w) { + function alignLines(cm, lines, aligners) { + var maxOffset = 0, offset = []; + for (var i = 0; i < cm.length; i++) if (lines[i] != null) { + var off = cm[i].heightAtLine(lines[i], "local"); + offset[i] = off; + maxOffset = Math.max(maxOffset, off); + } + for (var i = 0; i < cm.length; i++) if (lines[i] != null) { + var diff = maxOffset - offset[i]; + if (diff > 1) + aligners.push(padAbove(cm[i], lines[i], diff)); + } + } + + function padAbove(cm, line, size) { + var above = true; + if (line > cm.lastLine()) { + line--; + above = false; + } + var elt = document.createElement("div"); + elt.className = "CodeMirror-merge-spacer"; + elt.style.height = size + "px"; elt.style.minWidth = "1px"; + return cm.addLineWidget(line, elt, {height: size, above: above}); + } + + function drawConnectorsForChunk(dv, chunk, sTopOrig, sTopEdit, w) { var flip = dv.type == "left"; - var top = dv.orig.heightAtLine(topOrig, "local") - sTopOrig; + var top = dv.orig.heightAtLine(chunk.origFrom, "local") - sTopOrig; if (dv.svg) { var topLpx = top; - var topRpx = dv.edit.heightAtLine(topEdit, "local") - sTopEdit; + var topRpx = dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit; if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; } - var botLpx = dv.orig.heightAtLine(botOrig, "local") - sTopOrig; - var botRpx = dv.edit.heightAtLine(botEdit, "local") - sTopEdit; + var botLpx = dv.orig.heightAtLine(chunk.origTo, "local") - sTopOrig; + var botRpx = dv.edit.heightAtLine(chunk.editTo, "local") - sTopEdit; if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; } var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx; var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx; @@ -321,48 +407,26 @@ "CodeMirror-merge-copy")); var editOriginals = dv.mv.options.allowEditingOriginals; copy.title = editOriginals ? "Push to left" : "Revert chunk"; - copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig}; + copy.chunk = chunk; copy.style.top = top + "px"; if (editOriginals) { - var topReverse = dv.orig.heightAtLine(topEdit, "local") - sTopEdit; + var topReverse = dv.orig.heightAtLine(chunk.editFrom, "local") - sTopEdit; var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc", "CodeMirror-merge-copy-reverse")); copyReverse.title = "Push to right"; - copyReverse.chunk = {topEdit: topOrig, botEdit: botOrig, topOrig: topEdit, botOrig: botEdit}; + copyReverse.chunk = {editFrom: chunk.origFrom, editTo: chunk.origTo, + origFrom: chunk.editFrom, origTo: chunk.editTo}; copyReverse.style.top = topReverse + "px"; dv.type == "right" ? copyReverse.style.left = "2px" : copyReverse.style.right = "2px"; } } } - function alignChunks(dv, topOrig, botOrig, topEdit, botEdit, aboveViewport) { - var topOrigPx = dv.orig.heightAtLine(topOrig, "local"); - var botOrigPx = dv.orig.heightAtLine(botOrig, "local"); - var topEditPx = dv.edit.heightAtLine(topEdit, "local"); - var botEditPx = dv.edit.heightAtLine(botEdit, "local"); - var origH = botOrigPx -topOrigPx, editH = botEditPx - topEditPx; - var diff = editH - origH; - if (diff > 1) { - if (aboveViewport) aboveViewport.orig += diff; - else dv.aligners.push(padBelow(dv.orig, botOrig - 1, diff)); - } else if (diff < -1) { - if (aboveViewport) aboveViewport.edit -= diff; - else dv.aligners.push(padBelow(dv.edit, botEdit - 1, -diff)); - } - return 0; - } - - function padBelow(cm, line, size) { - var elt = document.createElement("div"); - elt.style.height = size + "px"; elt.style.minWidth = "1px"; - return cm.addLineWidget(line, elt, {height: size}); - } - function copyChunk(dv, to, from, chunk) { if (dv.diffOutOfDate) return; - to.replaceRange(from.getRange(Pos(chunk.topOrig, 0), Pos(chunk.botOrig, 0)), - Pos(chunk.topEdit, 0), Pos(chunk.botEdit, 0)); + to.replaceRange(from.getRange(Pos(chunk.origFrom, 0), Pos(chunk.origTo, 0)), + Pos(chunk.editFrom, 0), Pos(chunk.editTo, 0)); } // Merge view, containing 0, 1, or 2 diff views. @@ -372,16 +436,11 @@ this.options = options; var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight; - if (origLeft && origRight) { - if (options.connect == "align") - throw new Error("connect: \"align\" is not supported for three-way merge views"); - if (options.collapseIdentical) - throw new Error("collapseIdentical option is not supported for three-way merge views"); - } var hasLeft = origLeft != null, hasRight = origRight != null; var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0); var wrap = [], left = this.left = null, right = this.right = null; + var self = this; if (hasLeft) { left = this.left = new DiffView(this, "left"); @@ -410,8 +469,17 @@ if (left) left.init(leftPane, origLeft, options); if (right) right.init(rightPane, origRight, options); - if (options.collapseIdentical) - collapseIdenticalStretches(left || right, options.collapseIdentical); + if (options.collapseIdentical) { + updating = true; + this.editor().operation(function() { + collapseIdenticalStretches(self, options.collapseIdentical); + }); + updating = false; + } + if (options.connect == "align") { + this.aligners = []; + alignChunks(this.left || this.right, true); + } var onResize = function() { if (left) makeConnections(left); @@ -463,10 +531,10 @@ if (this.left) this.left.setShowDifferences(val); }, rightChunks: function() { - return this.right && getChunks(this.right); + if (this.right) { ensureDiff(this.right); return this.right.chunks; } }, leftChunks: function() { - return this.left && getChunks(this.left); + if (this.left) { ensureDiff(this.left); return this.left.chunks; } } }; @@ -494,7 +562,8 @@ return diff; } - function iterateChunks(diff, f) { + function getChunks(diff) { + var chunks = []; var startEdit = 0, startOrig = 0; var edit = Pos(0, 0), orig = Pos(0, 0); for (var i = 0; i < diff.length; ++i) { @@ -506,7 +575,8 @@ var endOff = endOfLineClean(diff, i) ? 1 : 0; var cleanToEdit = edit.line + endOff, cleanToOrig = orig.line + endOff; if (cleanToEdit > cleanFromEdit) { - if (i) f(startOrig, cleanFromOrig, startEdit, cleanFromEdit); + if (i) chunks.push({origFrom: startOrig, origTo: cleanFromOrig, + editFrom: startEdit, editTo: cleanFromEdit}); startEdit = cleanToEdit; startOrig = cleanToOrig; } } else { @@ -514,17 +584,9 @@ } } if (startEdit <= edit.line || startOrig <= orig.line) - f(startOrig, orig.line + 1, startEdit, edit.line + 1); - } - - function getChunks(dv) { - ensureDiff(dv); - var collect = []; - iterateChunks(dv.diff, function(topOrig, botOrig, topEdit, botEdit) { - collect.push({origFrom: topOrig, origTo: botOrig, - editFrom: topEdit, editTo: botEdit}); - }); - return collect; + chunks.push({origFrom: startOrig, origTo: orig.line + 1, + editFrom: startEdit, editTo: edit.line + 1}); + return chunks; } function endOfLineClean(diff, i) { @@ -545,18 +607,19 @@ return last.charCodeAt(last.length - 1) == 10; } - function chunkBoundariesAround(diff, n, nInEdit) { + function chunkBoundariesAround(chunks, n, nInEdit) { var beforeE, afterE, beforeO, afterO; - iterateChunks(diff, function(fromOrig, toOrig, fromEdit, toEdit) { - var fromLocal = nInEdit ? fromEdit : fromOrig; - var toLocal = nInEdit ? toEdit : toOrig; + for (var i = 0; i < chunks.length; i++) { + var chunk = chunks[i]; + var fromLocal = nInEdit ? chunk.editFrom : chunk.origFrom; + var toLocal = nInEdit ? chunk.editTo : chunk.origTo; if (afterE == null) { - if (fromLocal > n) { afterE = fromEdit; afterO = fromOrig; } - else if (toLocal > n) { afterE = toEdit; afterO = toOrig; } + if (fromLocal > n) { afterE = chunk.editFrom; afterO = chunk.origFrom; } + else if (toLocal > n) { afterE = chunk.editTo; afterO = chunk.origTo; } } - if (toLocal <= n) { beforeE = toEdit; beforeO = toOrig; } - else if (fromLocal <= n) { beforeE = fromEdit; beforeO = fromOrig; } - }); + if (toLocal <= n) { beforeE = chunk.editTo; beforeO = chunk.origTo; } + else if (fromLocal <= n) { beforeE = chunk.editFrom; beforeO = chunk.origFrom; } + } return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}}; } @@ -579,25 +642,50 @@ return {mark: mark, clear: clear}; } - function collapseStretch(dv, origStart, editStart, size) { - var mOrig = collapseSingle(dv.orig, origStart, origStart + size); - var mEdit = collapseSingle(dv.edit, editStart, editStart + size); - mOrig.mark.on("clear", function() { mEdit.clear(); }); - mEdit.mark.on("clear", function() { mOrig.clear(); }); + function collapseStretch(size, editors) { + var marks = []; + function clear() { + for (var i = 0; i < marks.length; i++) marks[i].clear(); + } + for (var i = 0; i < editors.length; i++) { + var editor = editors[i]; + var mark = collapseSingle(editor.cm, editor.line, editor.line + size); + marks.push(mark); + mark.mark.on("clear", clear); + } + return marks[0].mark; + } + + function unclearNearChunks(dv, margin, off, clear) { + for (var i = 0; i < dv.chunks.length; i++) { + var chunk = dv.chunks[i]; + for (var l = chunk.editFrom - margin; l < chunk.editTo + margin; l++) { + var pos = l + off; + if (pos >= 0 && pos < clear.length) clear[pos] = false; + } + } } - function collapseIdenticalStretches(dv, margin) { + function collapseIdenticalStretches(mv, margin) { if (typeof margin != "number") margin = 2; - var lastOrig = dv.orig.firstLine(), lastEdit = dv.edit.firstLine(); - iterateChunks(dv.diff, function(topOrig, botOrig, _topEdit, botEdit) { - var identicalSize = topOrig - margin - lastOrig; - if (identicalSize > margin) - collapseStretch(dv, lastOrig, lastEdit, identicalSize); - lastOrig = botOrig + margin; lastEdit = botEdit + margin; - }); - var bottomSize = dv.orig.lastLine() + 1 - lastOrig; - if (bottomSize > margin) - collapseStretch(dv, lastOrig, lastEdit, bottomSize); + var clear = [], edit = mv.editor(), off = edit.firstLine(); + for (var l = off, e = edit.lastLine(); l <= e; l++) clear.push(true); + if (mv.left) unclearNearChunks(mv.left, margin, off, clear); + if (mv.right) unclearNearChunks(mv.right, margin, off, clear); + + for (var i = 0; i < clear.length; i++) { + if (clear[i]) { + var line = i + off; + for (var size = 1; i < clear.length - 1 && clear[i + 1]; i++, size++) {} + if (size > margin) { + var editors = [{line: line, cm: edit}]; + if (mv.left) editors.push({line: getMatchingOrigLine(line, mv.left.chunks), cm: mv.left.orig}); + if (mv.right) editors.push({line: getMatchingOrigLine(line, mv.right.chunks), cm: mv.right.orig}); + var mark = collapseStretch(size, editors); + if (mv.options.onCollapse) mv.options.onCollapse(mv, line, size, mark); + } + } + } } // General utilities diff --git a/addon/scroll/annotatescrollbar.js b/addon/scroll/annotatescrollbar.js index 6dfff1a6a4..54aeacf271 100644 --- a/addon/scroll/annotatescrollbar.js +++ b/addon/scroll/annotatescrollbar.js @@ -11,27 +11,46 @@ })(function(CodeMirror) { "use strict"; - CodeMirror.defineExtension("annotateScrollbar", function(className) { - return new Annotation(this, className); + CodeMirror.defineExtension("annotateScrollbar", function(options) { + if (typeof options == "string") options = {className: options}; + return new Annotation(this, options); }); - function Annotation(cm, className) { + CodeMirror.defineOption("scrollButtonHeight", 0); + + function Annotation(cm, options) { this.cm = cm; - this.className = className; + this.options = options; + this.buttonHeight = options.scrollButtonHeight || cm.getOption("scrollButtonHeight"); this.annotations = []; + this.doRedraw = this.doUpdate = null; this.div = cm.getWrapperElement().appendChild(document.createElement("div")); this.div.style.cssText = "position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none"; this.computeScale(); + function scheduleRedraw(delay) { + clearTimeout(self.doRedraw); + self.doRedraw = setTimeout(function() { self.redraw(); }, delay); + } + var self = this; - cm.on("refresh", this.resizeHandler = function(){ - if (self.computeScale()) self.redraw(); + cm.on("refresh", this.resizeHandler = function() { + clearTimeout(self.doUpdate); + self.doUpdate = setTimeout(function() { + if (self.computeScale()) scheduleRedraw(20); + }, 100); }); + cm.on("markerAdded", this.resizeHandler); + cm.on("markerCleared", this.resizeHandler); + if (options.listenForChanges !== false) + cm.on("change", this.changeHandler = function() { + scheduleRedraw(250); + }); } Annotation.prototype.computeScale = function() { var cm = this.cm; - var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight) / + var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight - this.buttonHeight * 2) / cm.heightAtLine(cm.lastLine() + 1, "local"); if (hScale != this.hScale) { this.hScale = hScale; @@ -44,12 +63,12 @@ this.redraw(); }; - Annotation.prototype.redraw = function() { + Annotation.prototype.redraw = function(compute) { + if (compute !== false) this.computeScale(); var cm = this.cm, hScale = this.hScale; - if (!cm.display.barWidth) return; var frag = document.createDocumentFragment(), anns = this.annotations; - for (var i = 0, nextTop; i < anns.length; i++) { + if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) { var ann = anns[i]; var top = nextTop || cm.charCoords(ann.from, "local").top * hScale; var bottom = cm.charCoords(ann.to, "local").bottom * hScale; @@ -59,11 +78,13 @@ ann = anns[++i]; bottom = cm.charCoords(ann.to, "local").bottom * hScale; } + if (bottom == top) continue; var height = Math.max(bottom - top, 3); var elt = frag.appendChild(document.createElement("div")); - elt.style.cssText = "position: absolute; right: 0px; width: " + Math.max(cm.display.barWidth - 1, 2) + "px; top: " + top + "px; height: " + height + "px"; - elt.className = this.className; + elt.style.cssText = "position: absolute; right: 0px; width: " + Math.max(cm.display.barWidth - 1, 2) + "px; top: " + + (top + this.buttonHeight) + "px; height: " + height + "px"; + elt.className = this.options.className; } this.div.textContent = ""; this.div.appendChild(frag); @@ -71,6 +92,9 @@ Annotation.prototype.clear = function() { this.cm.off("refresh", this.resizeHandler); + this.cm.off("markerAdded", this.resizeHandler); + this.cm.off("markerCleared", this.resizeHandler); + if (this.changeHandler) this.cm.off("change", this.changeHandler); this.div.parentNode.removeChild(this.div); }; }); diff --git a/addon/search/matchesonscrollbar.js b/addon/search/matchesonscrollbar.js index 937d3f786e..dbd67a4a5d 100644 --- a/addon/search/matchesonscrollbar.js +++ b/addon/search/matchesonscrollbar.js @@ -11,13 +11,18 @@ })(function(CodeMirror) { "use strict"; - CodeMirror.defineExtension("showMatchesOnScrollbar", function(query, caseFold, className) { - return new SearchAnnotation(this, query, caseFold, className); + CodeMirror.defineExtension("showMatchesOnScrollbar", function(query, caseFold, options) { + if (typeof options == "string") options = {className: options}; + if (!options) options = {}; + return new SearchAnnotation(this, query, caseFold, options); }); - function SearchAnnotation(cm, query, caseFold, className) { + function SearchAnnotation(cm, query, caseFold, options) { this.cm = cm; - this.annotation = cm.annotateScrollbar(className || "CodeMirror-search-match"); + var annotateOptions = {listenForChanges: false}; + for (var prop in options) annotateOptions[prop] = options[prop]; + if (!annotateOptions.className) annotateOptions.className = "CodeMirror-search-match"; + this.annotation = cm.annotateScrollbar(annotateOptions); this.query = query; this.caseFold = caseFold; this.gap = {from: cm.firstLine(), to: cm.lastLine() + 1}; diff --git a/addon/selection/selection-pointer.js b/addon/selection/selection-pointer.js index 8cc0fc6860..ef5e404ad3 100644 --- a/addon/selection/selection-pointer.js +++ b/addon/selection/selection-pointer.js @@ -16,6 +16,7 @@ if (data) { CodeMirror.off(cm.getWrapperElement(), "mousemove", data.mousemove); CodeMirror.off(cm.getWrapperElement(), "mouseout", data.mouseout); + CodeMirror.off(window, "scroll", data.windowScroll); cm.off("cursorActivity", reset); cm.off("scroll", reset); cm.state.selectionPointer = null; @@ -26,12 +27,14 @@ value: typeof val == "string" ? val : "default", mousemove: function(event) { mousemove(cm, event); }, mouseout: function(event) { mouseout(cm, event); }, + windowScroll: function() { reset(cm); }, rects: null, mouseX: null, mouseY: null, willUpdate: false }; CodeMirror.on(cm.getWrapperElement(), "mousemove", data.mousemove); CodeMirror.on(cm.getWrapperElement(), "mouseout", data.mouseout); + CodeMirror.on(window, "scroll", data.windowScroll); cm.on("cursorActivity", reset); cm.on("scroll", reset); } diff --git a/addon/tern/tern.js b/addon/tern/tern.js index 86729e2d3f..b049549d35 100644 --- a/addon/tern/tern.js +++ b/addon/tern/tern.js @@ -130,6 +130,13 @@ data = self.options.responseFilter(doc, query, request, error, data); c(error, data); }); + }, + + destroy: function () { + if (this.worker) { + this.worker.terminate(); + this.worker = null; + } } }; @@ -252,7 +259,9 @@ tip.appendChild(document.createTextNode(" — " + data.doc)); if (data.url) { tip.appendChild(document.createTextNode(" ")); - tip.appendChild(elt("a", null, "[docs]")).href = data.url; + var child = tip.appendChild(elt("a", null, "[docs]")); + child.href = data.url; + child.target = "_blank"; } } tempTooltip(cm, tip); @@ -582,15 +591,33 @@ // Tooltips function tempTooltip(cm, content) { + if (cm.state.ternTooltip) remove(cm.state.ternTooltip); var where = cm.cursorCoords(); - var tip = makeTooltip(where.right + 1, where.bottom, content); + var tip = cm.state.ternTooltip = makeTooltip(where.right + 1, where.bottom, content); + function maybeClear() { + old = true; + if (!mouseOnTip) clear(); + } function clear() { + cm.state.ternTooltip = null; if (!tip.parentNode) return; cm.off("cursorActivity", clear); + cm.off('blur', clear); + cm.off('scroll', clear); fadeOut(tip); } - setTimeout(clear, 1700); + var mouseOnTip = false, old = false; + CodeMirror.on(tip, "mousemove", function() { mouseOnTip = true; }); + CodeMirror.on(tip, "mouseout", function(e) { + if (!CodeMirror.contains(tip, e.relatedTarget || e.toElement)) { + if (old) clear(); + else mouseOnTip = false; + } + }); + setTimeout(maybeClear, 1700); cm.on("cursorActivity", clear); + cm.on('blur', clear); + cm.on('scroll', clear); } function makeTooltip(x, y, content) { @@ -631,7 +658,7 @@ // Worker wrapper function WorkerServer(ts) { - var worker = new Worker(ts.options.workerScript); + var worker = ts.worker = new Worker(ts.options.workerScript); worker.postMessage({type: "init", defs: ts.options.defs, plugins: ts.options.plugins, diff --git a/bin/release b/bin/release index ba6630aaa8..c6b97c1c03 100755 --- a/bin/release +++ b/bin/release @@ -41,6 +41,6 @@ rewrite("doc/compress.html", function(cmp) { }); rewrite("index.html", function(index) { - return index.replace(/version \d+\.\d+<\/strong>/, - "version " + simple + ""); + return index.replace(/\.zip">\d+\.\d+<\/a>/, + ".zip>" + simple + ""); }); diff --git a/bower.json b/bower.json index d378331c4c..dad8fe2e02 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version":"4.12.0", + "version":"4.13.0", "main": ["lib/codemirror.js", "lib/codemirror.css"], "ignore": [ "**/.*", diff --git a/demo/merge.html b/demo/merge.html index dad1952757..c60e653297 100644 --- a/demo/merge.html +++ b/demo/merge.html @@ -8,10 +8,17 @@ + + + + + +
+ +

Forth mode

+ +
+
+ + + +

Simple mode that handle Forth-Syntax (Forth on WikiPedia).

+ +

MIME types defined: text/x-forth.

+ +
diff --git a/mode/go/go.js b/mode/go/go.js index 173e034d0c..b121f4e6eb 100644 --- a/mode/go/go.js +++ b/mode/go/go.js @@ -117,6 +117,7 @@ CodeMirror.defineMode("go", function(config) { return state.context = new Context(state.indented, col, type, null, state.context); } function popContext(state) { + if (!state.context.prev) return; var t = state.context.type; if (t == ")" || t == "]" || t == "}") state.indented = state.context.indented; diff --git a/mode/idl/idl.js b/mode/idl/idl.js index 15c852e36c..07308d71dc 100644 --- a/mode/idl/idl.js +++ b/mode/idl/idl.js @@ -275,7 +275,7 @@ // Handle non-detected items stream.next(); - return 'error'; + return null; }; CodeMirror.defineMode('idl', function() { diff --git a/mode/index.html b/mode/index.html index c933e1e943..04167a5a54 100644 --- a/mode/index.html +++ b/mode/index.html @@ -51,6 +51,7 @@
  • ECL
  • Eiffel
  • Erlang
  • +
  • Forth
  • Fortran
  • F#
  • Gas (AT&T-style assembly)
  • @@ -108,6 +109,7 @@
  • Smarty/HTML mixed
  • Solr
  • Soy
  • +
  • Stylus
  • SQL (several dialects)
  • SPARQL
  • sTeX, LaTeX
  • diff --git a/mode/javascript/javascript.js b/mode/javascript/javascript.js index 93df06d152..3f05ac46c3 100644 --- a/mode/javascript/javascript.js +++ b/mode/javascript/javascript.js @@ -118,7 +118,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { } else if (state.lastType == "operator" || state.lastType == "keyword c" || state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) { readRegexp(stream); - stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla + stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/); return ret("regexp", "string-2"); } else { stream.eatWhile(isOperatorChar); diff --git a/mode/meta.js b/mode/meta.js index 8d91df7d77..e110288afc 100644 --- a/mode/meta.js +++ b/mode/meta.js @@ -38,6 +38,7 @@ {name: "Embedded Javascript", mime: "application/x-ejs", mode: "htmlembedded", ext: ["ejs"]}, {name: "Embedded Ruby", mime: "application/x-erb", mode: "htmlembedded", ext: ["erb"]}, {name: "Erlang", mime: "text/x-erlang", mode: "erlang", ext: ["erl"]}, + {name: "Forth", mime: "text/x-forth", mode: "forth", ext: ["forth", "fth", "4th"]}, {name: "Fortran", mime: "text/x-fortran", mode: "fortran", ext: ["f", "for", "f77", "f90"]}, {name: "F#", mime: "text/x-fsharp", mode: "mllike", ext: ["fs"], alias: ["fsharp"]}, {name: "Gas", mime: "text/x-gas", mode: "gas", ext: ["s"]}, diff --git a/mode/sql/sql.js b/mode/sql/sql.js index f2c2384a72..ee6c194b0e 100644 --- a/mode/sql/sql.js +++ b/mode/sql/sql.js @@ -190,7 +190,7 @@ CodeMirror.defineMode("sql", function(config, parserConfig) { indent: function(state, textAfter) { var cx = state.context; - if (!cx) return 0; + if (!cx) return CodeMirror.Pass; var closing = textAfter.charAt(0) == cx.type; if (cx.align) return cx.col + (closing ? 0 : 1); else return cx.indent + (closing ? 0 : config.indentUnit); diff --git a/mode/stylus/index.html b/mode/stylus/index.html new file mode 100644 index 0000000000..354bf30328 --- /dev/null +++ b/mode/stylus/index.html @@ -0,0 +1,104 @@ + + +CodeMirror: Stylus mode + + + + + + + + + + + +
    +

    Stylus mode

    +
    +
    + + +

    MIME types defined: text/x-styl.

    + +
    diff --git a/mode/stylus/stylus.js b/mode/stylus/stylus.js new file mode 100644 index 0000000000..6f7c754425 --- /dev/null +++ b/mode/stylus/stylus.js @@ -0,0 +1,444 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineMode("stylus", function(config) { + + var operatorsRegexp = /^(\?:?|\+[+=]?|-[\-=]?|\*[\*=]?|\/=?|[=!:\?]?=|<=?|>=?|%=?|&&|\|=?|\~|!|\^|\\)/, + delimitersRegexp = /^(?:[()\[\]{},:`=;]|\.\.?\.?)/, + wordOperatorsRegexp = wordRegexp(wordOperators), + commonKeywordsRegexp = wordRegexp(commonKeywords), + commonAtomsRegexp = wordRegexp(commonAtoms), + commonDefRegexp = wordRegexp(commonDef), + vendorPrefixesRegexp = new RegExp(/^\-(moz|ms|o|webkit)-/), + cssValuesWithBracketsRegexp = new RegExp("^(" + cssValuesWithBrackets_.join("|") + ")\\([\\w\-\\#\\,\\.\\%\\s\\(\\)]*\\)"); + + var tokenBase = function(stream, state) { + + if (stream.eatSpace()) return null; + + var ch = stream.peek(); + + // Single line Comment + if (stream.match('//')) { + stream.skipToEnd(); + return "comment"; + } + + // Multiline Comment + if (stream.match('/*')) { + state.tokenizer = multilineComment; + return state.tokenizer(stream, state); + } + + // Strings + if (ch === '"' || ch === "'") { + stream.next(); + state.tokenizer = buildStringTokenizer(ch); + return "string"; + } + + // Def + if (ch === "@") { + stream.next(); + if (stream.match(/extend/)) { + dedent(state); // remove indentation after selectors + } else if (stream.match(/media[\w-\s]*[\w-]/)) { + indent(state); + } else if(stream.eatWhile(/[\w-]/)) { + if(stream.current().match(commonDefRegexp)) { + indent(state); + } + } + return "def"; + } + + // Number + if (stream.match(/^-?[0-9\.]/, false)) { + + // Floats + if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i) || stream.match(/^-?\d+\.\d*/)) { + + // Prevent from getting extra . on 1.. + if (stream.peek() == ".") { + stream.backUp(1); + } + // Units + stream.eatWhile(/[a-z%]/i); + return "number"; + } + // Integers + if (stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/) || stream.match(/^-?0(?![\dx])/i)) { + // Units + stream.eatWhile(/[a-z%]/i); + return "number"; + } + } + + // Hex color and id selector + if (ch === "#") { + stream.next(); + + // Hex color + if (stream.match(/^[0-9a-f]{6}|[0-9a-f]{3}/i)) { + return "atom"; + } + + // ID selector + if (stream.match(/^[\w-]+/i)) { + indent(state); + return "builtin"; + } + } + + // Vendor prefixes + if (stream.match(vendorPrefixesRegexp)) { + return "meta"; + } + + // Gradients and animation as CSS value + if (stream.match(cssValuesWithBracketsRegexp)) { + return "atom"; + } + + // Mixins / Functions with indentation + if (stream.sol() && stream.match(/^\.?[a-z][\w-]*\(/i)) { + stream.backUp(1); + indent(state); + return "keyword"; + } + + // Mixins / Functions + if (stream.match(/^\.?[a-z][\w-]*\(/i)) { + stream.backUp(1); + return "keyword"; + } + + // +Block mixins + if (stream.match(/^(\+|\-)[a-z][\w-]+\(/i)) { + stream.backUp(1); + indent(state); + return "keyword"; + } + + // url tokens + if (stream.match(/^url/) && stream.peek() === "(") { + state.tokenizer = urlTokens; + if(!stream.peek()) { + state.cursorHalf = 0; + } + return "atom"; + } + + // Class + if (stream.match(/^\.[a-z][\w-]*/i)) { + indent(state); + return "qualifier"; + } + + // & Parent Reference with BEM naming + if (stream.match(/^(_|__|-|--)[a-z0-9-]+/)) { + return "qualifier"; + } + + // Pseudo elements/classes + if (ch == ':' && stream.match(/^::?[\w-]+/)) { + indent(state); + return "variable-3"; + } + + // Conditionals + if (stream.match(wordRegexp(["for", "if", "else", "unless"]))) { + indent(state); + return "keyword"; + } + + // Keywords + if (stream.match(commonKeywordsRegexp)) { + return "keyword"; + } + + // Atoms + if (stream.match(commonAtomsRegexp)) { + return "atom"; + } + + // Variables + if (stream.match(/^\$?[a-z][\w-]+\s?=(\s|[\w-'"\$])/i)) { + stream.backUp(2); + var cssPropertie = stream.current().toLowerCase().match(/[\w-]+/)[0]; + return cssProperties[cssPropertie] === undefined ? "variable-2" : "property"; + } else if (stream.match(/\$[\w-\.]+/i)) { + return "variable-2"; + } else if (stream.match(/\$?[\w-]+\.[\w-]+/i)) { + var cssTypeSelector = stream.current().toLowerCase().match(/[\w]+/)[0]; + if(cssTypeSelectors[cssTypeSelector] === undefined) { + return "variable-2"; + } else stream.backUp(stream.current().length); + } + + // !important + if (ch === "!") { + stream.next(); + return stream.match(/^[\w]+/) ? "keyword": "operator"; + } + + // / Root Reference + if (stream.match(/^\/(:|\.|#|[a-z])/)) { + stream.backUp(1); + return "variable-3"; + } + + // Operators and delimiters + if (stream.match(operatorsRegexp) || stream.match(wordOperatorsRegexp)) { + return "operator"; + } + if (stream.match(delimitersRegexp)) { + return null; + } + + // & Parent Reference + if (ch === "&") { + stream.next(); + return "variable-3"; + } + + // Font family + if (stream.match(/^[A-Z][a-z0-9-]+/)) { + return "string"; + } + + // CSS rule + // NOTE: Some css selectors and property values have the same name + // (embed, menu, pre, progress, sub, table), + // so they will have the same color (.cm-atom). + if (stream.match(/[\w-]*/i)) { + + var word = stream.current().toLowerCase(); + + if(cssProperties[word] !== undefined) { + // CSS property + if(!stream.eol()) + return "property"; + else + return "variable-2"; + + } else if(cssValues[word] !== undefined) { + // CSS value + return "atom"; + + } else if(cssTypeSelectors[word] !== undefined) { + // CSS type selectors + indent(state); + return "tag"; + + } else if(word) { + // By default variable-2 + return "variable-2"; + } + } + + // Handle non-detected items + stream.next(); + return null; + + }; + + var tokenLexer = function(stream, state) { + + if (stream.sol()) { + state.indentCount = 0; + } + + var style = state.tokenizer(stream, state); + var current = stream.current(); + + if (stream.eol() && (current === "}" || current === ",")) { + dedent(state); + } + + if (style !== null) { + var startOfToken = stream.pos - current.length; + var withCurrentIndent = startOfToken + (config.indentUnit * state.indentCount); + + var newScopes = []; + + for (var i = 0; i < state.scopes.length; i++) { + var scope = state.scopes[i]; + + if (scope.offset <= withCurrentIndent) { + newScopes.push(scope); + } + } + + state.scopes = newScopes; + } + + return style; + }; + + return { + startState: function() { + return { + tokenizer: tokenBase, + scopes: [{offset: 0, type: 'styl'}] + }; + }, + + token: function(stream, state) { + var style = tokenLexer(stream, state); + state.lastToken = { style: style, content: stream.current() }; + return style; + }, + + indent: function(state) { + return state.scopes[0].offset; + }, + + lineComment: "//", + fold: "indent" + + }; + + function urlTokens(stream, state) { + var ch = stream.peek(); + + if (ch === ")") { + stream.next(); + state.tokenizer = tokenBase; + return "operator"; + } else if (ch === "(") { + stream.next(); + stream.eatSpace(); + + return "operator"; + } else if (ch === "'" || ch === '"') { + state.tokenizer = buildStringTokenizer(stream.next()); + return "string"; + } else { + state.tokenizer = buildStringTokenizer(")", false); + return "string"; + } + } + + function multilineComment(stream, state) { + if (stream.skipTo("*/")) { + stream.next(); + stream.next(); + state.tokenizer = tokenBase; + } else { + stream.next(); + } + return "comment"; + } + + function buildStringTokenizer(quote, greedy) { + + if(greedy == null) { + greedy = true; + } + + function stringTokenizer(stream, state) { + var nextChar = stream.next(); + var peekChar = stream.peek(); + var previousChar = stream.string.charAt(stream.pos-2); + + var endingString = ((nextChar !== "\\" && peekChar === quote) || + (nextChar === quote && previousChar !== "\\")); + + if (endingString) { + if (nextChar !== quote && greedy) { + stream.next(); + } + state.tokenizer = tokenBase; + return "string"; + } else if (nextChar === "#" && peekChar === "{") { + state.tokenizer = buildInterpolationTokenizer(stringTokenizer); + stream.next(); + return "operator"; + } else { + return "string"; + } + } + + return stringTokenizer; + } + + function buildInterpolationTokenizer(currentTokenizer) { + return function(stream, state) { + if (stream.peek() === "}") { + stream.next(); + state.tokenizer = currentTokenizer; + return "operator"; + } else { + return tokenBase(stream, state); + } + }; + } + + function indent(state) { + if (state.indentCount == 0) { + state.indentCount++; + var lastScopeOffset = state.scopes[0].offset; + var currentOffset = lastScopeOffset + config.indentUnit; + state.scopes.unshift({ offset:currentOffset }); + } + } + + function dedent(state) { + if (state.scopes.length == 1) { return true; } + state.scopes.shift(); + } + + }); + + // https://developer.mozilla.org/en-US/docs/Web/HTML/Element + var cssTypeSelectors_ = ["a","abbr","address","area","article","aside","audio", "b", "base","bdi","bdo","bgsound","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","data","datalist","dd","del","details","dfn","div","dl","dt","em","embed","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","iframe","img","input","ins","kbd","keygen","label","legend","li","link","main","map","mark","marquee","menu","menuitem","meta","meter","nav","nobr","noframes","noscript","object","ol","optgroup","option","output","p","param","pre","progress","q","rp","rt","ruby","s","samp","script","section","select","small","source","span","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","title","tr","track","u","ul","var","video","wbr"]; + // https://github.com/csscomb/csscomb.js/blob/master/config/zen.json + var cssProperties_ = ["position","top","right","bottom","left","z-index","display","visibility","flex-direction","flex-order","flex-pack","float","clear","flex-align","overflow","overflow-x","overflow-y","overflow-scrolling","clip","box-sizing","margin","margin-top","margin-right","margin-bottom","margin-left","padding","padding-top","padding-right","padding-bottom","padding-left","min-width","min-height","max-width","max-height","width","height","outline","outline-width","outline-style","outline-color","outline-offset","border","border-spacing","border-collapse","border-width","border-style","border-color","border-top","border-top-width","border-top-style","border-top-color","border-right","border-right-width","border-right-style","border-right-color","border-bottom","border-bottom-width","border-bottom-style","border-bottom-color","border-left","border-left-width","border-left-style","border-left-color","border-radius","border-top-left-radius","border-top-right-radius","border-bottom-right-radius","border-bottom-left-radius","border-image","border-image-source","border-image-slice","border-image-width","border-image-outset","border-image-repeat","border-top-image","border-right-image","border-bottom-image","border-left-image","border-corner-image","border-top-left-image","border-top-right-image","border-bottom-right-image","border-bottom-left-image","background","filter:progid:DXImageTransform\\.Microsoft\\.AlphaImageLoader","background-color","background-image","background-attachment","background-position","background-position-x","background-position-y","background-clip","background-origin","background-size","background-repeat","box-decoration-break","box-shadow","color","table-layout","caption-side","empty-cells","list-style","list-style-position","list-style-type","list-style-image","quotes","content","counter-increment","counter-reset","writing-mode","vertical-align","text-align","text-align-last","text-decoration","text-emphasis","text-emphasis-position","text-emphasis-style","text-emphasis-color","text-indent","-ms-text-justify","text-justify","text-outline","text-transform","text-wrap","text-overflow","text-overflow-ellipsis","text-overflow-mode","text-size-adjust","text-shadow","white-space","word-spacing","word-wrap","word-break","tab-size","hyphens","letter-spacing","font","font-weight","font-style","font-variant","font-size-adjust","font-stretch","font-size","font-family","src","line-height","opacity","filter:\\\\\\\\'progid:DXImageTransform.Microsoft.Alpha","filter:progid:DXImageTransform.Microsoft.Alpha\\(Opacity","interpolation-mode","filter","resize","cursor","nav-index","nav-up","nav-right","nav-down","nav-left","transition","transition-delay","transition-timing-function","transition-duration","transition-property","transform","transform-origin","animation","animation-name","animation-duration","animation-play-state","animation-timing-function","animation-delay","animation-iteration-count","animation-direction","pointer-events","unicode-bidi","direction","columns","column-span","column-width","column-count","column-fill","column-gap","column-rule","column-rule-width","column-rule-style","column-rule-color","break-before","break-inside","break-after","page-break-before","page-break-inside","page-break-after","orphans","widows","zoom","max-zoom","min-zoom","user-zoom","orientation","text-rendering","speak","animation-fill-mode","backface-visibility","user-drag","user-select","appearance"]; + // https://github.com/codemirror/CodeMirror/blob/master/mode/css/css.js#L501 + var cssValues_ = ["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","avoid-column","avoid-page","avoid-region","background","backwards","baseline","below","bidi-override","binary","bengali","block","block-axis","bold","bolder","border","border-box","both","bottom","break","break-all","break-word","button-bevel","buttonface","buttonhighlight","buttonshadow","buttontext","cambodian","capitalize","caps-lock-indicator","captiontext","caret","cell","center","checkbox","circle","cjk-earthly-branch","cjk-heavenly-stem","cjk-ideographic","clear","clip","close-quote","col-resize","collapse","column","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","ellipse","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","keep-all","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","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","outside-shape","overlay","overline","padding","padding-box","painted","page","paused","persian","plus-darker","plus-lighter","pointer","polygon","portrait","pre","pre-line","pre-wrap","preserve-3d","progress","push-button","radio","read-only","read-write","read-write-plaintext-only","rectangle","region","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-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","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","wider","window","windowframe","windowtext","x-large","x-small","xor","xx-large","xx-small","bicubic","optimizespeed","grayscale"]; + var cssColorValues_ = ["aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","indianred","indigo","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","red","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","snow","springgreen","steelblue","tan","teal","thistle","tomato","turquoise","violet","wheat","white","whitesmoke","yellow","yellowgreen"]; + var cssValuesWithBrackets_ = ["gradient","linear-gradient","radial-gradient","repeating-linear-gradient","repeating-radial-gradient","cubic-bezier","translateX","translateY","translate3d","rotate3d","scale","scale3d","perspective","skewX"]; + + var wordOperators = ["in", "and", "or", "not", "is a", "is", "isnt", "defined", "if unless"], + commonKeywords = ["for", "if", "else", "unless", "return"], + commonAtoms = ["null", "true", "false", "href", "title", "type", "not-allowed", "readonly", "disabled"], + commonDef = ["@font-face", "@keyframes", "@media", "@viewport", "@page", "@host", "@supports", "@block", "@css"], + cssTypeSelectors = keySet(cssTypeSelectors_), + cssProperties = keySet(cssProperties_), + cssValues = keySet(cssValues_.concat(cssColorValues_)), + hintWords = wordOperators.concat(commonKeywords, + commonAtoms, + commonDef, + cssTypeSelectors_, + cssProperties_, + cssValues_, + cssValuesWithBrackets_, + cssColorValues_); + + function wordRegexp(words) { + return new RegExp("^((" + words.join(")|(") + "))\\b"); + }; + + function keySet(array) { + var keys = {}; + for (var i = 0; i < array.length; ++i) { + keys[array[i]] = true; + } + return keys; + }; + + CodeMirror.registerHelper("hintWords", "stylus", hintWords); + CodeMirror.defineMIME("text/x-styl", "stylus"); + +}); diff --git a/mode/verilog/verilog.js b/mode/verilog/verilog.js index 8fc9ea4f8c..96b9f24581 100644 --- a/mode/verilog/verilog.js +++ b/mode/verilog/verilog.js @@ -17,7 +17,8 @@ CodeMirror.defineMode("verilog", function(config, parserConfig) { statementIndentUnit = parserConfig.statementIndentUnit || indentUnit, dontAlignCalls = parserConfig.dontAlignCalls, noIndentKeywords = parserConfig.noIndentKeywords || [], - multiLineStrings = parserConfig.multiLineStrings; + multiLineStrings = parserConfig.multiLineStrings, + hooks = parserConfig.hooks || {}; function words(str) { var obj = {}, words = str.split(" "); @@ -107,7 +108,11 @@ CodeMirror.defineMode("verilog", function(config, parserConfig) { var statementKeywords = words("always always_comb always_ff always_latch assert assign assume else export for foreach forever if import initial repeat while"); function tokenBase(stream, state) { - var ch = stream.peek(); + var ch = stream.peek(), style; + if (hooks[ch] && (style = hooks[ch](stream, state)) != false) return style; + if (hooks.tokenBase && (style = hooks.tokenBase(stream, state)) != false) + return style; + if (/[,;:\.]/.test(ch)) { curPunc = stream.next(); return null; @@ -280,12 +285,14 @@ CodeMirror.defineMode("verilog", function(config, parserConfig) { electricInput: buildElectricInputRegEx(), startState: function(basecolumn) { - return { + var state = { tokenize: null, context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), indented: 0, startOfLine: true }; + if (hooks.startState) hooks.startState(state); + return state; }, token: function(stream, state) { @@ -295,6 +302,7 @@ CodeMirror.defineMode("verilog", function(config, parserConfig) { state.indented = stream.indentation(); state.startOfLine = true; } + if (hooks.token) hooks.token(stream, state); if (stream.eatSpace()) return null; curPunc = null; curKeyword = null; @@ -304,17 +312,19 @@ CodeMirror.defineMode("verilog", function(config, parserConfig) { if (curPunc == ctx.type) { popContext(state); - } - else if ((curPunc == ";" && ctx.type == "statement") || + } else if ((curPunc == ";" && ctx.type == "statement") || (ctx.type && isClosing(curKeyword, ctx.type))) { ctx = popContext(state); while (ctx && ctx.type == "statement") ctx = popContext(state); - } - else if (curPunc == "{") { pushContext(state, stream.column(), "}"); } - else if (curPunc == "[") { pushContext(state, stream.column(), "]"); } - else if (curPunc == "(") { pushContext(state, stream.column(), ")"); } - else if (ctx && ctx.type == "endcase" && curPunc == ":") { pushContext(state, stream.column(), "statement"); } - else if (curPunc == "newstatement") { + } else if (curPunc == "{") { + pushContext(state, stream.column(), "}"); + } else if (curPunc == "[") { + pushContext(state, stream.column(), "]"); + } else if (curPunc == "(") { + pushContext(state, stream.column(), ")"); + } else if (ctx && ctx.type == "endcase" && curPunc == ":") { + pushContext(state, stream.column(), "statement"); + } else if (curPunc == "newstatement") { pushContext(state, stream.column(), "statement"); } else if (curPunc == "newblock") { if (curKeyword == "function" && ctx && (ctx.type == "statement" || ctx.type == "endgroup")) { @@ -335,13 +345,16 @@ CodeMirror.defineMode("verilog", function(config, parserConfig) { indent: function(state, textAfter) { if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass; + if (hooks.indent) { + var fromHook = hooks.indent(state); + if (fromHook >= 0) return fromHook; + } var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; var closing = false; var possibleClosing = textAfter.match(closingBracketOrWord); - if (possibleClosing) { + if (possibleClosing) closing = isClosing(possibleClosing[0], ctx.type); - } if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit); else if (closingBracket.test(ctx.type) && ctx.align && !dontAlignCalls) return ctx.column + (closing ? 0 : 1); else if (ctx.type == ")" && !closing) return ctx.indented + statementIndentUnit; @@ -354,11 +367,171 @@ CodeMirror.defineMode("verilog", function(config, parserConfig) { }; }); -CodeMirror.defineMIME("text/x-verilog", { - name: "verilog" -}); -CodeMirror.defineMIME("text/x-systemverilog", { - name: "systemverilog" -}); + CodeMirror.defineMIME("text/x-verilog", { + name: "verilog" + }); + + CodeMirror.defineMIME("text/x-systemverilog", { + name: "verilog" + }); + + // SVXVerilog mode + + var svxchScopePrefixes = { + ">": "property", "->": "property", "-": "hr", "|": "link", "?$": "qualifier", "?*": "qualifier", + "@-": "variable-3", "@": "variable-3", "?": "qualifier" + }; + function svxGenIndent(stream, state) { + var svxindentUnit = 2; + var rtnIndent = -1, indentUnitRq = 0, curIndent = stream.indentation(); + switch (state.svxCurCtlFlowChar) { + case "\\": + curIndent = 0; + break; + case "|": + if (state.svxPrevPrevCtlFlowChar == "@") { + indentUnitRq = -2; //-2 new pipe rq after cur pipe + break; + } + if (svxchScopePrefixes[state.svxPrevCtlFlowChar]) + indentUnitRq = 1; // +1 new scope + break; + case "M": // m4 + if (state.svxPrevPrevCtlFlowChar == "@") { + indentUnitRq = -2; //-2 new inst rq after pipe + break; + } + if (svxchScopePrefixes[state.svxPrevCtlFlowChar]) + indentUnitRq = 1; // +1 new scope + break; + case "@": + if (state.svxPrevCtlFlowChar == "S") + indentUnitRq = -1; // new pipe stage after stmts + if (state.svxPrevCtlFlowChar == "|") + indentUnitRq = 1; // 1st pipe stage + break; + case "S": + if (state.svxPrevCtlFlowChar == "@") + indentUnitRq = 1; // flow in pipe stage + if (svxchScopePrefixes[state.svxPrevCtlFlowChar]) + indentUnitRq = 1; // +1 new scope + break; + } + var statementIndentUnit = svxindentUnit; + rtnIndent = curIndent + (indentUnitRq*statementIndentUnit); + return rtnIndent >= 0 ? rtnIndent : curIndent; + } + + CodeMirror.defineMIME("text/x-svx", { + name: "verilog", + hooks: { + "\\": function(stream, state) { + var vxIndent = 0, style = false; + var curPunc = stream.string; + if ((stream.sol()) && (/\\SV/.test(stream.string))) { + curPunc = (/\\SVX_version/.test(stream.string)) + ? "\\SVX_version" : stream.string; + stream.skipToEnd(); + if (curPunc == "\\SV" && state.vxCodeActive) {state.vxCodeActive = false;}; + if ((/\\SVX/.test(curPunc) && !state.vxCodeActive) + || (curPunc=="\\SVX_version" && state.vxCodeActive)) {state.vxCodeActive = true;}; + style = "keyword"; + state.svxCurCtlFlowChar = state.svxPrevPrevCtlFlowChar + = state.svxPrevCtlFlowChar = ""; + if (state.vxCodeActive == true) { + state.svxCurCtlFlowChar = "\\"; + vxIndent = svxGenIndent(stream, state); + } + state.vxIndentRq = vxIndent; + } + return style; + }, + tokenBase: function(stream, state) { + var vxIndent = 0, style = false; + var svxisOperatorChar = /[\[\]=:]/; + var svxkpScopePrefixs = { + "**":"variable-2", "*":"variable-2", "$$":"variable", "$":"variable", + "^^":"attribute", "^":"attribute"}; + var ch = stream.peek(); + var vxCurCtlFlowCharValueAtStart = state.svxCurCtlFlowChar; + if (state.vxCodeActive == true) { + if (/[\[\]{}\(\);\:]/.test(ch)) { + // bypass nesting and 1 char punc + style = "meta"; + stream.next(); + } else if (ch == "/") { + stream.next(); + if (stream.eat("/")) { + stream.skipToEnd(); + style = "comment"; + state.svxCurCtlFlowChar = "S"; + } else { + stream.backUp(1); + } + } else if (ch == "@") { + // pipeline stage + style = svxchScopePrefixes[ch]; + state.svxCurCtlFlowChar = "@"; + stream.next(); + stream.eatWhile(/[\w\$_]/); + } else if (stream.match(/\b[mM]4+/, true)) { // match: function(pattern, consume, caseInsensitive) + // m4 pre proc + stream.skipTo("("); + style = "def"; + state.svxCurCtlFlowChar = "M"; + } else if (ch == "!" && stream.sol()) { + // v stmt in svx region + // state.svxCurCtlFlowChar = "S"; + style = "comment"; + stream.next(); + } else if (svxisOperatorChar.test(ch)) { + // operators + stream.eatWhile(svxisOperatorChar); + style = "operator"; + } else if (ch == "#") { + // phy hier + state.svxCurCtlFlowChar = (state.svxCurCtlFlowChar == "") + ? ch : state.svxCurCtlFlowChar; + stream.next(); + stream.eatWhile(/[+-]\d/); + style = "tag"; + } else if (svxkpScopePrefixs.propertyIsEnumerable(ch)) { + // special SVX operators + style = svxkpScopePrefixs[ch]; + state.svxCurCtlFlowChar = state.svxCurCtlFlowChar == "" ? "S" : state.svxCurCtlFlowChar; // stmt + stream.next(); + stream.match(/[a-zA-Z_0-9]+/); + } else if (style = svxchScopePrefixes[ch] || false) { + // special SVX operators + state.svxCurCtlFlowChar = state.svxCurCtlFlowChar == "" ? ch : state.svxCurCtlFlowChar; + stream.next(); + stream.match(/[a-zA-Z_0-9]+/); + } + if (state.svxCurCtlFlowChar != vxCurCtlFlowCharValueAtStart) { // flow change + vxIndent = svxGenIndent(stream, state); + state.vxIndentRq = vxIndent; + } + } + return style; + }, + token: function(stream, state) { + if (state.vxCodeActive == true && stream.sol() && state.svxCurCtlFlowChar != "") { + state.svxPrevPrevCtlFlowChar = state.svxPrevCtlFlowChar; + state.svxPrevCtlFlowChar = state.svxCurCtlFlowChar; + state.svxCurCtlFlowChar = ""; + } + }, + indent: function(state) { + return (state.vxCodeActive == true) ? state.vxIndentRq : -1; + }, + startState: function(state) { + state.svxCurCtlFlowChar = ""; + state.svxPrevCtlFlowChar = ""; + state.svxPrevPrevCtlFlowChar = ""; + state.vxCodeActive = true; + state.vxIndentRq = 0; + } + } + }); }); diff --git a/package.json b/package.json index 8f529cd925..5801a6c860 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version":"4.12.0", + "version":"4.13.0", "main": "lib/codemirror.js", "description": "In-browser code editing made bearable", "licenses": [{"type": "MIT", diff --git a/test/index.html b/test/index.html index 89f4e378c3..d7fdede74e 100644 --- a/test/index.html +++ b/test/index.html @@ -1,7 +1,7 @@ -CodeMirror: Test Suite +CodeMirror: Test Suite @@ -13,14 +13,29 @@ + - + + + + + + + + + + + + + + - + +