diff --git a/addon/dialog/dialog.js b/addon/dialog/dialog.js index 71e2287447..499964fdb2 100644 --- a/addon/dialog/dialog.js +++ b/addon/dialog/dialog.js @@ -10,11 +10,22 @@ } else { dialog.className = "CodeMirror-dialog CodeMirror-dialog-top"; } - dialog.innerHTML = template; + if (typeof template == "string") { + dialog.innerHTML = template; + } else { // Assuming it's a detached DOM element. + dialog.appendChild(template); + } return dialog; } + function closeNotification(cm, newVal) { + if (cm.state.currentNotificationClose) + cm.state.currentNotificationClose(); + cm.state.currentNotificationClose = newVal; + } + CodeMirror.defineExtension("openDialog", function(template, callback, options) { + closeNotification(this, null); var dialog = dialogDiv(this, template, options && options.bottom); var closed = false, me = this; function close() { @@ -51,6 +62,7 @@ }); CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) { + closeNotification(this, null); var dialog = dialogDiv(this, template, options && options.bottom); var buttons = dialog.getElementsByTagName("button"); var closed = false, me = this, blurring = 1; @@ -77,4 +89,33 @@ CodeMirror.on(b, "focus", function() { ++blurring; }); } }); + + /* + * openNotification + * Opens a notification, that can be closed with an optional timer + * (default 5000ms timer) and always closes on click. + * + * If a notification is opened while another is opened, it will close the + * currently opened one and open the new one immediately. + */ + CodeMirror.defineExtension("openNotification", function(template, options) { + closeNotification(this, close); + var dialog = dialogDiv(this, template, options && options.bottom); + var duration = options && (options.duration === undefined ? 5000 : options.duration); + var closed = false, doneTimer; + + function close() { + if (closed) return; + closed = true; + clearTimeout(doneTimer); + dialog.parentNode.removeChild(dialog); + } + + CodeMirror.on(dialog, 'click', function(e) { + CodeMirror.e_preventDefault(e); + close(); + }); + if (duration) + doneTimer = setTimeout(close, options.duration); + }); })(); diff --git a/addon/display/fullscreen.js b/addon/display/fullscreen.js index 3c31e97a33..a442f6a433 100644 --- a/addon/display/fullscreen.js +++ b/addon/display/fullscreen.js @@ -12,7 +12,8 @@ var wrap = cm.getWrapperElement(); cm.state.fullScreenRestore = {scrollTop: window.pageYOffset, scrollLeft: window.pageXOffset, width: wrap.style.width, height: wrap.style.height}; - wrap.style.width = wrap.style.height = ""; + wrap.style.width = ""; + wrap.style.height = "auto"; wrap.className += " CodeMirror-fullscreen"; document.documentElement.style.overflow = "hidden"; cm.refresh(); diff --git a/addon/display/placeholder.js b/addon/display/placeholder.js index 18f9dff3ab..748afe7275 100644 --- a/addon/display/placeholder.js +++ b/addon/display/placeholder.js @@ -2,12 +2,10 @@ CodeMirror.defineOption("placeholder", "", function(cm, val, old) { var prev = old && old != CodeMirror.Init; if (val && !prev) { - cm.on("focus", onFocus); cm.on("blur", onBlur); cm.on("change", onChange); onChange(cm); } else if (!val && prev) { - cm.off("focus", onFocus); cm.off("blur", onBlur); cm.off("change", onChange); clearPlaceholder(cm); @@ -33,9 +31,6 @@ cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild); } - function onFocus(cm) { - clearPlaceholder(cm); - } function onBlur(cm) { if (isEmpty(cm)) setPlaceholder(cm); } @@ -43,7 +38,6 @@ var wrapper = cm.getWrapperElement(), empty = isEmpty(cm); wrapper.className = wrapper.className.replace(" CodeMirror-empty", "") + (empty ? " CodeMirror-empty" : ""); - if (cm.hasFocus()) return; if (empty) setPlaceholder(cm); else clearPlaceholder(cm); } diff --git a/addon/edit/closetag.js b/addon/edit/closetag.js index d6a8fafd3c..1da89ba6dc 100644 --- a/addon/edit/closetag.js +++ b/addon/edit/closetag.js @@ -24,16 +24,15 @@ (function() { CodeMirror.defineOption("autoCloseTags", false, function(cm, val, old) { - if (val && (old == CodeMirror.Init || !old)) { - var map = {name: "autoCloseTags"}; - if (typeof val != "object" || val.whenClosing) - map["'/'"] = function(cm) { return autoCloseSlash(cm); }; - if (typeof val != "object" || val.whenOpening) - map["'>'"] = function(cm) { return autoCloseGT(cm); }; - cm.addKeyMap(map); - } else if (!val && (old != CodeMirror.Init && old)) { + if (old != CodeMirror.Init && old) cm.removeKeyMap("autoCloseTags"); - } + if (!val) return; + var map = {name: "autoCloseTags"}; + if (typeof val != "object" || val.whenClosing) + map["'/'"] = function(cm) { return autoCloseSlash(cm); }; + if (typeof val != "object" || val.whenOpening) + map["'>'"] = function(cm) { return autoCloseGT(cm); }; + cm.addKeyMap(map); }); var htmlDontClose = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", @@ -54,7 +53,8 @@ if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch); var lowerTagName = tagName.toLowerCase(); // Don't process the '>' at the end of an end-tag or self-closing tag - if (tok.type == "tag" && state.type == "closeTag" || + if (tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) || + tok.type == "tag" && state.type == "closeTag" || tok.string.indexOf("/") == (tok.string.length - 1) || // match something like dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1) return CodeMirror.Pass; @@ -72,7 +72,9 @@ function autoCloseSlash(cm) { var pos = cm.getCursor(), tok = cm.getTokenAt(pos); var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state; - if (tok.string.charAt(0) != "<" || tok.start != pos.ch - 1 || inner.mode.name != "xml") return CodeMirror.Pass; + if (tok.type == "string" || tok.string.charAt(0) != "<" || + tok.start != pos.ch - 1 || inner.mode.name != "xml") + return CodeMirror.Pass; var tagName = state.context && state.context.tagName; if (tagName) cm.replaceSelection("/" + tagName + ">", "end"); diff --git a/addon/edit/matchbrackets.js b/addon/edit/matchbrackets.js index 131fe831fd..9d9b3882f7 100644 --- a/addon/edit/matchbrackets.js +++ b/addon/edit/matchbrackets.js @@ -8,6 +8,7 @@ function findMatchingBracket(cm, where, strict) { var state = cm.state.matchBrackets; var maxScanLen = (state && state.maxScanLineLength) || 10000; + var maxScanLines = (state && state.maxScanLines) || 100; var cur = where || cm.getCursor(), line = cm.getLineHandle(cur.line), pos = cur.ch - 1; var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; @@ -32,7 +33,7 @@ } } } - for (var i = cur.line, found, e = forward ? Math.min(i + 100, cm.lineCount()) : Math.max(-1, i - 100); i != e; i+=d) { + for (var i = cur.line, found, e = forward ? Math.min(i + maxScanLines, cm.lineCount()) : Math.max(-1, i - maxScanLines); i != e; i+=d) { if (i == cur.line) found = scan(line, i, pos); else found = scan(cm.getLineHandle(i), i); if (found) break; diff --git a/addon/fold/foldgutter.js b/addon/fold/foldgutter.js index e3c52bc229..57336fbff8 100644 --- a/addon/fold/foldgutter.js +++ b/addon/fold/foldgutter.js @@ -88,14 +88,14 @@ } function onChange(cm) { - var state = cm.state.foldGutter; + var state = cm.state.foldGutter, opts = cm.state.foldGutter.options; state.from = state.to = 0; clearTimeout(state.changeUpdate); - state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, 600); + state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600); } function onViewportChange(cm) { - var state = cm.state.foldGutter; + var state = cm.state.foldGutter, opts = cm.state.foldGutter.options; clearTimeout(state.changeUpdate); state.changeUpdate = setTimeout(function() { var vp = cm.getViewport(); @@ -113,7 +113,7 @@ } }); } - }, 400); + }, opts.updateViewportTimeSpan || 400); } function onFold(cm, from) { diff --git a/addon/fold/indent-fold.js b/addon/fold/indent-fold.js index b54da34777..1bd600be42 100644 --- a/addon/fold/indent-fold.js +++ b/addon/fold/indent-fold.js @@ -1,26 +1,30 @@ CodeMirror.registerHelper("fold", "indent", function(cm, start) { - var lastLine = cm.lastLine(), - tabSize = cm.getOption("tabSize"), - firstLine = cm.getLine(start.line), - myIndent = CodeMirror.countColumn(firstLine, null, tabSize); - - function foldEnded(curColumn, prevColumn) { - return curColumn < myIndent || - (curColumn == myIndent && prevColumn >= myIndent) || - (curColumn > myIndent && i == lastLine); - } - - for (var i = start.line + 1; i <= lastLine; i++) { - var curColumn = CodeMirror.countColumn(cm.getLine(i), null, tabSize); - var prevColumn = CodeMirror.countColumn(cm.getLine(i-1), null, tabSize); - - if (foldEnded(curColumn, prevColumn)) { - var lastFoldLineNumber = curColumn > myIndent && i == lastLine ? i : i-1; - var lastFoldLine = cm.getLine(lastFoldLineNumber); - return {from: CodeMirror.Pos(start.line, firstLine.length), - to: CodeMirror.Pos(lastFoldLineNumber, lastFoldLine.length)}; + var tabSize = cm.getOption("tabSize"), firstLine = cm.getLine(start.line); + if (!/\S/.test(firstLine)) return; + var getIndent = function(lineNum) { + return CodeMirror.countColumn(lineNum, null, tabSize); + }; + var myIndent = getIndent(firstLine); + var lastLineInFold = null; + // Go through lines until we find a line that definitely doesn't belong in + // the block we're folding, or to the end. + for (var i = start.line + 1, end = cm.lastLine(); i <= end; ++i) { + var curLine = cm.getLine(i); + var curIndent = getIndent(curLine); + if (curIndent > myIndent) { + // Lines with a greater indent are considered part of the block. + lastLineInFold = i; + } else if (!/\S/.test(curLine)) { + // Empty lines might be breaks within the block we're trying to fold. + } else { + // A non-empty line at an indent equal to or less than ours marks the + // start of another block. + break; } } + if (lastLineInFold) return { + from: CodeMirror.Pos(start.line, firstLine.length), + to: CodeMirror.Pos(lastLineInFold, cm.getLine(lastLineInFold).length) + }; }); - CodeMirror.indentRangeFinder = CodeMirror.fold.indent; // deprecated diff --git a/addon/hint/javascript-hint.js b/addon/hint/javascript-hint.js index 513fb782b0..c66b0a7a5b 100644 --- a/addon/hint/javascript-hint.js +++ b/addon/hint/javascript-hint.js @@ -21,6 +21,7 @@ function scriptHint(editor, keywords, getToken, options) { // Find the token at the cursor var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token; + if (/\b(?:string|comment)\b/.test(token.type)) return; token.state = CodeMirror.innerMode(editor.getMode(), token.state).state; // If it's not a 'word-style' token, ignore the token. diff --git a/addon/hint/show-hint.js b/addon/hint/show-hint.js index dbf415527a..46103b719b 100644 --- a/addon/hint/show-hint.js +++ b/addon/hint/show-hint.js @@ -1,6 +1,9 @@ (function() { "use strict"; + var HINT_ELEMENT_CLASS = "CodeMirror-hint"; + var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active"; + CodeMirror.showHint = function(cm, getHints, options) { // We want a single cursor position. if (cm.somethingSelected()) return; @@ -140,6 +143,13 @@ return ourMap; } + function getHintElement(stopAt, el) { + while (el && el != stopAt) { + if (el.nodeName.toUpperCase() === "LI") return el; + el = el.parentNode; + } + } + function Widget(completion, data) { this.completion = completion; this.data = data; @@ -147,12 +157,12 @@ var hints = this.hints = document.createElement("ul"); hints.className = "CodeMirror-hints"; - this.selectedHint = 0; + this.selectedHint = options.getDefaultSelection ? options.getDefaultSelection(cm,options,data) : 0; var completions = data.list; for (var i = 0; i < completions.length; ++i) { var elt = hints.appendChild(document.createElement("li")), cur = completions[i]; - var className = "CodeMirror-hint" + (i ? "" : " CodeMirror-hint-active"); + var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS); if (cur.className != null) className = cur.className + " " + className; elt.className = className; if (cur.render) cur.render(elt, data, cur); @@ -216,13 +226,15 @@ }); CodeMirror.on(hints, "dblclick", function(e) { - var t = e.target || e.srcElement; - if (t.hintId != null) {widget.changeActive(t.hintId); widget.pick();} + var t = getHintElement(hints, e.target || e.srcElement); + if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();} }); + CodeMirror.on(hints, "click", function(e) { - var t = e.target || e.srcElement; - if (t.hintId != null) widget.changeActive(t.hintId); + var t = getHintElement(hints, e.target || e.srcElement); + if (t && t.hintId != null) widget.changeActive(t.hintId); }); + CodeMirror.on(hints, "mousedown", function() { setTimeout(function(){cm.focus();}, 20); }); @@ -257,9 +269,9 @@ i = avoidWrap ? 0 : this.data.list.length - 1; if (this.selectedHint == i) return; var node = this.hints.childNodes[this.selectedHint]; - node.className = node.className.replace(" CodeMirror-hint-active", ""); + node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, ""); node = this.hints.childNodes[this.selectedHint = i]; - node.className += " CodeMirror-hint-active"; + node.className += " " + ACTIVE_HINT_ELEMENT_CLASS; if (node.offsetTop < this.hints.scrollTop) this.hints.scrollTop = node.offsetTop - 3; else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight) diff --git a/addon/tern/tern.js b/addon/tern/tern.js index fe93fa7335..1f18a657fb 100644 --- a/addon/tern/tern.js +++ b/addon/tern/tern.js @@ -96,7 +96,7 @@ getHint: function(cm, c) { return hint(this, cm, c); }, - showType: function(cm) { showType(this, cm); }, + showType: function(cm, pos) { showType(this, cm, pos); }, updateArgHints: function(cm) { updateArgHints(this, cm); }, @@ -106,10 +106,10 @@ rename: function(cm) { rename(this, cm); }, - request: function (cm, query, c) { + request: function (cm, query, c, pos) { var self = this; var doc = findDoc(this, cm.getDoc()); - var request = buildRequest(this, doc, query); + var request = buildRequest(this, doc, query, pos); this.server.request(request, function (error, data) { if (!error && self.options.responseFilter) @@ -221,7 +221,7 @@ // Type queries - function showType(ts, cm) { + function showType(ts, cm, pos) { ts.request(cm, "type", function(error, data) { if (error) return showError(ts, cm, error); if (ts.options.typeTip) { @@ -236,7 +236,7 @@ } } tempTooltip(cm, tip); - }); + }, pos); } // Maintaining argument hints @@ -450,13 +450,13 @@ // Generic request-building helper - function buildRequest(ts, doc, query) { + function buildRequest(ts, doc, query, pos) { var files = [], offsetLines = 0, allowFragments = !query.fullDocs; if (!allowFragments) delete query.fullDocs; if (typeof query == "string") query = {type: query}; query.lineCharPositions = true; if (query.end == null) { - query.end = doc.doc.getCursor("end"); + query.end = pos || doc.doc.getCursor("end"); if (doc.doc.somethingSelected()) query.start = doc.doc.getCursor("start"); } diff --git a/demo/tern.html b/demo/tern.html index adc79e5c3d..6843c7f5ec 100644 --- a/demo/tern.html +++ b/demo/tern.html @@ -16,6 +16,7 @@ + diff --git a/demo/variableheight.html b/demo/variableheight.html index 1ef8fc445d..ab241c6d28 100644 --- a/demo/variableheight.html +++ b/demo/variableheight.html @@ -10,7 +10,13 @@ + + +

MIME types defined: text/x-julia.

+ diff --git a/mode/julia/julia.js b/mode/julia/julia.js new file mode 100644 index 0000000000..9ec2428cd4 --- /dev/null +++ b/mode/julia/julia.js @@ -0,0 +1,262 @@ +CodeMirror.defineMode("julia", function(_conf, parserConf) { + var ERRORCLASS = 'error'; + + function wordRegexp(words) { + return new RegExp("^((" + words.join(")|(") + "))\\b"); + } + + var operators = parserConf.operators || /^(?:\.?[|&^\\%*+\-<>!=\/]=?|\?|~|:|\$|<:|\.[<>]|<<=?|>>>?=?|\.[<>=]=|->?|\/\/|\bin\b|\.{3})/; + var delimiters = parserConf.delimiters || /^[;,()[\]{}]/; + var identifiers = parserConf.identifiers|| /^[_A-Za-z][_A-Za-z0-9]*!*/; + var blockOpeners = ["begin", "function", "type", "immutable", "let", "macro", "for", "while", "quote", "if", "else", "elseif", "try", "finally", "catch"]; + var blockClosers = ["end", "else", "elseif", "catch", "finally"]; + var keywordList = ['if', 'else', 'elseif', 'while', 'for', 'begin', 'let', 'end', 'do', 'try', 'catch', 'finally', 'return', 'break', 'continue', 'global', 'local', 'const', 'export', 'import', 'importall', 'using', 'function', 'macro', 'module', 'baremodule', 'type', 'immutable', 'quote', 'typealias', 'abstract', 'bitstype', 'ccall']; + var builtinList = ['true', 'false', 'enumerate', 'open', 'close', 'nothing', 'NaN', 'Inf', 'print', 'println', 'Int8', 'Uint8', 'Int16', 'Uint16', 'Int32', 'Uint32', 'Int64', 'Uint64', 'Int128', 'Uint128', 'Bool', 'Char', 'Float16', 'Float32', 'Float64', 'Array', 'Vector', 'Matrix', 'String', 'UTF8String', 'ASCIIString', 'error', 'warn', 'info', '@printf']; + + //var stringPrefixes = new RegExp("^[br]?('|\")") + var stringPrefixes = /^[br]?('|"{3}|")/; + var keywords = wordRegexp(keywordList); + var builtins = wordRegexp(builtinList); + var openers = wordRegexp(blockOpeners); + var closers = wordRegexp(blockClosers); + var macro = /@[_A-Za-z][_A-Za-z0-9]*!*/; + var indentInfo = null; + + function in_array(state) { + var ch = cur_scope(state); + if(ch=="[" || ch=="{") { + return true; + } + else { + return false; + } + } + + function cur_scope(state) { + if(state.scopes.length==0) { + return null; + } + return state.scopes[state.scopes.length - 1]; + } + + // tokenizers + function tokenBase(stream, state) { + // Handle scope changes + var leaving_expr = state.leaving_expr; + state.leaving_expr = false; + if(leaving_expr) { + if(stream.match(/^'+/)) { + return 'operator'; + } + if(stream.match("...")) { + return 'operator'; + } + } + + if (stream.eatSpace()) { + return null; + } + + var ch = stream.peek(); + // Handle Comments + if (ch === '#') { + stream.skipToEnd(); + return 'comment'; + } + if(ch==='[') { + state.scopes.push("["); + } + + if(ch==='{') { + state.scopes.push("{"); + } + + var scope=cur_scope(state); + + if(scope==='[' && ch===']') { + state.scopes.pop(); + state.leaving_expr=true; + } + + if(scope==='{' && ch==='}') { + state.scopes.pop(); + state.leaving_expr=true; + } + + var match; + if(match=stream.match(openers, false)) { + state.scopes.push(match); + } + + if(!in_array(state) && stream.match(closers, false)) { + state.scopes.pop(); + } + + if(in_array(state)) { + if(stream.match("end")) { + return 'number'; + } + + } + if(stream.match("=>")) { + return 'operator'; + } + // Handle Number Literals + if (stream.match(/^[0-9\.]/, false)) { + var imMatcher = RegExp(/^im\b/); + var floatLiteral = false; + // Floats + if (stream.match(/^\d*\.\d+([ef][\+\-]?\d+)?/i)) { floatLiteral = true; } + if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; } + if (stream.match(/^\.\d+/)) { floatLiteral = true; } + if (floatLiteral) { + // Float literals may be "imaginary" + stream.match(imMatcher); + return 'number'; + } + // Integers + var intLiteral = false; + // Hex + if (stream.match(/^0x[0-9a-f]+/i)) { intLiteral = true; } + // Binary + if (stream.match(/^0b[01]+/i)) { intLiteral = true; } + // Octal + if (stream.match(/^0o[0-7]+/i)) { intLiteral = true; } + // Decimal + if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) { + // Decimal literals may be "imaginary" + stream.eat(/J/i); + // TODO - Can you have imaginary longs? + intLiteral = true; + } + // Zero by itself with no other piece of number. + if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; } + if (intLiteral) { + // Integer literals may be "long" + stream.match(imMatcher); + return 'number'; + } + } + + // Handle Strings + if (stream.match(stringPrefixes)) { + state.tokenize = tokenStringFactory(stream.current()); + return state.tokenize(stream, state); + } + + // Handle operators and Delimiters + if (stream.match(operators)) { + return 'operator'; + } + + if (stream.match(delimiters)) { + return null; + } + + if (stream.match(keywords)) { + return 'keyword'; + } + + if (stream.match(builtins)) { + return 'builtin'; + } + + if (stream.match(macro)) { + return 'meta'; + } + + if (stream.match(identifiers)) { + state.leaving_expr=true; + return 'variable'; + } + // Handle non-detected items + stream.next(); + return ERRORCLASS; + } + + function tokenStringFactory(delimiter) { + while ('rub'.indexOf(delimiter.charAt(0).toLowerCase()) >= 0) { + delimiter = delimiter.substr(1); + } + var singleline = delimiter.length == 1; + var OUTCLASS = 'string'; + + function tokenString(stream, state) { + while (!stream.eol()) { + stream.eatWhile(/[^'"\\]/); + if (stream.eat('\\')) { + stream.next(); + if (singleline && stream.eol()) { + return OUTCLASS; + } + } else if (stream.match(delimiter)) { + state.tokenize = tokenBase; + return OUTCLASS; + } else { + stream.eat(/['"]/); + } + } + if (singleline) { + if (parserConf.singleLineStringErrors) { + return ERRORCLASS; + } else { + state.tokenize = tokenBase; + } + } + return OUTCLASS; + } + tokenString.isString = true; + return tokenString; + } + + function tokenLexer(stream, state) { + indentInfo = null; + var style = state.tokenize(stream, state); + var current = stream.current(); + + // Handle '.' connected identifiers + if (current === '.') { + style = stream.match(identifiers, false) ? null : ERRORCLASS; + if (style === null && state.lastStyle === 'meta') { + // Apply 'meta' style to '.' connected identifiers when + // appropriate. + style = 'meta'; + } + return style; + } + + return style; + } + + var external = { + startState: function() { + return { + tokenize: tokenBase, + scopes: [], + leaving_expr: false + }; + }, + + token: function(stream, state) { + var style = tokenLexer(stream, state); + state.lastStyle = style; + return style; + }, + + indent: function(state, textAfter) { + var delta = 0; + if(textAfter=="end" || textAfter=="]" || textAfter=="}" || textAfter=="else" || textAfter=="elseif" || textAfter=="catch" || textAfter=="finally") { + delta = -1; + } + return (state.scopes.length + delta) * 2; + }, + + lineComment: "#", + fold: "indent", + electricChars: "edlsifyh]}" + }; + return external; +}); + + +CodeMirror.defineMIME("text/x-julia", "julia"); diff --git a/mode/less/less.js b/mode/less/less.js index ec62319080..da39074851 100644 --- a/mode/less/less.js +++ b/mode/less/less.js @@ -70,7 +70,9 @@ CodeMirror.defineMode("less", function(config) { stream.eatWhile(/[\a-zA-Z0-9\-_]/); if(stream.peek() === " ")stream.eatSpace(); if(stream.peek() === ")" || type === ":")return ret("number", "unit");//rgba(0,0,0,.25); - else if(state.stack[state.stack.length-1] === "rule" && stream.peek().match(/{|,|\+|\(/) === null)return ret("number", "unit"); + else if(stream.current().length >1){ + if(state.stack[state.stack.length-1] === "rule" && stream.peek().match(/{|,|\+|\(/) === null)return ret("number", "unit"); + } return ret("tag", "tag"); } else if (ch == "#") { //we don't eat white-space, we want the hex color and or id only @@ -204,7 +206,7 @@ CodeMirror.defineMode("less", function(config) { else if(type === "unit" && state.stack[state.stack.length-1] === "rule")return ret(null, "unit"); else if(type === "unit" && state.stack[state.stack.length-1] === ";")return ret(null, "unit"); else if(type === ")" && state.stack[state.stack.length-1] === "rule")return ret(null, "unit"); - else if(type.match("@") !== null && state.stack[state.stack.length-1] === "rule")return ret(null, "unit"); + else if(type && type.match("@") !== null && state.stack[state.stack.length-1] === "rule")return ret(null, "unit"); //else if(type === "unit" && state.stack[state.stack.length-1] === "rule")return ret(null, stream.current()); else if((type === ";" || type === "}" || type === ",") && state.stack[state.stack.length-1] === ";")return ret("tag", stream.current()); diff --git a/mode/markdown/markdown.js b/mode/markdown/markdown.js index bf1750d5b6..218408fb53 100644 --- a/mode/markdown/markdown.js +++ b/mode/markdown/markdown.js @@ -74,7 +74,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { , ulRE = /^[*\-+]\s+/ , olRE = /^[0-9]+\.\s+/ , taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE - , headerRE = /^(?:\={1,}|-{1,})$/ + , atxHeaderRE = /^#+/ + , setextHeaderRE = /^(?:\={1,}|-{1,})$/ , textRE = /^[^!\[\]*_\\<>` "'(]+/; function switchInline(stream, state, f) { @@ -127,14 +128,17 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { state.listDepth = 0; } + var match = null; if (state.indentationDiff >= 4) { state.indentation -= 4; stream.skipToEnd(); return code; } else if (stream.eatSpace()) { return null; - } else if (stream.peek() === '#' || (state.prevLineHasContent && stream.match(headerRE)) ) { - state.header = true; + } else if (match = stream.match(atxHeaderRE)) { + state.header = match[0].length <= 6 ? match[0].length : 6; + } else if (state.prevLineHasContent && (match = stream.match(setextHeaderRE))) { + state.header = match[0].charAt(0) == '=' ? 1 : 2; } else if (stream.eat('>')) { state.indentation++; state.quote = 1; @@ -207,7 +211,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { if (state.code) { styles.push(code); } - if (state.header) { styles.push(header); } + if (state.header) { styles.push(header); styles.push(header + state.header); } if (state.quote) { styles.push(state.quote % 2 ? quote1 : quote2); } if (state.list !== false) { var listMod = (state.listDepth - 1) % 3; @@ -257,6 +261,9 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { state.taskOpen = false; state.taskClosed = false; + // Get sol() value now, before character is consumed + var sol = stream.sol(); + var ch = stream.next(); if (ch === '\\') { @@ -355,7 +362,9 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { } var t = getType(state); if (ch === '*' || (ch === '_' && !ignoreUnderscore)) { - if (state.strong === ch && stream.eat(ch)) { // Remove STRONG + if (sol && stream.peek() === ' ') { + // Do nothing, surrounded by newline and space + } else if (state.strong === ch && stream.eat(ch)) { // Remove STRONG state.strong = false; return t; } else if (!state.strong && stream.eat(ch)) { // Add STRONG @@ -466,7 +475,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { linkTitle: false, em: false, strong: false, - header: false, + header: 0, taskList: false, list: false, listDepth: 0, @@ -517,7 +526,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { } // Reset state.header - state.header = false; + state.header = 0; // Reset state.taskList state.taskList = false; diff --git a/mode/markdown/test.js b/mode/markdown/test.js index f167917289..6bf4006550 100644 --- a/mode/markdown/test.js +++ b/mode/markdown/test.js @@ -87,27 +87,31 @@ // http://daringfireball.net/projects/markdown/syntax#header MT("atxH1", - "[header # foo]"); + "[header&header1 # foo]"); MT("atxH2", - "[header ## foo]"); + "[header&header2 ## foo]"); MT("atxH3", - "[header ### foo]"); + "[header&header3 ### foo]"); MT("atxH4", - "[header #### foo]"); + "[header&header4 #### foo]"); MT("atxH5", - "[header ##### foo]"); + "[header&header5 ##### foo]"); MT("atxH6", - "[header ###### foo]"); + "[header&header6 ###### foo]"); // H6 - 7x '#' should still be H6, per Dingus // http://daringfireball.net/projects/markdown/dingus MT("atxH6NotH7", - "[header ####### foo]"); + "[header&header6 ####### foo]"); + + // Inline styles should be parsed inside headers + MT("atxH1inline", + "[header&header1 # foo ][header&header1&em *bar*]"); // Setext headers - H1, H2 // Per documentation, "Any number of underlining =’s or -’s will work." @@ -119,22 +123,22 @@ // Check if single underlining = works MT("setextH1", "foo", - "[header =]"); + "[header&header1 =]"); // Check if 3+ ='s work MT("setextH1", "foo", - "[header ===]"); + "[header&header1 ===]"); // Check if single underlining - works MT("setextH2", "foo", - "[header -]"); + "[header&header2 -]"); // Check if 3+ -'s work MT("setextH2", "foo", - "[header ---]"); + "[header&header2 ---]"); // Single-line blockquote with trailing space MT("blockquoteSpace", @@ -577,6 +581,10 @@ MT("emEscapedBySpaceOut", "foo _ bar[em _hello_]world"); + MT("emEscapedByNewline", + "foo", + "_ bar[em _hello_]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. diff --git a/mode/meta.js b/mode/meta.js index f6bbd1b45c..226cff12e5 100644 --- a/mode/meta.js +++ b/mode/meta.js @@ -36,7 +36,8 @@ CodeMirror.modeInfo = [ {name: 'JSON', mime: 'application/x-json', mode: 'javascript'}, {name: 'JSON', mime: 'application/json', mode: 'javascript'}, {name: 'TypeScript', mime: 'application/typescript', mode: 'javascript'}, - {name: 'Jinja2', mime: 'jinja2', mode: 'jinja2'}, + {name: 'Jinja2', mime: null, mode: 'jinja2'}, + {name: 'Julia', mime: 'text/x-julia', mode: 'julia'}, {name: 'LESS', mime: 'text/x-less', mode: 'less'}, {name: 'LiveScript', mime: 'text/x-livescript', mode: 'livescript'}, {name: 'Lua', mime: 'text/x-lua', mode: 'lua'}, @@ -47,6 +48,7 @@ CodeMirror.modeInfo = [ {name: 'OCaml', mime: 'text/x-ocaml', mode: 'ocaml'}, {name: 'Octave', mime: 'text/x-octave', mode: 'octave'}, {name: 'Pascal', mime: 'text/x-pascal', mode: 'pascal'}, + {name: 'PEG.js', mime: null, mode: 'pegjs'}, {name: 'Perl', mime: 'text/x-perl', mode: 'perl'}, {name: 'PHP', mime: 'text/x-php', mode: 'php'}, {name: 'PHP(HTML)', mime: 'application/x-httpd-php', mode: 'php'}, diff --git a/mode/pegjs/index.html b/mode/pegjs/index.html new file mode 100644 index 0000000000..678b714d8e --- /dev/null +++ b/mode/pegjs/index.html @@ -0,0 +1,66 @@ + + + + CodeMirror: PEG.js Mode + + + + + + + + + + + + +
+

PEG.js Mode

+
+ +

The PEG.js Mode

+

Created by Forbes Lindesay.

+
+ + diff --git a/mode/pegjs/pegjs.js b/mode/pegjs/pegjs.js new file mode 100644 index 0000000000..6cdcc61f30 --- /dev/null +++ b/mode/pegjs/pegjs.js @@ -0,0 +1,103 @@ +CodeMirror.defineMode("pegjs", function (config) { + var jsMode = CodeMirror.getMode(config, "javascript"); + + function identifier(stream) { + return stream.match(/^[a-zA-Z_][a-zA-Z0-9_]*/); + } + + return { + startState: function () { + return { + inString: false, + stringType: null, + inComment: false, + inChracterClass: false, + braced: 0, + lhs: true, + localState: null + }; + }, + token: function (stream, state) { + if (stream) + + //check for state changes + if (!state.inString && !state.inComment && ((stream.peek() == '"') || (stream.peek() == "'"))) { + state.stringType = stream.peek(); + stream.next(); // Skip quote + state.inString = true; // Update state + } + if (!state.inString && !state.inComment && stream.match(/^\/\*/)) { + state.inComment = true; + } + + //return state + if (state.inString) { + while (state.inString && !stream.eol()) { + if (stream.peek() === state.stringType) { + stream.next(); // Skip quote + state.inString = false; // Clear flag + } else if (stream.peek() === '\\') { + stream.next(); + stream.next(); + } else { + stream.match(/^.[^\\\"\']*/); + } + } + return state.lhs ? "property string" : "string"; // Token style + } else if (state.inComment) { + while (state.inComment && !stream.eol()) { + if (stream.match(/\*\//)) { + state.inComment = false; // Clear flag + } else { + stream.match(/^.[^\*]*/); + } + } + return "comment"; + } else if (state.inChracterClass) { + if (stream.match(/^[^\]\\]+/)) { + return; + } else if (stream.match(/^\\./)) { + return; + } else { + stream.next(); + state.inChracterClass = false; + return 'bracket'; + } + } else if (stream.peek() === '[') { + stream.next(); + state.inChracterClass = true; + return 'bracket'; + } else if (stream.match(/^\/\//)) { + stream.skipToEnd(); + return "comment"; + } else if (state.braced || stream.peek() === '{') { + if (state.localState === null) { + state.localState = jsMode.startState(); + } + var token = jsMode.token(stream, state.localState); + var text = stream.current(); + if (!token) { + for (var i = 0; i < text.length; i++) { + if (text[i] === '{') { + state.braced++; + } else if (text[i] === '}') { + state.braced--; + } + }; + } + return token; + } else if (identifier(stream)) { + if (stream.peek() === ':') { + return 'variable'; + } + return 'variable-2'; + } else if (['[', ']', '(', ')'].indexOf(stream.peek()) != -1) { + stream.next(); + return 'bracket'; + } else if (!stream.eatSpace()) { + stream.next(); + } + return null; + } + }; +}, "javascript"); diff --git a/mode/pig/pig.js b/mode/pig/pig.js index c2f611a1a0..4b44e7ccc3 100644 --- a/mode/pig/pig.js +++ b/mode/pig/pig.js @@ -157,7 +157,7 @@ CodeMirror.defineMode("pig", function(_config, parserConfig) { + "JOIN CROSS UNION SPLIT INTO IF OTHERWISE ALL AS BY USING INNER OUTER ONSCHEMA PARALLEL " + "PARTITION GROUP AND OR NOT GENERATE FLATTEN ASC DESC IS STREAM THROUGH STORE MAPREDUCE " + "SHIP CACHE INPUT OUTPUT STDERROR STDIN STDOUT LIMIT SAMPLE LEFT RIGHT FULL EQ GT LT GTE LTE " - + "NEQ MATCHES TRUE FALSE "; + + "NEQ MATCHES TRUE FALSE DUMP"; // data types var pTypes = "BOOLEAN INT LONG FLOAT DOUBLE CHARARRAY BYTEARRAY BAG TUPLE MAP "; diff --git a/package.json b/package.json index 8310ab1551..6c5a122ea2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version":"3.19.0", + "version":"3.20.0", "main": "lib/codemirror.js", "description": "In-browser code editing made bearable", "licenses": [{"type": "MIT", diff --git a/theme/ambiance.css b/theme/ambiance.css index a69d16e5a5..3a54b2a022 100644 --- a/theme/ambiance.css +++ b/theme/ambiance.css @@ -33,7 +33,7 @@ .cm-s-ambiance .CodeMirror-selected { background: rgba(255, 255, 255, 0.15); } -.cm-s-ambiance .CodeMirror-focused .CodeMirror-selected { +.cm-s-ambiance.CodeMirror-focused .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); } diff --git a/theme/mbo.css b/theme/mbo.css index f3250a7313..93fe3ee24c 100644 --- a/theme/mbo.css +++ b/theme/mbo.css @@ -24,9 +24,11 @@ .cm-s-mbo .CodeMirror-activeline-background {background: #494b41 !important;} .cm-s-mbo .CodeMirror-matchingbracket { - text-decoration: underline; + text-decoration: underline; color: #f5e107 !important; } + +.cm-s-mbo .CodeMirror-matchingtag {background: #4e4e4e;} div.CodeMirror span.CodeMirror-searching { background-color: none;