diff --git a/AUTHORS b/AUTHORS index 0c2f67c31a..acc10fe16d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -24,12 +24,14 @@ Alexandre Bique alexey-k Alex Piggott Amsul +amuntean Amy Ananya Sen anaran AndersMad Anders Nawroth Anderson Mesquita +Andreas Reischuck Andre von Houck Andrey Lushnikov Andy Joslin @@ -128,6 +130,7 @@ Gabriel Horner Gabriel Nahmias galambalazs Gautam Mehta +gekkoe Gergely Hegykozi Glenn Jorde Glenn Ruehle @@ -138,6 +141,7 @@ greengiant Guillaume Massé Guillaume Massé Gustavo Rodrigues +Hakan Tunc Hans Engel Hardest Hasan Karahan @@ -268,6 +272,7 @@ nextrevision nguillaumin Ng Zhi An Nicholas Bollweg +Nick Small Niels van Groningen Nikita Beloglazov Nikita Vasilyev @@ -334,6 +339,7 @@ Takuji Shimokawa Tarmil tfjgeorge Thaddee Tyl +TheHowl think Thomas Dvornik Thomas Schmid diff --git a/addon/comment/comment.js b/addon/comment/comment.js index cb78340231..d7f569cc00 100644 --- a/addon/comment/comment.js +++ b/addon/comment/comment.js @@ -157,12 +157,12 @@ // Positions of the last startString before the start of the selection, and the first endString after it. var lastStart = startLine.lastIndexOf(startString, from.ch); var firstEnd = lastStart == -1 ? -1 : startLine.slice(0, from.ch).indexOf(endString, lastStart + startString.length); - if (lastStart != -1 && firstEnd != -1) return false; + if (lastStart != -1 && firstEnd != -1 && firstEnd + endString.length != from.ch) return false; // Positions of the first endString after the end of the selection, and the last startString before it. firstEnd = endLine.indexOf(endString, to.ch); var almostLastStart = endLine.slice(to.ch).lastIndexOf(startString, firstEnd - to.ch); lastStart = (firstEnd == -1 || almostLastStart == -1) ? -1 : to.ch + almostLastStart; - if (firstEnd != -1 && lastStart != -1) return false; + if (firstEnd != -1 && lastStart != -1 && lastStart != to.ch) return false; self.operation(function() { self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)), diff --git a/addon/dialog/dialog.js b/addon/dialog/dialog.js index 622d689ed1..946040cb91 100644 --- a/addon/dialog/dialog.js +++ b/addon/dialog/dialog.js @@ -15,11 +15,11 @@ var wrap = cm.getWrapperElement(); var dialog; dialog = wrap.appendChild(document.createElement("div")); - if (bottom) { + if (bottom) dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom"; - } else { + else dialog.className = "CodeMirror-dialog CodeMirror-dialog-top"; - } + if (typeof template == "string") { dialog.innerHTML = template; } else { // Assuming it's a detached DOM element. @@ -35,8 +35,11 @@ } CodeMirror.defineExtension("openDialog", function(template, callback, options) { + if (!options) options = {}; + closeNotification(this, null); - var dialog = dialogDiv(this, template, options && options.bottom); + + var dialog = dialogDiv(this, template, options.bottom); var closed = false, me = this; function close(newVal) { if (typeof newVal == 'string') { @@ -45,34 +48,43 @@ if (closed) return; closed = true; dialog.parentNode.removeChild(dialog); + me.focus(); + + if (options.onClose) options.onClose(dialog); } } + var inp = dialog.getElementsByTagName("input")[0], button; if (inp) { - if (options && options.value) inp.value = options.value; + if (options.value) inp.value = options.value; + + if (options.onInput) + CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);}); + if (options.onKeyUp) + CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);}); + CodeMirror.on(inp, "keydown", function(e) { if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; } - if (e.keyCode == 13 || e.keyCode == 27) { + if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) { inp.blur(); CodeMirror.e_stop(e); close(); - me.focus(); - if (e.keyCode == 13) callback(inp.value); } + if (e.keyCode == 13) callback(inp.value); }); - if (options && options.onKeyUp) { - CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);}); - } - if (options && options.value) inp.value = options.value; + + if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close); + inp.focus(); - CodeMirror.on(inp, "blur", close); } else if (button = dialog.getElementsByTagName("button")[0]) { CodeMirror.on(button, "click", function() { close(); me.focus(); }); + + if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close); + button.focus(); - CodeMirror.on(button, "blur", close); } return close; }); diff --git a/addon/edit/closetag.js b/addon/edit/closetag.js index 414498bcd9..69ea4446be 100644 --- a/addon/edit/closetag.js +++ b/addon/edit/closetag.js @@ -109,6 +109,10 @@ replacements[i] = "/" + state.context.tagName + ">"; } cm.replaceSelections(replacements); + ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) + if (i == ranges.length - 1 || ranges[i].head.line < ranges[i + 1].head.line) + cm.indentLine(ranges[i].head.line); } function indexOf(collection, elt) { diff --git a/addon/fold/xml-fold.js b/addon/fold/xml-fold.js index a45da58422..504727f38c 100644 --- a/addon/fold/xml-fold.js +++ b/addon/fold/xml-fold.js @@ -151,8 +151,9 @@ if (iter.text.indexOf(">") == -1 && iter.text.indexOf("<") == -1) return; var end = toTagEnd(iter), to = end && Pos(iter.line, iter.ch); var start = end && toTagStart(iter); - if (!end || end == "selfClose" || !start || cmp(iter, pos) > 0) return; + if (!end || !start || cmp(iter, pos) > 0) return; var here = {from: Pos(iter.line, iter.ch), to: to, tag: start[2]}; + if (end == "selfClose") return {open: here, close: null, at: "open"}; if (start[1]) { // closing tag return {open: findMatchingOpen(iter, start[2]), close: here, at: "close"}; diff --git a/addon/hint/sql-hint.js b/addon/hint/sql-hint.js index fd58b8834e..20653b5318 100644 --- a/addon/hint/sql-hint.js +++ b/addon/hint/sql-hint.js @@ -21,7 +21,7 @@ function getKeywords(editor) { var mode = editor.doc.modeOption; - if(mode === "sql") mode = "text/x-sql"; + if (mode === "sql") mode = "text/x-sql"; return CodeMirror.resolveMode(mode).keywords; } @@ -32,12 +32,12 @@ } function addMatches(result, search, wordlist, formatter) { - for(var word in wordlist) { - if(!wordlist.hasOwnProperty(word)) continue; - if(Array.isArray(wordlist)) { + for (var word in wordlist) { + if (!wordlist.hasOwnProperty(word)) continue; + if (Array.isArray(wordlist)) { word = wordlist[word]; } - if(match(search, word)) { + if (match(search, word)) { result.push(formatter(word)); } } @@ -49,33 +49,30 @@ var string = token.string.substr(1); var prevCur = Pos(cur.line, token.start); var table = editor.getTokenAt(prevCur).string; - if( !tables.hasOwnProperty( table ) ){ + if (!tables.hasOwnProperty(table)) table = findTableByAlias(table, editor); - } var columns = tables[table]; - if(!columns) { - return; - } - addMatches(result, string, columns, - function(w) {return "." + w;}); + if (!columns) return; + + addMatches(result, string, columns, function(w) {return "." + w;}); } function eachWord(lineText, f) { - if( !lineText ){return;} + if (!lineText) return; var excepted = /[,;]/g; - var words = lineText.split( " " ); - for( var i = 0; i < words.length; i++ ){ - f( words[i]?words[i].replace( excepted, '' ) : '' ); + var words = lineText.split(" "); + for (var i = 0; i < words.length; i++) { + f(words[i]?words[i].replace(excepted, '') : ''); } } - function convertCurToNumber( cur ){ + function convertCurToNumber(cur) { // max characters of a line is 999,999. - return cur.line + cur.ch / Math.pow( 10, 6 ); + return cur.line + cur.ch / Math.pow(10, 6); } - function convertNumberToCur( num ){ - return Pos(Math.floor( num ), +num.toString().split( '.' ).pop()); + function convertNumberToCur(num) { + return Pos(Math.floor(num), +num.toString().split('.').pop()); } function findTableByAlias(alias, editor) { @@ -86,26 +83,26 @@ var table = ""; var separator = []; var validRange = { - start: Pos( 0, 0 ), - end: Pos( editor.lastLine(), editor.getLineHandle( editor.lastLine() ).length ) + start: Pos(0, 0), + end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length) }; //add separator - var indexOfSeparator = fullQuery.indexOf( CONS.QUERY_DIV ); - while( indexOfSeparator != -1 ){ - separator.push( doc.posFromIndex(indexOfSeparator)); - indexOfSeparator = fullQuery.indexOf( CONS.QUERY_DIV, indexOfSeparator+1); + var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV); + while(indexOfSeparator != -1) { + separator.push(doc.posFromIndex(indexOfSeparator)); + indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator+1); } - separator.unshift( Pos( 0, 0 ) ); - separator.push( Pos( editor.lastLine(), editor.getLineHandle( editor.lastLine() ).text.length ) ); + separator.unshift(Pos(0, 0)); + separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length)); - //find valieRange + //find valid range var prevItem = 0; - var current = convertCurToNumber( editor.getCursor() ); - for( var i=0; i< separator.length; i++){ - var _v = convertCurToNumber( separator[i] ); - if( current > prevItem && current <= _v ){ - validRange = { start: convertNumberToCur( prevItem ), end: convertNumberToCur( _v ) }; + var current = convertCurToNumber(editor.getCursor()); + for (var i=0; i< separator.length; i++) { + var _v = convertCurToNumber(separator[i]); + if (current > prevItem && current <= _v) { + validRange = { start: convertNumberToCur(prevItem), end: convertNumberToCur(_v) }; break; } prevItem = _v; @@ -113,52 +110,51 @@ var query = doc.getRange(validRange.start, validRange.end, false); - for(var i=0; i < query.length; i++){ + for (var i = 0; i < query.length; i++) { var lineText = query[i]; - eachWord( lineText, function( word ){ + eachWord(lineText, function(word) { var wordUpperCase = word.toUpperCase(); - if( wordUpperCase === aliasUpperCase && tables.hasOwnProperty( previousWord ) ){ + if (wordUpperCase === aliasUpperCase && tables.hasOwnProperty(previousWord)) { table = previousWord; } - if( wordUpperCase !== CONS.ALIAS_KEYWORD ){ + if (wordUpperCase !== CONS.ALIAS_KEYWORD) { previousWord = word; } }); - if( table ){ break; } + if (table) break; } return table; } - function sqlHint(editor, options) { + CodeMirror.registerHelper("hint", "sql", function(editor, options) { tables = (options && options.tables) || {}; keywords = keywords || getKeywords(editor); var cur = editor.getCursor(); - var token = editor.getTokenAt(cur), end = token.end; var result = []; - var search = token.string.trim(); - + var token = editor.getTokenAt(cur), start, end, search; + if (token.string.match(/^[.\w@]\w*$/)) { + search = token.string; + start = token.start; + end = token.end; + } else { + start = end = cur.ch; + search = ""; + } if (search.charAt(0) == ".") { columnCompletion(result, editor); if (!result.length) { - while (token.start && search.charAt(0) == ".") { + while (start && search.charAt(0) == ".") { token = editor.getTokenAt(Pos(cur.line, token.start - 1)); + start = token.start; search = token.string + search; } - addMatches(result, search, tables, - function(w) {return w;}); + addMatches(result, search, tables, function(w) {return w;}); } } else { - addMatches(result, search, keywords, - function(w) {return w.toUpperCase();}); - addMatches(result, search, tables, - function(w) {return w;}); + addMatches(result, search, tables, function(w) {return w;}); + addMatches(result, search, keywords, function(w) {return w.toUpperCase();}); } - return { - list: result, - from: Pos(cur.line, token.start), - to: Pos(cur.line, end) - }; - } - CodeMirror.registerHelper("hint", "sql", sqlHint); + return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)}; + }); }); diff --git a/addon/merge/merge.css b/addon/merge/merge.css index 63237fc8e9..5d24b9bb7f 100644 --- a/addon/merge/merge.css +++ b/addon/merge/merge.css @@ -62,6 +62,12 @@ color: #44c; } +.CodeMirror-merge-copy-reverse { + position: absolute; + cursor: pointer; + color: #44c; +} + .CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy { left: 2px; } .CodeMirror-merge-copybuttons-right .CodeMirror-merge-copy { right: 2px; } diff --git a/addon/merge/merge.js b/addon/merge/merge.js index 3583936198..d9b277664b 100644 --- a/addon/merge/merge.js +++ b/addon/merge/merge.js @@ -37,7 +37,7 @@ constructor: DiffView, init: function(pane, orig, options) { this.edit = this.mv.edit; - this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: true}, copyObj(options))); + 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; @@ -71,7 +71,7 @@ function update(mode) { if (mode == "full") { if (dv.svg) clear(dv.svg); - clear(dv.copyButtons); + if (dv.copyButtons) clear(dv.copyButtons); clearMarks(dv.edit, edit.marked, dv.classes); clearMarks(dv.orig, orig.marked, dv.classes); edit.from = edit.to = orig.from = orig.to = 0; @@ -257,7 +257,7 @@ var w = dv.gap.offsetWidth; attrs(dv.svg, "width", w, "height", dv.gap.offsetHeight); } - clear(dv.copyButtons); + if (dv.copyButtons) clear(dv.copyButtons); var flip = dv.type == "left"; var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport(); @@ -279,17 +279,30 @@ "d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z", "class", dv.classes.connect); } - var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc", - "CodeMirror-merge-copy")); - copy.title = "Revert chunk"; - copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig}; - copy.style.top = top + "px"; + if (dv.copyButtons) { + var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc", + "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.style.top = top + "px"; + + if (editOriginals) { + var topReverse = dv.orig.heightAtLine(topEdit, "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.style.top = topReverse + "px"; + dv.type == "right" ? copyReverse.style.left = "2px" : copyReverse.style.right = "2px"; + } + } }); } - function copyChunk(dv, chunk) { + function copyChunk(dv, to, from, chunk) { if (dv.diffOutOfDate) return; - dv.edit.replaceRange(dv.orig.getRange(Pos(chunk.topOrig, 0), Pos(chunk.botOrig, 0)), + to.replaceRange(from.getRange(Pos(chunk.topOrig, 0), Pos(chunk.botOrig, 0)), Pos(chunk.topEdit, 0), Pos(chunk.botEdit, 0)); } @@ -298,6 +311,7 @@ var MergeView = CodeMirror.MergeView = function(node, options) { if (!(this instanceof MergeView)) return new MergeView(node, options); + this.options = options; var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight; var hasLeft = origLeft != null, hasRight = origRight != null; var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0); @@ -323,6 +337,7 @@ (hasRight ? rightPane : editPane).className += " CodeMirror-merge-pane-rightmost"; wrap.push(elt("div", null, null, "height: 0; clear: both;")); + var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-merge CodeMirror-merge-" + panes + "pane")); this.edit = CodeMirror(editPane, copyObj(options)); @@ -345,12 +360,20 @@ lock.title = "Toggle locked scrolling"; var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap"); CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); }); - dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type); - CodeMirror.on(dv.copyButtons, "click", function(e) { - var node = e.target || e.srcElement; - if (node.chunk) copyChunk(dv, node.chunk); - }); - var gapElts = [dv.copyButtons, lockWrap]; + var gapElts = [lockWrap]; + if (dv.mv.options.revertButtons !== false) { + dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type); + CodeMirror.on(dv.copyButtons, "click", function(e) { + var node = e.target || e.srcElement; + if (!node.chunk) return; + if (node.className == "CodeMirror-merge-copy-reverse") { + copyChunk(dv, dv.orig, dv.edit, node.chunk); + return; + } + copyChunk(dv, dv.edit, dv.orig, node.chunk); + }); + gapElts.unshift(dv.copyButtons); + } var svg = document.createElementNS && document.createElementNS(svgNS, "svg"); if (svg && !svg.createSVGRect) svg = null; dv.svg = svg; diff --git a/addon/search/search.js b/addon/search/search.js index b177dce6ed..c25aeda8b2 100644 --- a/addon/search/search.js +++ b/addon/search/search.js @@ -71,7 +71,7 @@ return query; } var queryDialog = - 'Search: (Use /re/ syntax for regexp search)'; + 'Search: (Use /re/ syntax for regexp search)'; function doSearch(cm, rev) { var state = getSearchState(cm); if (state.query) return findNext(cm, rev); @@ -106,8 +106,8 @@ });} var replaceQueryDialog = - 'Replace: (Use /re/ syntax for regexp search)'; - var replacementQueryDialog = 'With: '; + 'Replace: (Use /re/ syntax for regexp search)'; + var replacementQueryDialog = 'With: '; var doReplaceConfirm = "Replace? "; function replace(cm, all) { if (cm.getOption("readOnly")) return; diff --git a/bower.json b/bower.json index 8c57fcdd4b..9cf2abecfe 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "codemirror", - "version":"4.4.0", + "version":"4.5.0", "main": ["lib/codemirror.js", "lib/codemirror.css"], "ignore": [ "**/.*", diff --git a/doc/compress.html b/doc/compress.html index 859210c45c..46c102af47 100644 --- a/doc/compress.html +++ b/doc/compress.html @@ -36,6 +36,7 @@

Version: + + +

MIME types defined: application/x-slim.

+ +

+ Parsing/Highlighting Tests: + normal, + verbose. +

+ diff --git a/mode/slim/slim.js b/mode/slim/slim.js new file mode 100644 index 0000000000..164464d066 --- /dev/null +++ b/mode/slim/slim.js @@ -0,0 +1,575 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// Slim Highlighting for CodeMirror copyright (c) HicknHack Software Gmbh + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../ruby/ruby")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../ruby/ruby"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + + CodeMirror.defineMode("slim", function(config) { + var htmlMode = CodeMirror.getMode(config, {name: "htmlmixed"}); + var rubyMode = CodeMirror.getMode(config, "ruby"); + var modes = { html: htmlMode, ruby: rubyMode }; + var embedded = { + ruby: "ruby", + javascript: "javascript", + css: "text/css", + sass: "text/x-sass", + scss: "text/x-scss", + less: "text/x-less", + styl: "text/x-styl", // no highlighting so far + coffee: "coffeescript", + asciidoc: "text/x-asciidoc", + markdown: "text/x-markdown", + textile: "text/x-textile", // no highlighting so far + creole: "text/x-creole", // no highlighting so far + wiki: "text/x-wiki", // no highlighting so far + mediawiki: "text/x-mediawiki", // no highlighting so far + rdoc: "text/x-rdoc", // no highlighting so far + builder: "text/x-builder", // no highlighting so far + nokogiri: "text/x-nokogiri", // no highlighting so far + erb: "application/x-erb" + }; + var embeddedRegexp = function(map){ + var arr = []; + for(var key in map) arr.push(key); + return new RegExp("^("+arr.join('|')+"):"); + }(embedded); + + var styleMap = { + "commentLine": "comment", + "slimSwitch": "operator special", + "slimTag": "tag", + "slimId": "attribute def", + "slimClass": "attribute qualifier", + "slimAttribute": "attribute", + "slimSubmode": "keyword special", + "closeAttributeTag": null, + "slimDoctype": null, + "lineContinuation": null + }; + var closing = { + "{": "}", + "[": "]", + "(": ")" + }; + + var nameStartChar = "_a-zA-Z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD"; + var nameChar = nameStartChar + "\\-0-9\xB7\u0300-\u036F\u203F-\u2040"; + var nameRegexp = new RegExp("^[:"+nameStartChar+"](?::["+nameChar+"]|["+nameChar+"]*)"); + var attributeNameRegexp = new RegExp("^[:"+nameStartChar+"][:\\."+nameChar+"]*(?=\\s*=)"); + var wrappedAttributeNameRegexp = new RegExp("^[:"+nameStartChar+"][:\\."+nameChar+"]*"); + var classNameRegexp = /^\.-?[_a-zA-Z]+[\w\-]*/; + var classIdRegexp = /^#[_a-zA-Z]+[\w\-]*/; + + function backup(pos, tokenize, style) { + var restore = function(stream, state) { + state.tokenize = tokenize; + if (stream.pos < pos) { + stream.pos = pos; + return style; + } + return state.tokenize(stream, state); + }; + return function(stream, state) { + state.tokenize = restore; + return tokenize(stream, state); + }; + } + + function maybeBackup(stream, state, pat, offset, style) { + var cur = stream.current(); + var idx = cur.search(pat); + if (idx > -1) { + state.tokenize = backup(stream.pos, state.tokenize, style); + stream.backUp(cur.length - idx - offset); + } + return style; + } + + function continueLine(state, column) { + state.stack = { + parent: state.stack, + style: "continuation", + indented: column, + tokenize: state.line + }; + state.line = state.tokenize; + } + function finishContinue(state) { + if (state.line == state.tokenize) { + state.line = state.stack.tokenize; + state.stack = state.stack.parent; + } + } + + function lineContinuable(column, tokenize) { + return function(stream, state) { + finishContinue(state); + if (stream.match(/^\\$/)) { + continueLine(state, column); + return "lineContinuation"; + } + var style = tokenize(stream, state); + if (stream.eol() && stream.current().match(/(?:^|[^\\])(?:\\\\)*\\$/)) { + stream.backUp(1); + } + return style; + }; + } + function commaContinuable(column, tokenize) { + return function(stream, state) { + finishContinue(state); + var style = tokenize(stream, state); + if (stream.eol() && stream.current().match(/,$/)) { + continueLine(state, column); + } + return style; + }; + } + + function rubyInQuote(endQuote, tokenize) { + // TODO: add multi line support + return function(stream, state) { + var ch = stream.peek(); + if (ch == endQuote && state.rubyState.tokenize.length == 1) { + // step out of ruby context as it seems to complete processing all the braces + stream.next(); + state.tokenize = tokenize; + return "closeAttributeTag"; + } else { + return ruby(stream, state); + } + }; + } + function startRubySplat(tokenize) { + var rubyState; + var runSplat = function(stream, state) { + if (state.rubyState.tokenize.length == 1 && !state.rubyState.context.prev) { + stream.backUp(1); + if (stream.eatSpace()) { + state.rubyState = rubyState; + state.tokenize = tokenize; + return tokenize(stream, state); + } + stream.next(); + } + return ruby(stream, state); + }; + return function(stream, state) { + rubyState = state.rubyState; + state.rubyState = rubyMode.startState(); + state.tokenize = runSplat; + return ruby(stream, state); + }; + } + + function ruby(stream, state) { + return rubyMode.token(stream, state.rubyState); + } + + function htmlLine(stream, state) { + if (stream.match(/^\\$/)) { + return "lineContinuation"; + } + return html(stream, state); + } + function html(stream, state) { + if (stream.match(/^#\{/)) { + state.tokenize = rubyInQuote("}", state.tokenize); + return null; + } + return maybeBackup(stream, state, /[^\\]#\{/, 1, htmlMode.token(stream, state.htmlState)); + } + + function startHtmlLine(lastTokenize) { + return function(stream, state) { + var style = htmlLine(stream, state); + if (stream.eol()) state.tokenize = lastTokenize; + return style; + }; + } + + function startHtmlMode(stream, state, offset) { + state.stack = { + parent: state.stack, + style: "html", + indented: stream.column() + offset, // pipe + space + tokenize: state.line + }; + state.line = state.tokenize = html; + return null; + } + + function comment(stream, state) { + stream.skipToEnd(); + return state.stack.style; + } + + function commentMode(stream, state) { + state.stack = { + parent: state.stack, + style: "comment", + indented: state.indented + 1, + tokenize: state.line + }; + state.line = comment; + return comment(stream, state); + } + + function attributeWrapper(stream, state) { + if (stream.eat(state.stack.endQuote)) { + state.line = state.stack.line; + state.tokenize = state.stack.tokenize; + state.stack = state.stack.parent; + return null; + } + if (stream.match(wrappedAttributeNameRegexp)) { + state.tokenize = attributeWrapperAssign; + return "slimAttribute"; + } + stream.next(); + return null; + } + function attributeWrapperAssign(stream, state) { + if (stream.match(/^==?/)) { + state.tokenize = attributeWrapperValue; + return null; + } + return attributeWrapper(stream, state); + } + function attributeWrapperValue(stream, state) { + var ch = stream.peek(); + if (ch == '"' || ch == "\'") { + state.tokenize = readQuoted(ch, "string", true, false, attributeWrapper); + stream.next(); + return state.tokenize(stream, state); + } + if (ch == '[') { + return startRubySplat(attributeWrapper)(stream, state); + } + if (stream.match(/^(true|false|nil)\b/)) { + state.tokenize = attributeWrapper; + return "keyword"; + } + return startRubySplat(attributeWrapper)(stream, state); + } + + function startAttributeWrapperMode(state, endQuote, tokenize) { + state.stack = { + parent: state.stack, + style: "wrapper", + indented: state.indented + 1, + tokenize: tokenize, + line: state.line, + endQuote: endQuote + }; + state.line = state.tokenize = attributeWrapper; + return null; + } + + function sub(stream, state) { + if (stream.match(/^#\{/)) { + state.tokenize = rubyInQuote("}", state.tokenize); + return null; + } + var subStream = new CodeMirror.StringStream(stream.string.slice(state.stack.indented), stream.tabSize); + subStream.pos = stream.pos - state.stack.indented; + subStream.start = stream.start - state.stack.indented; + subStream.lastColumnPos = stream.lastColumnPos - state.stack.indented; + subStream.lastColumnValue = stream.lastColumnValue - state.stack.indented; + var style = state.subMode.token(subStream, state.subState); + stream.pos = subStream.pos + state.stack.indented; + return style; + } + function firstSub(stream, state) { + state.stack.indented = stream.column(); + state.line = state.tokenize = sub; + return state.tokenize(stream, state); + } + + function createMode(mode) { + var query = embedded[mode]; + var spec = CodeMirror.mimeModes[query]; + if (spec) { + return CodeMirror.getMode(config, spec); + } + var factory = CodeMirror.modes[query]; + if (factory) { + return factory(config, {name: query}); + } + return CodeMirror.getMode(config, "null"); + } + + function getMode(mode) { + if (!modes.hasOwnProperty(mode)) { + return modes[mode] = createMode(mode); + } + return modes[mode]; + } + + function startSubMode(mode, state) { + var subMode = getMode(mode); + var subState = subMode.startState && subMode.startState(); + + state.subMode = subMode; + state.subState = subState; + + state.stack = { + parent: state.stack, + style: "sub", + indented: state.indented + 1, + tokenize: state.line + }; + state.line = state.tokenize = firstSub; + return "slimSubmode"; + } + + function doctypeLine(stream, _state) { + stream.skipToEnd(); + return "slimDoctype"; + } + + function startLine(stream, state) { + var ch = stream.peek(); + if (ch == '<') { + return (state.tokenize = startHtmlLine(state.tokenize))(stream, state); + } + if (stream.match(/^[|']/)) { + return startHtmlMode(stream, state, 1); + } + if (stream.match(/^\/(!|\[\w+])?/)) { + return commentMode(stream, state); + } + if (stream.match(/^(-|==?[<>]?)/)) { + state.tokenize = lineContinuable(stream.column(), commaContinuable(stream.column(), ruby)); + return "slimSwitch"; + } + if (stream.match(/^doctype\b/)) { + state.tokenize = doctypeLine; + return "keyword"; + } + + var m = stream.match(embeddedRegexp); + if (m) { + return startSubMode(m[1], state); + } + + return slimTag(stream, state); + } + + function slim(stream, state) { + if (state.startOfLine) { + return startLine(stream, state); + } + return slimTag(stream, state); + } + + function slimTag(stream, state) { + if (stream.eat('*')) { + state.tokenize = startRubySplat(slimTagExtras); + return null; + } + if (stream.match(nameRegexp)) { + state.tokenize = slimTagExtras; + return "slimTag"; + } + return slimClass(stream, state); + } + function slimTagExtras(stream, state) { + if (stream.match(/^(<>?|> state.indented && state.last != "slimSubmode") { + state.line = state.tokenize = state.stack.tokenize; + state.stack = state.stack.parent; + state.subMode = null; + state.subState = null; + } + } + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + state.startOfLine = false; + if (style) state.last = style; + return styleMap.hasOwnProperty(style) ? styleMap[style] : style; + }, + + blankLine: function(state) { + if (state.subMode && state.subMode.blankLine) { + return state.subMode.blankLine(state.subState); + } + }, + + innerMode: function(state) { + if (state.subMode) return {state: state.subState, mode: state.subMode}; + return {state: state, mode: mode}; + } + + //indent: function(state) { + // return state.indented; + //} + }; + return mode; + }, "htmlmixed", "ruby"); + + CodeMirror.defineMIME("text/x-slim", "slim"); + CodeMirror.defineMIME("application/x-slim", "slim"); +}); diff --git a/mode/slim/test.js b/mode/slim/test.js new file mode 100644 index 0000000000..be4ddacb62 --- /dev/null +++ b/mode/slim/test.js @@ -0,0 +1,96 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// Slim Highlighting for CodeMirror copyright (c) HicknHack Software Gmbh + +(function() { + var mode = CodeMirror.getMode({tabSize: 4, indentUnit: 2}, "slim"); + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } + + // Requires at least one media query + MT("elementName", + "[tag h1] Hey There"); + + MT("oneElementPerLine", + "[tag h1] Hey There .h2"); + + MT("idShortcut", + "[attribute&def #test] Hey There"); + + MT("tagWithIdShortcuts", + "[tag h1][attribute&def #test] Hey There"); + + MT("classShortcut", + "[attribute&qualifier .hello] Hey There"); + + MT("tagWithIdAndClassShortcuts", + "[tag h1][attribute&def #test][attribute&qualifier .hello] Hey There"); + + MT("docType", + "[keyword doctype] xml"); + + MT("comment", + "[comment / Hello WORLD]"); + + MT("notComment", + "[tag h1] This is not a / comment "); + + MT("attributes", + "[tag a]([attribute title]=[string \"test\"]) [attribute href]=[string \"link\"]}"); + + MT("multiLineAttributes", + "[tag a]([attribute title]=[string \"test\"]", + " ) [attribute href]=[string \"link\"]}"); + + MT("htmlCode", + "[tag&bracket <][tag h1][tag&bracket >]Title[tag&bracket ]"); + + MT("rubyBlock", + "[operator&special =][variable-2 @item]"); + + MT("selectorRubyBlock", + "[tag a][attribute&qualifier .test][operator&special =] [variable-2 @item]"); + + MT("nestedRubyBlock", + "[tag a]", + " [operator&special =][variable puts] [string \"test\"]"); + + MT("multilinePlaintext", + "[tag p]", + " | Hello,", + " World"); + + MT("multilineRuby", + "[tag p]", + " [comment /# this is a comment]", + " [comment and this is a comment too]", + " | Date/Time", + " [operator&special -] [variable now] [operator =] [tag DateTime][operator .][property now]", + " [tag strong][operator&special =] [variable now]", + " [operator&special -] [keyword if] [variable now] [operator >] [tag DateTime][operator .][property parse]([string \"December 31, 2006\"])", + " [operator&special =][string \"Happy\"]", + " [operator&special =][string \"Belated\"]", + " [operator&special =][string \"Birthday\"]"); + + MT("multilineComment", + "[comment /]", + " [comment Multiline]", + " [comment Comment]"); + + MT("hamlAfterRubyTag", + "[attribute&qualifier .block]", + " [tag strong][operator&special =] [variable now]", + " [attribute&qualifier .test]", + " [operator&special =][variable now]", + " [attribute&qualifier .right]"); + + MT("stretchedRuby", + "[operator&special =] [variable puts] [string \"Hello\"],", + " [string \"World\"]"); + + MT("interpolationInHashAttribute", + "[tag div]{[attribute id] = [string \"]#{[variable test]}[string _]#{[variable ting]}[string \"]} test"); + + MT("interpolationInHTMLAttribute", + "[tag div]([attribute title]=[string \"]#{[variable test]}[string _]#{[variable ting]()}[string \"]) Test"); +})(); diff --git a/mode/sql/index.html b/mode/sql/index.html index 79a2e74e0f..7dd5f3075e 100644 --- a/mode/sql/index.html +++ b/mode/sql/index.html @@ -7,6 +7,9 @@ + + +