15 changes: 13 additions & 2 deletions addon/fold/foldcode.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@

function doFold(cm, pos, options, force) {
var finder = options && (options.call ? options : options.rangeFinder);
if (!finder) finder = cm.getHelper(pos, "fold");
if (!finder) return;
if (!finder) finder = CodeMirror.fold.auto;
if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0);
var minSize = options && options.minFoldSize || 0;

Expand Down Expand Up @@ -63,6 +62,10 @@
doFold(this, pos, options, force);
});

CodeMirror.commands.fold = function(cm) {
cm.foldCode(cm.getCursor());
};

CodeMirror.registerHelper("fold", "combine", function() {
var funcs = Array.prototype.slice.call(arguments, 0);
return function(cm, start) {
Expand All @@ -72,4 +75,12 @@
}
};
});

CodeMirror.registerHelper("fold", "auto", function(cm, start) {
var helpers = cm.getHelpers(start, "fold");
for (var i = 0; i < helpers.length; i++) {
var cur = helpers[i](cm, start);
if (cur) return cur;
}
});
})();
2 changes: 1 addition & 1 deletion addon/fold/foldgutter.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
if (isFolded(cm, cur)) {
mark = marker(opts.indicatorFolded);
} else {
var pos = Pos(cur, 0), func = opts.rangeFinder || cm.getHelper(pos, "fold");
var pos = Pos(cur, 0), func = opts.rangeFinder || CodeMirror.fold.auto;
var range = func && func(cm, pos);
if (range && range.from.line + 1 < range.to.line)
mark = marker(opts.indicatorOpen);
Expand Down
4 changes: 2 additions & 2 deletions addon/fold/indent-fold.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
CodeMirror.registerHelper("fold", "indent", function(cm, start) {
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 getIndent = function(line) {
return CodeMirror.countColumn(line, null, tabSize);
};
var myIndent = getIndent(firstLine);
var lastLineInFold = null;
Expand Down
6 changes: 6 additions & 0 deletions addon/fold/xml-fold.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,10 @@
if (close) return {open: open, close: close};
}
};

// Used by addon/edit/closetag.js
CodeMirror.scanForClosingTag = function(cm, pos, name, end) {
var iter = new Iter(cm, pos.line, pos.ch, end ? {from: 0, to: end} : null);
return !!findMatchingClose(iter, name);
};
})();
8 changes: 3 additions & 5 deletions addon/hint/anyword-hint.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,20 @@
var curWord = start != end && curLine.slice(start, end);

var list = [], seen = {};
function scan(dir) {
var re = new RegExp(word.source, "g");
for (var dir = -1; dir <= 1; dir += 2) {
var line = cur.line, end = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir;
for (; line != end; line += dir) {
var text = editor.getLine(line), m;
var re = new RegExp(word.source, "g");
while (m = re.exec(text)) {
if (line == cur.line && m[0] === curWord) continue;
if ((!curWord || m[0].indexOf(curWord) == 0) && !seen.hasOwnProperty(m[0])) {
if ((!curWord || m[0].lastIndexOf(curWord, 0) == 0) && !Object.prototype.hasOwnProperty.call(seen, m[0])) {
seen[m[0]] = true;
list.push(m[0]);
}
}
}
}
scan(-1);
scan(1);
return {list: list, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)};
});
})();
62 changes: 29 additions & 33 deletions addon/hint/css-hint.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,46 @@
(function () {
"use strict";

function getHints(cm) {
var pseudoClasses = {link: 1, visited: 1, active: 1, hover: 1, focus: 1,
"first-letter": 1, "first-line": 1, "first-child": 1,
before: 1, after: 1, lang: 1};

CodeMirror.registerHelper("hint", "css", function(cm) {
var cur = cm.getCursor(), token = cm.getTokenAt(cur);
var inner = CodeMirror.innerMode(cm.getMode(), token.state);
if (inner.mode.name != "css") return;

// If it's not a 'word-style' token, ignore the token.
if (!/^[\w$_-]*$/.test(token.string)) {
token = {
start: cur.ch, end: cur.ch, string: "", state: token.state,
type: null
};
var stack = token.state.stack;
var lastToken = stack && stack.length > 0 ? stack[stack.length - 1] : "";
if (token.string == ":" || lastToken.indexOf("property") == 0)
token.type = "variable";
else if (token.string == "{" || lastToken.indexOf("rule") == 0)
token.type = "property";
var word = token.string, start = token.start, end = token.end;
if (/[^\w$_-]/.test(word)) {
word = ""; start = end = cur.ch;
}

if (!token.type)
return;

var spec = CodeMirror.resolveMode("text/css");
var keywords = null;
if (token.type.indexOf("property") == 0)
keywords = spec.propertyKeywords;
else if (token.type.indexOf("variable") == 0)
keywords = spec.valueKeywords;

if (!keywords)
return;

var result = [];
for (var name in keywords) {
if (name.indexOf(token.string) == 0 /* > -1 */)
result.push(name);
function add(keywords) {
for (var name in keywords)
if (!word || name.lastIndexOf(word, 0) == 0)
result.push(name);
}

return {
var st = token.state.state;
if (st == "pseudo" || token.type == "variable-3") {
add(pseudoClasses);
} else if (st == "block" || st == "maybeprop") {
add(spec.propertyKeywords);
} else if (st == "prop" || st == "parens" || st == "at" || st == "params") {
add(spec.valueKeywords);
add(spec.colorKeywords);
} else if (st == "media" || st == "media_parens") {
add(spec.mediaTypes);
add(spec.mediaFeatures);
}

if (result.length) return {
list: result,
from: CodeMirror.Pos(cur.line, token.start),
to: CodeMirror.Pos(cur.line, token.end)
from: CodeMirror.Pos(cur.line, start),
to: CodeMirror.Pos(cur.line, end)
};
}

CodeMirror.registerHelper("hint", "css", getHints);
});
})();
2 changes: 1 addition & 1 deletion addon/hint/javascript-hint.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
function getCompletions(token, context, keywords, options) {
var found = [], start = token.string;
function maybeAdd(str) {
if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str);
if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
}
function gatherCompletions(obj) {
if (typeof obj == "string") forEach(stringProps, maybeAdd);
Expand Down
2 changes: 1 addition & 1 deletion addon/hint/pig-hint.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
function getCompletions(token, context) {
var found = [], start = token.string;
function maybeAdd(str) {
if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str);
if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
}

function gatherCompletions(obj) {
Expand Down
6 changes: 1 addition & 5 deletions addon/hint/python-hint.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@

var completionList = getCompletions(token, context);
completionList = completionList.sort();
//prevent autocomplete for last word, instead show dropdown with one word
if(completionList.length == 1) {
completionList.push(" ");
}

return {list: completionList,
from: CodeMirror.Pos(cur.line, token.start),
Expand Down Expand Up @@ -66,7 +62,7 @@
function getCompletions(token, context) {
var found = [], start = token.string;
function maybeAdd(str) {
if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str);
if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
}

function gatherCompletions(_obj) {
Expand Down
67 changes: 58 additions & 9 deletions addon/hint/show-hint.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
CodeMirror.showHint = function(cm, getHints, options) {
// We want a single cursor position.
if (cm.somethingSelected()) return;
if (getHints == null) getHints = cm.getHelper(cm.getCursor(), "hint");
if (getHints == null) return;
if (getHints == null) {
if (options && options.async) return;
else getHints = CodeMirror.hint.auto;
}

if (cm.state.completionActive) cm.state.completionActive.close();

Expand Down Expand Up @@ -45,6 +47,7 @@
var completion = data.list[i];
if (completion.hint) completion.hint(this.cm, data, completion);
else this.cm.replaceRange(getText(completion), data.from, data.to);
CodeMirror.signal(data, "pick", completion);
this.close();
},

Expand All @@ -61,10 +64,15 @@
this.widget = new Widget(this, data);
CodeMirror.signal(data, "shown");

var debounce = null, completion = this, finished;
var debounce = 0, completion = this, finished;
var closeOn = this.options.closeCharacters || /[\s()\[\]{};:>,]/;
var startPos = this.cm.getCursor(), startLen = this.cm.getLine(startPos.line).length;

var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
return setTimeout(fn, 1000/60);
};
var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;

function done() {
if (finished) return;
finished = true;
Expand All @@ -88,15 +96,22 @@
completion.widget = new Widget(completion, data);
}

function clearDebounce() {
if (debounce) {
cancelAnimationFrame(debounce);
debounce = 0;
}
}

function activity() {
clearTimeout(debounce);
clearDebounce();
var pos = completion.cm.getCursor(), line = completion.cm.getLine(pos.line);
if (pos.line != startPos.line || line.length - pos.ch != startLen - startPos.ch ||
pos.ch < startPos.ch || completion.cm.somethingSelected() ||
(pos.ch && closeOn.test(line.charAt(pos.ch - 1)))) {
completion.close();
} else {
debounce = setTimeout(update, 170);
debounce = requestAnimationFrame(update);
if (completion.widget) completion.widget.close();
}
}
Expand Down Expand Up @@ -143,9 +158,9 @@
return ourMap;
}

function getHintElement(stopAt, el) {
while (el && el != stopAt) {
if (el.nodeName.toUpperCase() === "LI") return el;
function getHintElement(hintsElement, el) {
while (el && el != hintsElement) {
if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el;
el = el.parentNode;
}
}
Expand Down Expand Up @@ -232,7 +247,10 @@

CodeMirror.on(hints, "click", function(e) {
var t = getHintElement(hints, e.target || e.srcElement);
if (t && t.hintId != null) widget.changeActive(t.hintId);
if (t && t.hintId != null) {
widget.changeActive(t.hintId);
if (options.completeOnSingleClick) widget.pick();
}
});

CodeMirror.on(hints, "mousedown", function() {
Expand Down Expand Up @@ -283,4 +301,35 @@
return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
}
};

CodeMirror.registerHelper("hint", "auto", function(cm, options) {
var helpers = cm.getHelpers(cm.getCursor(), "hint");
if (helpers.length) {
for (var i = 0; i < helpers.length; i++) {
var cur = helpers[i](cm, options);
if (cur && cur.list.length) return cur;
}
} else {
var words = cm.getHelper(cm.getCursor(), "hintWords");
if (words) return CodeMirror.hint.fromList(cm, {words: words});
}
});

CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
var cur = cm.getCursor(), token = cm.getTokenAt(cur);
var found = [];
for (var i = 0; i < options.words.length; i++) {
var word = options.words[i];
if (word.slice(0, token.string.length) == token.string)
found.push(word);
}

if (found.length) return {
list: found,
from: CodeMirror.Pos(cur.line, token.start),
to: CodeMirror.Pos(cur.line, token.end)
};
});

CodeMirror.commands.autocomplete = CodeMirror.showHint;
})();
75 changes: 58 additions & 17 deletions addon/hint/sql-hint.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

var tables;
var keywords;
var CONS = {
QUERY_DIV: ";",
ALIAS_KEYWORD: "AS"
};

function getKeywords(editor) {
var mode = editor.doc.modeOption;
Expand Down Expand Up @@ -34,44 +38,83 @@
var string = token.string.substr(1);
var prevCur = CodeMirror.Pos(cur.line, token.start);
var table = editor.getTokenAt(prevCur).string;
var columns = tables[table];
if(!columns) {
if( !tables.hasOwnProperty( table ) ){
table = findTableByAlias(table, editor);
}
columns = tables[table];
var columns = tables[table];
if(!columns) {
return;
}
addMatches(result, string, columns,
function(w) {return "." + w;});
}

function eachWord(line, f) {
var words = line.text.split(" ");
for(var i = 0; i < words.length; i++) {
f(words[i]);
function eachWord(lineText, f) {
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, '' ) : '' );
}
}

// Tries to find possible table name from alias.
function convertCurToNumber( cur ){
// max characters of a line is 999,999.
return cur.line + cur.ch / Math.pow( 10, 6 );
}

function convertNumberToCur( num ){
return CodeMirror.Pos(Math.floor( num ), +num.toString().split( '.' ).pop());
}

function findTableByAlias(alias, editor) {
var doc = editor.doc;
var fullQuery = doc.getValue();
var aliasUpperCase = alias.toUpperCase();
var previousWord = "";
var table = "";
var separator = [];
var validRange = {
start: CodeMirror.Pos( 0, 0 ),
end: CodeMirror.Pos( editor.lastLine(), editor.getLineHandle( editor.lastLine() ).length )
};

editor.eachLine(function(line) {
eachWord(line, function(word) {
//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);
}
separator.unshift( CodeMirror.Pos( 0, 0 ) );
separator.push( CodeMirror.Pos( editor.lastLine(), editor.getLineHandle( editor.lastLine() ).text.length ) );

//find valieRange
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 ) };
break;
}
prevItem = _v;
}

var query = doc.getRange(validRange.start, validRange.end, false);

for(var i=0; i < query.length; i++){
var lineText = query[i];
eachWord( lineText, function( word ){
var wordUpperCase = word.toUpperCase();
if(wordUpperCase === aliasUpperCase) {
if(tables.hasOwnProperty(previousWord)) {
if( wordUpperCase === aliasUpperCase && tables.hasOwnProperty( previousWord ) ){
table = previousWord;
}
}
if(wordUpperCase !== "AS") {
if( wordUpperCase !== CONS.ALIAS_KEYWORD ){
previousWord = word;
}
});
});
if( table ){ break; }
}
return table;
}

Expand All @@ -80,9 +123,7 @@
keywords = keywords || getKeywords(editor);
var cur = editor.getCursor();
var token = editor.getTokenAt(cur);

var result = [];

var search = token.string.trim();

addMatches(result, search, keywords,
Expand Down
10 changes: 5 additions & 5 deletions addon/hint/xml-hint.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
var cx = inner.state.context, curTag = cx && tags[cx.tagName];
var childList = cx ? curTag && curTag.children : tags["!top"];
if (childList) {
for (var i = 0; i < childList.length; ++i) if (!prefix || childList[i].indexOf(prefix) == 0)
for (var i = 0; i < childList.length; ++i) if (!prefix || childList[i].lastIndexOf(prefix, 0) == 0)
result.push("<" + childList[i]);
} else {
for (var name in tags) if (tags.hasOwnProperty(name) && name != "!top" && (!prefix || name.indexOf(prefix) == 0))
for (var name in tags) if (tags.hasOwnProperty(name) && name != "!top" && (!prefix || name.lastIndexOf(prefix, 0) == 0))
result.push("<" + name);
}
if (cx && (!prefix || ("/" + cx.tagName).indexOf(prefix) == 0))
if (cx && (!prefix || ("/" + cx.tagName).lastIndexOf(prefix, 0) == 0))
result.push("</" + cx.tagName + ">");
} else {
// Attribute completion
Expand All @@ -46,14 +46,14 @@
}
replaceToken = true;
}
for (var i = 0; i < atValues.length; ++i) if (!prefix || atValues[i].indexOf(prefix) == 0)
for (var i = 0; i < atValues.length; ++i) if (!prefix || atValues[i].lastIndexOf(prefix, 0) == 0)
result.push(quote + atValues[i] + quote);
} else { // An attribute name
if (token.type == "attribute") {
prefix = token.string;
replaceToken = true;
}
for (var attr in attrs) if (attrs.hasOwnProperty(attr) && (!prefix || attr.indexOf(prefix) == 0))
for (var attr in attrs) if (attrs.hasOwnProperty(attr) && (!prefix || attr.lastIndexOf(prefix, 0) == 0))
result.push(attr);
}
}
Expand Down
2 changes: 1 addition & 1 deletion addon/lint/lint.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
if (options.async)
options.getAnnotations(cm, updateLinting, options);
else
updateLinting(cm, options.getAnnotations(cm.getValue(), options));
updateLinting(cm, options.getAnnotations(cm.getValue(), options.options));
}

function updateLinting(cm, annotationsNotSorted) {
Expand Down
7 changes: 6 additions & 1 deletion addon/merge/merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
this.edit = this.mv.edit;
this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: true}, copyObj(options)));

this.diff = getDiff(orig, options.value);
this.diff = getDiff(asString(orig), asString(options.value));
this.diffOutOfDate = false;

this.showDifferences = options.showDifferences !== false;
Expand Down Expand Up @@ -352,6 +352,11 @@
}
};

function asString(obj) {
if (typeof obj == "string") return obj;
else return obj.getValue();
}

// Operations on diffs

var dmp = new diff_match_patch();
Expand Down
8 changes: 5 additions & 3 deletions addon/mode/multiplex.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
return outerToken;
} else {
var curInner = state.innerActive, oldContent = stream.string;
var found = indexOf(oldContent, curInner.close, stream.pos);
if (!curInner.close && stream.sol()) {
state.innerActive = state.inner = null;
return this.token(stream, state);
}
var found = curInner.close ? indexOf(oldContent, curInner.close, stream.pos) : -1;
if (found == stream.pos) {
stream.match(curInner.close);
state.innerActive = state.inner = null;
Expand All @@ -56,8 +60,6 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
if (found > -1) stream.string = oldContent.slice(0, found);
var innerToken = curInner.mode.token(stream, state.inner);
if (found > -1) stream.string = oldContent;
var cur = stream.current(), found = cur.indexOf(curInner.close);
if (found > -1) stream.backUp(cur.length - found);

if (curInner.innerStyle) {
if (innerToken) innerToken = innerToken + ' ' + curInner.innerStyle;
Expand Down
37 changes: 26 additions & 11 deletions addon/runmode/runmode-standalone.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ function splitLines(string){ return string.split(/\r?\n|\r/); };
function StringStream(string) {
this.pos = this.start = 0;
this.string = string;
this.lineStart = 0;
}
StringStream.prototype = {
eol: function() {return this.pos >= this.string.length;},
Expand Down Expand Up @@ -41,7 +42,7 @@ StringStream.prototype = {
if (found > -1) {this.pos = found; return true;}
},
backUp: function(n) {this.pos -= n;},
column: function() {return this.start;},
column: function() {return this.start - this.lineStart;},
indentation: function() {return 0;},
match: function(pattern, consume, caseInsensitive) {
if (typeof pattern == "string") {
Expand All @@ -58,7 +59,12 @@ StringStream.prototype = {
return match;
}
},
current: function(){return this.string.slice(this.start, this.pos);}
current: function(){return this.string.slice(this.start, this.pos);},
hideFirstChars: function(n, inner) {
this.lineStart += n;
try { return inner(); }
finally { this.lineStart -= n; }
}
};
CodeMirror.StringStream = StringStream;

Expand All @@ -69,17 +75,26 @@ CodeMirror.startState = function (mode, a1, a2) {
var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
CodeMirror.defineMode = function (name, mode) { modes[name] = mode; };
CodeMirror.defineMIME = function (mime, spec) { mimeModes[mime] = spec; };
CodeMirror.getMode = function (options, spec) {
if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
CodeMirror.resolveMode = function(spec) {
if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
spec = mimeModes[spec];
if (typeof spec == "string")
var mname = spec, config = {};
else if (spec != null)
var mname = spec.name, config = spec;
var mfactory = modes[mname];
} else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) {
spec = mimeModes[spec.name];
}
if (typeof spec == "string") return {name: spec};
else return spec || {name: "null"};
};
CodeMirror.getMode = function (options, spec) {
spec = CodeMirror.resolveMode(spec);
var mfactory = modes[spec.name];
if (!mfactory) throw new Error("Unknown mode: " + spec);
return mfactory(options, config || {});
return mfactory(options, spec);
};
CodeMirror.registerHelper = CodeMirror.registerGlobalHelper = Math.min;
CodeMirror.defineMode("null", function() {
return {token: function(stream) {stream.skipToEnd();}};
});
CodeMirror.defineMIME("text/plain", "null");

CodeMirror.runMode = function (string, modespec, callback, options) {
var mode = CodeMirror.getMode({ indentUnit: 2 }, modespec);
Expand Down Expand Up @@ -122,7 +137,7 @@ CodeMirror.runMode = function (string, modespec, callback, options) {
};
}

var lines = splitLines(string), state = CodeMirror.startState(mode);
var lines = splitLines(string), state = (options && options.state) || CodeMirror.startState(mode);
for (var i = 0, e = lines.length; i < e; ++i) {
if (i) callback("\n");
var stream = new CodeMirror.StringStream(lines[i]);
Expand Down
2 changes: 1 addition & 1 deletion addon/runmode/runmode.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ CodeMirror.runMode = function(string, modespec, callback, options) {
};
}

var lines = CodeMirror.splitLines(string), state = CodeMirror.startState(mode);
var lines = CodeMirror.splitLines(string), state = (options && options.state) || CodeMirror.startState(mode);
for (var i = 0, e = lines.length; i < e; ++i) {
if (i) callback("\n");
var stream = new CodeMirror.StringStream(lines[i]);
Expand Down
33 changes: 22 additions & 11 deletions addon/runmode/runmode.node.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ function splitLines(string){ return string.split(/\r?\n|\r/); };
function StringStream(string) {
this.pos = this.start = 0;
this.string = string;
this.lineStart = 0;
}
StringStream.prototype = {
eol: function() {return this.pos >= this.string.length;},
Expand Down Expand Up @@ -36,7 +37,7 @@ StringStream.prototype = {
if (found > -1) {this.pos = found; return true;}
},
backUp: function(n) {this.pos -= n;},
column: function() {return this.start;},
column: function() {return this.start - this.lineStart;},
indentation: function() {return 0;},
match: function(pattern, consume, caseInsensitive) {
if (typeof pattern == "string") {
Expand All @@ -53,7 +54,12 @@ StringStream.prototype = {
return match;
}
},
current: function(){return this.string.slice(this.start, this.pos);}
current: function(){return this.string.slice(this.start, this.pos);},
hideFirstChars: function(n, inner) {
this.lineStart += n;
try { return inner(); }
finally { this.lineStart -= n; }
}
};
exports.StringStream = StringStream;

Expand All @@ -76,21 +82,26 @@ exports.defineMode("null", function() {
});
exports.defineMIME("text/plain", "null");

exports.getMode = function(options, spec) {
if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
exports.resolveMode = function(spec) {
if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
spec = mimeModes[spec];
if (typeof spec == "string")
var mname = spec, config = {};
else if (spec != null)
var mname = spec.name, config = spec;
var mfactory = modes[mname];
} else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) {
spec = mimeModes[spec.name];
}
if (typeof spec == "string") return {name: spec};
else return spec || {name: "null"};
};
exports.getMode = function(options, spec) {
spec = exports.resolveMode(mimeModes[spec]);
var mfactory = modes[spec.name];
if (!mfactory) throw new Error("Unknown mode: " + spec);
return mfactory(options, config || {});
return mfactory(options, spec);
};
exports.registerHelper = exports.registerGlobalHelper = Math.min;

exports.runMode = function(string, modespec, callback) {
var mode = exports.getMode({indentUnit: 2}, modespec);
var lines = splitLines(string), state = exports.startState(mode);
var lines = splitLines(string), state = (options && options.state) || exports.startState(mode);
for (var i = 0, e = lines.length; i < e; ++i) {
if (i) callback("\n");
var stream = new exports.StringStream(lines[i]);
Expand Down
31 changes: 22 additions & 9 deletions addon/search/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@
// Ctrl-G.

(function() {
function searchOverlay(query) {
function searchOverlay(query, caseInsensitive) {
var startChar;
if (typeof query == "string") {
startChar = query.charAt(0);
query = new RegExp("^" + query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"),
caseInsensitive ? "i" : "");
} else {
query = new RegExp("^(?:" + query.source + ")", query.ignoreCase ? "i" : "");
}
if (typeof query == "string") return {token: function(stream) {
if (stream.match(query)) return "searching";
stream.next();
Expand All @@ -17,6 +25,8 @@
if (stream.match(query)) return "searching";
while (!stream.eol()) {
stream.next();
if (startChar)
stream.skipTo(startChar) || stream.skipToEnd();
if (stream.match(query, false)) break;
}
}};
Expand All @@ -29,13 +39,16 @@
function getSearchState(cm) {
return cm.state.search || (cm.state.search = new SearchState());
}
function queryCaseInsensitive(query) {
return typeof query == "string" && query == query.toLowerCase();
}
function getSearchCursor(cm, query, pos) {
// Heuristic: if the query string is all lowercase, do a case insensitive search.
return cm.getSearchCursor(query, pos, typeof query == "string" && query == query.toLowerCase());
return cm.getSearchCursor(query, pos, queryCaseInsensitive(query));
}
function dialog(cm, text, shortText, f) {
if (cm.openDialog) cm.openDialog(text, f);
else f(prompt(shortText, ""));
function dialog(cm, text, shortText, deflt, f) {
if (cm.openDialog) cm.openDialog(text, f, {value: deflt});
else f(prompt(shortText, deflt));
}
function confirmDialog(cm, text, shortText, fs) {
if (cm.openConfirm) cm.openConfirm(text, fs);
Expand All @@ -50,11 +63,11 @@
function doSearch(cm, rev) {
var state = getSearchState(cm);
if (state.query) return findNext(cm, rev);
dialog(cm, queryDialog, "Search for:", function(query) {
dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) {
cm.operation(function() {
if (!query || state.query) return;
state.query = parseQuery(query);
cm.removeOverlay(state.overlay);
cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
state.overlay = searchOverlay(state.query);
cm.addOverlay(state.overlay);
state.posFrom = state.posTo = cm.getCursor();
Expand Down Expand Up @@ -85,10 +98,10 @@
var replacementQueryDialog = 'With: <input type="text" style="width: 10em"/>';
var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
function replace(cm, all) {
dialog(cm, replaceQueryDialog, "Replace:", function(query) {
dialog(cm, replaceQueryDialog, "Replace:", cm.getSelection(), function(query) {
if (!query) return;
query = parseQuery(query);
dialog(cm, replacementQueryDialog, "Replace with:", function(text) {
dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) {
if (all) {
cm.operation(function() {
for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
Expand Down
72 changes: 48 additions & 24 deletions addon/search/searchcursor.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
match: match};
};
} else { // String query
var origQuery = query;
if (caseFold) query = query.toLowerCase();
var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
var target = query.split("\n");
Expand All @@ -58,33 +59,45 @@
this.matches = function() {};
} else {
this.matches = function(reverse, pos) {
var line = fold(doc.getLine(pos.line)), len = query.length, match;
if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
: (match = line.indexOf(query, pos.ch)) != -1)
return {from: Pos(pos.line, match),
to: Pos(pos.line, match + len)};
if (reverse) {
var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig);
var match = line.lastIndexOf(query);
if (match > -1) {
match = adjustPos(orig, line, match);
return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
}
} else {
var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig);
var match = line.indexOf(query);
if (match > -1) {
match = adjustPos(orig, line, match) + pos.ch;
return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
}
}
};
}
} else {
var origTarget = origQuery.split("\n");
this.matches = function(reverse, pos) {
var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(doc.getLine(ln));
var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
if (reverse ? offsetA > pos.ch || offsetA != match.length
: offsetA < pos.ch || offsetA != line.length - match.length)
return;
for (;;) {
if (reverse ? !ln : ln == doc.lineCount() - 1) return;
line = fold(doc.getLine(ln += reverse ? -1 : 1));
match = target[reverse ? --idx : ++idx];
if (idx > 0 && idx < target.length - 1) {
if (line != match) return;
else continue;
}
var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
return;
var start = Pos(pos.line, offsetA), end = Pos(ln, offsetB);
return {from: reverse ? end : start, to: reverse ? start : end};
var last = target.length - 1;
if (reverse) {
if (pos.line - (target.length - 1) < doc.firstLine()) return;
if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return;
var to = Pos(pos.line, origTarget[last].length);
for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln)
if (target[i] != fold(doc.getLine(ln))) return;
var line = doc.getLine(ln), cut = line.length - origTarget[0].length;
if (fold(line.slice(cut)) != target[0]) return;
return {from: Pos(ln, cut), to: to};
} else {
if (pos.line + (target.length - 1) > doc.lastLine()) return;
var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length;
if (fold(line.slice(cut)) != target[0]) return;
var from = Pos(pos.line, cut);
for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln)
if (target[i] != fold(doc.getLine(ln))) return;
if (doc.getLine(ln).slice(0, origTarget[last].length) != target[last]) return;
return {from: from, to: Pos(ln, origTarget[last].length)};
}
};
}
Expand All @@ -106,7 +119,6 @@

for (;;) {
if (this.pos = this.matches(reverse, pos)) {
if (!this.pos.from || !this.pos.to) { console.log(this.matches, this.pos); }
this.atOccurrence = true;
return this.pos.match || true;
}
Expand Down Expand Up @@ -134,6 +146,18 @@
}
};

// Maps a position in a case-folded line back to a position in the original line
// (compensating for codepoints increasing in number during folding)
function adjustPos(orig, folded, pos) {
if (orig.length == folded.length) return pos;
for (var pos1 = Math.min(pos, orig.length);;) {
var len1 = orig.slice(0, pos1).toLowerCase().length;
if (len1 < pos) ++pos1;
else if (len1 > pos) --pos1;
else return pos1;
}
}

CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
return new SearchCursor(this.doc, query, pos, caseFold);
});
Expand Down
24 changes: 15 additions & 9 deletions addon/selection/active-line.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) {
var prev = old && old != CodeMirror.Init;
if (val && !prev) {
updateActiveLine(cm);
cm.on("cursorActivity", updateActiveLine);
updateActiveLine(cm, cm.getCursor().line);
cm.on("beforeSelectionChange", selectionChange);
} else if (!val && prev) {
cm.off("cursorActivity", updateActiveLine);
cm.off("beforeSelectionChange", selectionChange);
clearActiveLine(cm);
delete cm.state.activeLine;
}
Expand All @@ -28,12 +28,18 @@
}
}

function updateActiveLine(cm) {
var line = cm.getLineHandleVisualStart(cm.getCursor().line);
function updateActiveLine(cm, selectedLine) {
var line = cm.getLineHandleVisualStart(selectedLine);
if (cm.state.activeLine == line) return;
clearActiveLine(cm);
cm.addLineClass(line, "wrap", WRAP_CLASS);
cm.addLineClass(line, "background", BACK_CLASS);
cm.state.activeLine = line;
cm.operation(function() {
clearActiveLine(cm);
cm.addLineClass(line, "wrap", WRAP_CLASS);
cm.addLineClass(line, "background", BACK_CLASS);
cm.state.activeLine = line;
});
}

function selectionChange(cm, sel) {
updateActiveLine(cm, sel.head.line);
}
})();
6 changes: 3 additions & 3 deletions addon/tern/tern.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@
var lex = inner.state.lexical;
if (lex.info != "call") return;

var ch, pos = lex.pos || 0, tabSize = cm.getOption("tabSize");
var ch, argPos = lex.pos || 0, tabSize = cm.getOption("tabSize");
for (var line = cm.getCursor().line, e = Math.max(0, line - 9), found = false; line >= e; --line) {
var str = cm.getLine(line), extra = 0;
for (var pos = 0;;) {
Expand All @@ -268,7 +268,7 @@
var start = Pos(line, ch);
var cache = ts.cachedArgHints;
if (cache && cache.doc == cm.getDoc() && cmpPos(start, cache.start) == 0)
return showArgHints(ts, cm, pos);
return showArgHints(ts, cm, argPos);

ts.request(cm, {type: "type", preferFunction: true, end: start}, function(error, data) {
if (error || !data.type || !(/^fn\(/).test(data.type)) return;
Expand All @@ -279,7 +279,7 @@
guess: data.guess,
doc: cm.getDoc()
};
showArgHints(ts, cm, pos);
showArgHints(ts, cm, argPos);
});
}

Expand Down
30 changes: 21 additions & 9 deletions addon/wrap/hardwrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,31 +36,40 @@
var killTrailing = options.killTrailingSpace !== false;
var changes = [], curLine = "", curNo = from.line;
var lines = cm.getRange(from, to, false);
if (!lines.length) return null;
var leadingSpace = lines[0].match(/^[ \t]*/)[0];

for (var i = 0; i < lines.length; ++i) {
var text = lines[i], oldLen = curLine.length, spaceInserted = 0;
if (curLine && text && !wrapOn.test(curLine.charAt(curLine.length - 1) + text.charAt(0))) {
curLine += " ";
spaceInserted = 1;
}
var spaceTrimmed = "";
if (i) {
spaceTrimmed = text.match(/^\s*/)[0];
text = text.slice(spaceTrimmed.length);
}
curLine += text;
if (i) {
var firstBreak = curLine.length > column && findBreakPoint(curLine, column, wrapOn, killTrailing);
var firstBreak = curLine.length > column && leadingSpace == spaceTrimmed &&
findBreakPoint(curLine, column, wrapOn, killTrailing);
// If this isn't broken, or is broken at a different point, remove old break
if (!firstBreak || firstBreak.from != oldLen || firstBreak.to != oldLen + spaceInserted) {
changes.push({text: spaceInserted ? " " : "",
changes.push({text: [spaceInserted ? " " : ""],
from: Pos(curNo, oldLen),
to: Pos(curNo + 1, 0)});
to: Pos(curNo + 1, spaceTrimmed.length)});
} else {
curLine = text;
curLine = leadingSpace + text;
++curNo;
}
}
while (curLine.length > column) {
var bp = findBreakPoint(curLine, column, wrapOn, killTrailing);
changes.push({text: "\n",
changes.push({text: ["", leadingSpace],
from: Pos(curNo, bp.from),
to: Pos(curNo, bp.to)});
curLine = curLine.slice(bp.to);
curLine = leadingSpace + curLine.slice(bp.to);
++curNo;
}
}
Expand All @@ -70,17 +79,18 @@
cm.replaceRange(change.text, change.from, change.to);
}
});
return changes.length ? {from: changes[0].from, to: CodeMirror.changeEnd(changes[changes.length - 1])} : null;
}

CodeMirror.defineExtension("wrapParagraph", function(pos, options) {
options = options || {};
if (!pos) pos = this.getCursor();
var para = findParagraph(this, pos, options);
wrapRange(this, Pos(para.from, 0), Pos(para.to - 1), options);
return wrapRange(this, Pos(para.from, 0), Pos(para.to - 1), options);
});

CodeMirror.defineExtension("wrapRange", function(from, to, options) {
wrapRange(this, from, to, options || {});
return wrapRange(this, from, to, options || {});
});

CodeMirror.defineExtension("wrapParagraphsInRange", function(from, to, options) {
Expand All @@ -91,9 +101,11 @@
paras.push(para);
line = para.to;
}
var madeChange = false;
if (paras.length) cm.operation(function() {
for (var i = paras.length - 1; i >= 0; --i)
wrapRange(cm, Pos(paras[i].from, 0), Pos(paras[i].to - 1), options);
madeChange = madeChange || wrapRange(cm, Pos(paras[i].from, 0), Pos(paras[i].to - 1), options);
});
return madeChange;
});
})();
41 changes: 41 additions & 0 deletions bin/release
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env node

var fs = require("fs"), child = require("child_process");

var number, bumpOnly;

for (var i = 2; i < process.argv.length; i++) {
if (process.argv[i] == "-bump") bumpOnly = true;
else if (/^\d+\.\d+\.\d+$/.test(process.argv[i])) number = process.argv[i];
else { console.log("Bogus command line arg: " + process.argv[i]); process.exit(1); }
}

if (!number) { console.log("Must give a version"); process.exit(1); }

function rewrite(file, f) {
fs.writeFileSync(file, f(fs.readFileSync(file, "utf8")), "utf8");
}

rewrite("lib/codemirror.js", function(lib) {
return lib.replace(/CodeMirror\.version = "\d+\.\d+\.\d+"/,
"CodeMirror.version = \"" + number + "\"");
});
rewrite("package.json", function(pack) {
return pack.replace(/"version":"\d+\.\d+\.\d+"/, "\"version\":\"" + number + "\"");
});

if (bumpOnly) process.exit(0);

child.exec("bash bin/authors.sh", function(){});

var simple = number.slice(0, number.lastIndexOf("."));

rewrite("doc/compress.html", function(cmp) {
return cmp.replace(/<option value="http:\/\/codemirror.net\/">HEAD<\/option>/,
"<option value=\"http://codemirror.net/\">HEAD</option>\n <option value=\"http://marijnhaverbeke.nl/git/codemirror?a=blob_plain;hb=" + number + ";f=\">" + simple + "</option>");
});

rewrite("index.html", function(index) {
return index.replace(/<strong>version 3.20<\/strong>/,
"<strong>version " + simple + "</strong>");
});
4 changes: 2 additions & 2 deletions demo/changemode.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ <h2>Mode-Changing Demo</h2>
lineNumbers: true,
tabMode: "indent"
});
var pending;
editor.on("change", function() {
clearTimeout(pending);
setTimeout(update, 400);
pending = setTimeout(update, 400);
});
var pending;
function looksLikeScheme(code) {
return !/^\s*\(\s*function\b/.test(code) && /^\s*[;\(]/.test(code);
}
Expand Down
1 change: 1 addition & 0 deletions demo/closetag.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<link rel="stylesheet" href="../lib/codemirror.css">
<script src="../lib/codemirror.js"></script>
<script src="../addon/edit/closetag.js"></script>
<script src="../addon/fold/xml-fold.js"></script>
<script src="../mode/xml/xml.js"></script>
<script src="../mode/javascript/javascript.js"></script>
<script src="../mode/css/css.js"></script>
Expand Down
4 changes: 1 addition & 3 deletions demo/complete.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<script src="../addon/hint/show-hint.js"></script>
<script src="../addon/hint/javascript-hint.js"></script>
<script src="../mode/javascript/javascript.js"></script>

<div id=nav>
<a href="http://codemirror.net"><img id=logo src="../doc/logo.png"></a>

Expand Down Expand Up @@ -69,9 +70,6 @@ <h2>Autocomplete Demo</h2>
addons.</p>

<script>
CodeMirror.commands.autocomplete = function(cm) {
CodeMirror.showHint(cm, CodeMirror.hint.javascript);
};
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
extraKeys: {"Ctrl-Space": "autocomplete"}
Expand Down
67 changes: 36 additions & 31 deletions demo/folding.html
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
<!doctype html>

<title>CodeMirror: Code Folding Demo</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../doc/docs.css">
<head>
<title>CodeMirror: Code Folding Demo</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../doc/docs.css">

<link rel="stylesheet" href="../lib/codemirror.css">
<link rel="stylesheet" href="../addon/fold/foldgutter.css" />
<script src="../lib/codemirror.js"></script>
<script src="../addon/fold/foldcode.js"></script>
<script src="../addon/fold/foldgutter.js"></script>
<script src="../addon/fold/brace-fold.js"></script>
<script src="../addon/fold/xml-fold.js"></script>
<script src="../addon/fold/comment-fold.js"></script>
<script src="../mode/javascript/javascript.js"></script>
<script src="../mode/xml/xml.js"></script>
<style type="text/css">
.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}
</style>
<link rel="stylesheet" href="../lib/codemirror.css">
<link rel="stylesheet" href="../addon/fold/foldgutter.css" />
<script src="../lib/codemirror.js"></script>
<script src="../addon/fold/foldcode.js"></script>
<script src="../addon/fold/foldgutter.js"></script>
<script src="../addon/fold/brace-fold.js"></script>
<script src="../addon/fold/xml-fold.js"></script>
<script src="../addon/fold/comment-fold.js"></script>
<script src="../mode/javascript/javascript.js"></script>
<script src="../mode/xml/xml.js"></script>
<style type="text/css">
.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}
</style>
</head>

<body>
<div id=nav>
<a href="http://codemirror.net"><img id=logo src="../doc/logo.png"></a>

Expand All @@ -31,12 +35,14 @@
</div>

<article>
<h2>Code Folding Demo</h2>
<form>
<div style="max-width: 50em; margin-bottom: 1em">JavaScript:<br><textarea id="code" name="code"></textarea></div>
<div style="max-width: 50em">HTML:<br><textarea id="code-html" name="code-html"></textarea></div>
</form>
<script id="script">
<h2>Code Folding Demo</h2>
<form>
<div style="max-width: 50em; margin-bottom: 1em">JavaScript:<br>
<textarea id="code" name="code"></textarea></div>
<div style="max-width: 50em">HTML:<br>
<textarea id="code-html" name="code-html"></textarea></div>
</form>
<script id="script">
/*
* Demonstration of code folding
*/
Expand All @@ -46,19 +52,17 @@ <h2>Code Folding Demo</h2>
te.value = (sc.textContent || sc.innerText || sc.innerHTML).replace(/^\s*/, "");
sc.innerHTML = "";
var te_html = document.getElementById("code-html");
te_html.value = "<html>\n " + document.documentElement.innerHTML + "\n</html>";
te_html.value = document.documentElement.innerHTML;

window.editor = CodeMirror.fromTextArea(te, {
mode: "javascript",
lineNumbers: true,
lineWrapping: true,
extraKeys: {"Ctrl-Q": function(cm){ cm.foldCode(cm.getCursor()); }},
foldGutter: {
rangeFinder: new CodeMirror.fold.combine(CodeMirror.fold.brace, CodeMirror.fold.comment)
},
foldGutter: true,
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
});
editor.foldCode(CodeMirror.Pos(8, 0));
editor.foldCode(CodeMirror.Pos(11, 0));

window.editor_html = CodeMirror.fromTextArea(te_html, {
mode: "text/html",
Expand All @@ -68,8 +72,9 @@ <h2>Code Folding Demo</h2>
foldGutter: true,
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
});
editor_html.foldCode(CodeMirror.Pos(13, 0));
editor_html.foldCode(CodeMirror.Pos(1, 0));
editor_html.foldCode(CodeMirror.Pos(0, 0));
editor_html.foldCode(CodeMirror.Pos(21, 0));
};
</script>
</article>
</script>
</article>
</body>
7 changes: 5 additions & 2 deletions demo/hardwrap.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,16 @@ <h2>Hard-wrapping Demo</h2>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
mode: "markdown",
lineNumbers: true
lineNumbers: true,
extraKeys: {
"Ctrl-Q": function(cm) { cm.wrapParagraph(cm.getCursor(), options); }
}
});
var wait, options = {column: 60};
editor.on("change", function(cm, change) {
clearTimeout(wait);
wait = setTimeout(function() {
cm.wrapParagraphsInRange(change.from, CodeMirror.changeEnd(change), options);
console.log(cm.wrapParagraphsInRange(change.from, CodeMirror.changeEnd(change), options));
}, 200);
});
</script>
Expand Down
74 changes: 38 additions & 36 deletions demo/html5complete.html
Original file line number Diff line number Diff line change
@@ -1,37 +1,41 @@
<!doctype html>

<title>CodeMirror: HTML completion demo</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../doc/docs.css">

<link rel="stylesheet" href="../lib/codemirror.css">
<link rel="stylesheet" href="../addon/hint/show-hint.css">
<script src="../lib/codemirror.js"></script>
<script src="../addon/hint/show-hint.js"></script>
<script src="../addon/hint/xml-hint.js"></script>
<script src="../addon/hint/html-hint.js"></script>
<script src="../mode/xml/xml.js"></script>
<script src="../mode/javascript/javascript.js"></script>
<script src="../mode/css/css.js"></script>
<script src="../mode/htmlmixed/htmlmixed.js"></script>
<style type="text/css">
.CodeMirror {border-top: 1px solid #888; border-bottom: 1px solid #888;}
</style>
<div id=nav>
<a href="http://codemirror.net"><img id=logo src="../doc/logo.png"></a>

<ul>
<li><a href="../index.html">Home</a>
<li><a href="../doc/manual.html">Manual</a>
<li><a href="https://github.com/marijnh/codemirror">Code</a>
</ul>
<ul>
<li><a class=active href="#">HTML completion</a>
</ul>
</div>

<article>
<h2>HTML completion demo</h2>
<head>
<title>CodeMirror: HTML completion demo</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../doc/docs.css">

<link rel="stylesheet" href="../lib/codemirror.css">
<link rel="stylesheet" href="../addon/hint/show-hint.css">
<script src="../lib/codemirror.js"></script>
<script src="../addon/hint/show-hint.js"></script>
<script src="../addon/hint/xml-hint.js"></script>
<script src="../addon/hint/html-hint.js"></script>
<script src="../mode/xml/xml.js"></script>
<script src="../mode/javascript/javascript.js"></script>
<script src="../mode/css/css.js"></script>
<script src="../mode/htmlmixed/htmlmixed.js"></script>
<style type="text/css">
.CodeMirror {border-top: 1px solid #888; border-bottom: 1px solid #888;}
</style>
</head>

<body>
<div id=nav>
<a href="http://codemirror.net"><img id=logo src="../doc/logo.png"></a>

<ul>
<li><a href="../index.html">Home</a>
<li><a href="../doc/manual.html">Manual</a>
<li><a href="https://github.com/marijnh/codemirror">Code</a>
</ul>
<ul>
<li><a class=active href="#">HTML completion</a>
</ul>
</div>

<article>
<h2>HTML completion demo</h2>

<p>Shows the <a href="xmlcomplete.html">XML completer</a>
parameterized with information about the tags in HTML.
Expand All @@ -40,15 +44,13 @@ <h2>HTML completion demo</h2>
<div id="code"></div>

<script type="text/javascript">
CodeMirror.commands.autocomplete = function(cm) {
CodeMirror.showHint(cm, CodeMirror.hint.html);
}
window.onload = function() {
editor = CodeMirror(document.getElementById("code"), {
mode: "text/html",
extraKeys: {"Ctrl-Space": "autocomplete"},
value: "<!doctype html>\n<html>\n " + document.documentElement.innerHTML + "\n</html>"
value: document.documentElement.innerHTML
});
};
</script>
</article>
</body>
1 change: 1 addition & 0 deletions demo/indentwrap.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<script src="../mode/xml/xml.js"></script>
<style type="text/css">
.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}
.CodeMirror pre > * { text-indent: 0px; }
</style>
<div id=nav>
<a href="http://codemirror.net"><img id=logo src="../doc/logo.png"></a>
Expand Down
1 change: 0 additions & 1 deletion demo/merge.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
<script src="../addon/merge/merge.js"></script>
<style>
.CodeMirror { line-height: 1.2; }
body { max-width: 80em; }
span.clicky {
cursor: pointer;
background: #d70;
Expand Down
2 changes: 2 additions & 0 deletions demo/theme.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<link rel="stylesheet" href="../theme/night.css">
<link rel="stylesheet" href="../theme/paraiso-dark.css">
<link rel="stylesheet" href="../theme/paraiso-light.css">
<link rel="stylesheet" href="../theme/pastel-on-dark.css">
<link rel="stylesheet" href="../theme/rubyblue.css">
<link rel="stylesheet" href="../theme/solarized.css">
<link rel="stylesheet" href="../theme/the-matrix.css">
Expand Down Expand Up @@ -88,6 +89,7 @@ <h2>Theme Demo</h2>
<option>night</option>
<option>paraiso-dark</option>
<option>paraiso-light</option>
<option>pastel-on-dark</option>
<option>rubyblue</option>
<option>solarized dark</option>
<option>solarized light</option>
Expand Down
10 changes: 8 additions & 2 deletions doc/activebookmark.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Kludge in HTML5 tag recognition in IE8
document.createElement("section");
document.createElement("article");

(function() {
var pending = false, prevVal = null;

Expand Down Expand Up @@ -37,6 +41,8 @@
}
}

window.addEventListener("scroll", updateSoon);
window.addEventListener("load", updateSoon);
if (window.addEventListener) {
window.addEventListener("scroll", updateSoon);
window.addEventListener("load", updateSoon);
}
})();
4 changes: 3 additions & 1 deletion doc/compress.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ <h2>Script compression helper</h2>
<input type="hidden" id="download" name="download" value="codemirror-compressed.js"/>
<p>Version: <select id="version" onchange="setVersion(this);" style="padding: 1px;">
<option value="http://codemirror.net/">HEAD</option>
<option value="http://marijnhaverbeke.nl/git/codemirror?a=blob_plain;hb=3.21.0;f=">3.21</option>
<option value="http://marijnhaverbeke.nl/git/codemirror?a=blob_plain;hb=3.20.0;f=">3.20</option>
<option value="http://marijnhaverbeke.nl/git/codemirror?a=blob_plain;hb=3.19.0;f=">3.19</option>
<option value="http://marijnhaverbeke.nl/git/codemirror?a=blob_plain;hb=3.18.0;f=">3.18</option>
Expand Down Expand Up @@ -114,9 +115,9 @@ <h2>Script compression helper</h2>
<option value="http://codemirror.net/mode/lua/lua.js">lua.js</option>
<option value="http://codemirror.net/mode/markdown/markdown.js">markdown.js</option>
<option value="http://codemirror.net/mode/mirc/mirc.js">mirc.js</option>
<option value="http://codemirror.net/mode/mllike/mllike.js">mllike.js</option>
<option value="http://codemirror.net/mode/nginx/nginx.js">nginx.js</option>
<option value="http://codemirror.net/mode/ntriples/ntriples.js">ntriples.js</option>
<option value="http://codemirror.net/mode/ocaml/ocaml.js">ocaml.js</option>
<option value="http://codemirror.net/mode/octave/octave.js">octave.js</option>
<option value="http://codemirror.net/mode/pascal/pascal.js">pascal.js</option>
<option value="http://codemirror.net/mode/pegjs/pegjs.js">pegjs.js</option>
Expand Down Expand Up @@ -159,6 +160,7 @@ <h2>Script compression helper</h2>
</optgroup>
<optgroup label="Add-ons">
<option value="http://codemirror.net/addon/selection/active-line.js">active-line.js</option>
<option value="http://codemirror.net/addon/hint/anyword-hint.js">anyword-hint.js</option>
<option value="http://codemirror.net/addon/fold/brace-fold.js">brace-fold.js</option>
<option value="http://codemirror.net/addon/edit/closebrackets.js">closebrackets.js</option>
<option value="http://codemirror.net/addon/edit/closetag.js">closetag.js</option>
Expand Down
13 changes: 7 additions & 6 deletions doc/docs.css
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ em {

article {
max-width: 700px;
margin: 0 auto;
margin: 0 0 0 160px;
border-left: 2px solid #E30808;
border-right: 1px solid #ddd;
padding: 30px 50px 100px 50px;
Expand All @@ -54,19 +54,20 @@ article {
#nav {
position: fixed;
top: 30px;
right: 50%;
left: 0; right: none;
width: 160px;
padding-right: 350px;
text-align: right;
z-index: 1;
}

@media screen and (max-width: 1000px) {
@media screen and (min-width: 1000px) {
article {
margin: 0 0 0 160px;
margin: 0 auto;
}
#nav {
left: 0; right: none;
width: 160px;
right: 50%;
width: auto;
}
}

Expand Down
112 changes: 85 additions & 27 deletions doc/manual.html
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,13 @@ <h2>Configuration</h2>
document looks. You can set this option to false to disable this
behavior.</dd>

<dt id="option_addModeClass"><code><strong>addModeClass</strong>: boolean</code></dt>
<dd>When enabled (off by default), an extra CSS class will be
added to each token, indicating the
(<a href="#innerMode">inner</a>) mode that produced it, prefixed
with <code>"cm-m-"</code>. For example, tokens from the XML mode
will get the <code>cm-m-xml</code> class.</dd>

<dt id="option_maxHighlightLength"><code><strong>maxHighlightLength</strong>: number</code></dt>
<dd>When highlighting long lines, in order to stay responsive,
the editor will give up and simply style the rest of the line as
Expand Down Expand Up @@ -935,10 +942,14 @@ <h3 id="api_content">Content manipulation methods</h3>
of <a href="#changeGeneration"><code>changeGeneration</code></a>,
which allows multiple subsystems to track different notions of
cleanness without interfering.</dd>
<dt id="changeGeneration"><code><strong>doc.changeGeneration</strong>() → integer</code></dt>
<dt id="changeGeneration"><code><strong>doc.changeGeneration</strong>(?closeEvent: boolean) → integer</code></dt>
<dd>Returns a number that can later be passed
to <a href="#isClean"><code>isClean</code></a> to test whether
any edits were made (and not undone) in the meantime.</dd>
any edits were made (and not undone) in the meantime.
If <code>closeEvent</code> is true, the current history event
will be ‘closed’, meaning it can't be combined with further
changes (rapid typing or deleting events are typically
combined).</dd>
<dt id="isClean"><code><strong>doc.isClean</strong>(?generation: integer) → boolean</code></dt>
<dd>Returns whether the document is currently clean — not
modified since initialization or the last call
Expand Down Expand Up @@ -1209,6 +1220,9 @@ <h3 id="api_marker">Text-marking methods</h3>
<a href="#event_clear"><code>"clear"</code></a> event
fired on the range handle can be used to be notified when this
happens.</dd>
<dt id="mark_clearWhenEmpty"><code><strong>clearWhenEmpty</strong>: boolean</code></dt>
<dd>Determines whether the mark is automatically cleared when
it becomes empty. Default is true.</dd>
<dt id="mark_replacedWith"><code><strong>replacedWith</strong>: Element</code></dt>
<dd>Use a given node to display this range. Implies both
collapsed and atomic. The given DOM node <em>must</em> be an
Expand Down Expand Up @@ -1273,7 +1287,7 @@ <h3 id="api_marker">Text-marking methods</h3>
<dt><code><strong>widget</strong>: Element</code></dt><dd>Can be used to display a DOM
node at the current location of the bookmark (analogous to
the <a href="#mark_replacedWith"><code>replacedWith</code></a>
option to <code>markText</code>).</dd>
option to <a href="#markText"><code>markText</code></a>).</dd>
<dt><code><strong>insertLeft</strong>: boolean</code></dt><dd>By default, text typed
when the cursor is on top of the bookmark will end up to the
right of the bookmark. Set this option to true to make it go
Expand Down Expand Up @@ -1530,18 +1544,35 @@ <h3 id="api_mode">Mode, state, and token-related methods</h3>
unstyled tokens, and a string, potentially containing multiple
space-separated style names, otherwise.</dd>

<dt id="getHelpers"><code><strong>cm.getHelpers</strong>(pos: {line, ch}, type: string) → array&lt;helper&gt;</code></dt>
<dd>Fetch the set of applicable helper values for the given
position. Helpers provide a way to look up functionality
appropriate for a mode. The <code>type</code> argument provides
the helper namespace (see
<a href="#registerHelper"><code>registerHelper</code></a>), in
which the values will be looked up. When the mode itself has a
property that corresponds to the <code>type</code>, that
directly determines the keys that are used to look up the helper
values (it may be either a single string, or an array of
strings). Failing that, the mode's <code>helperType</code>
property and finally the mode's name are used.</dd>
<dd>For example, the JavaScript mode has a
property <code>fold</code> containing <code>"brace"</code>. When
the <code>brace-fold</code> addon is loaded, that defines a
helper named <code>brace</code> in the <code>fold</code>
namespace. This is then used by
the <a href="#addon_foldcode"><code>foldcode</code></a> addon to
figure out that it can use that folding function to fold
JavaScript code.</dd>
<dd>When any <a href="#registerGlobalHelper">'global'</a>
helpers are defined for the given namespace, their predicates
are called on the current mode and editor, and all those that
declare they are applicable will also be added to the array that
is returned.</dd>

<dt id="getHelper"><code><strong>cm.getHelper</strong>(pos: {line, ch}, type: string) → helper</code></dt>
<dd>Fetch appropriate helper for the given position. Helpers
provide a way to look up functionality appropriate for a mode.
The <code>type</code> argument provides the helper namespace
(see
also <a href="#registerHelper"><code>registerHelper</code></a>),
in which the value will be looked up. The key that is used
depends on the <a href="#getMode">mode</a> active at the given
position. If the mode object contains a property with the same
name as the <code>type</code> argument, that is tried first.
Next, the mode's <code>helperType</code>, if any, is tried. And
finally, the mode's name.</dd>
<dd>Returns the first applicable helper value.
See <a href="#getHelpers"><code>getHelpers</code></a>.</dd>

<dt id="getStateAfter"><code><strong>cm.getStateAfter</strong>(?line: integer, ?precise: boolean) → object</code></dt>
<dd>Returns the mode's parser state, if any, at the end of the
Expand Down Expand Up @@ -1686,7 +1717,7 @@ <h3 id="api_static">Static properties</h3>
(with the instance as argument) whenever a new CodeMirror instance
is initialized.</dd>

<dt id="registerHelper"><code><strong>CodeMirror.registerHelper</strong>(type: string, name: string, value: helper)</code></dt>
<dt id="registerHelper"><code><strong>CodeMirror.registerHelper</strong>(type: string, name: string, value: helper)</code></dt>
<dd>Registers a helper value with the given <code>name</code> in
the given namespace (<code>type</code>). This is used to define
functionality that may be looked up by mode. Will create (if it
Expand All @@ -1697,6 +1728,14 @@ <h3 id="api_static">Static properties</h3>
myFoo)</code>, the value <code>CodeMirror.hint.foo</code> will
point to <code>myFoo</code>.</dd>

<dt id="registerGlobalHelper"><code><strong>CodeMirror.registerGlobalHelper</strong>(type: string, name: string, predicate: fn(mode, CodeMirror), value: helper)</code></dt>
<dd>Acts
like <a href="#registerHelper"><code>registerHelper</code></a>,
but also registers this helper as 'global', meaning that it will
be included by <a href="#getHelpers"><code>getHelpers</code></a>
whenever the given <code>predicate</code> returns true when
called with the local mode and editor.</dd>

<dt id="Pos"><code><strong>CodeMirror.Pos</strong>(line: integer, ?ch: integer)</code></dt>
<dd>A constructor for the <code>{line, ch}</code> objects that
are used to represent positions in editor documents.</dd>
Expand All @@ -1716,7 +1755,10 @@ <h2>Addons</h2>

<p>The <code>addon</code> directory in the distribution contains a
number of reusable components that implement extra editor
functionality. In brief, they are:</p>
functionality (on top of extension functions
like <a href="#defineOption"><code>defineOption</code></a>, <a href="#defineExtension"><code>defineExtension</code></a>,
and <a href="#registerHelper"><code>registerHelper</code></a>). In
brief, they are:</p>

<dl>
<dt id="addon_dialog"><a href="../addon/dialog/dialog.js"><code>dialog/dialog.js</code></a></dt>
Expand Down Expand Up @@ -1875,11 +1917,12 @@ <h2>Addons</h2>
supporting the following properties:
<dl>
<dt><code><strong>rangeFinder</strong>: fn(CodeMirror, Pos)</code></dt>
<dd>The function that is used to find foldable ranges. If this
is not directly passed, it will
call <a href="#getHelper"><code>getHelper</code></a> with
a <code>"fold"</code> type to find one that's appropriate for
the mode. There are files in
<dd id="helper_fold_auto">The function that is used to find
foldable ranges. If this is not directly passed, it will
default to <code>CodeMirror.fold.auto</code>, which
uses <a href="#getHelpers"><code>getHelpers</code></a> with
a <code>"fold"</code> type to find folding functions
appropriate for the local mode. There are files in
the <a href="../addon/fold/"><code>addon/fold/</code></a>
directory providing <code>CodeMirror.fold.brace</code>, which
finds blocks in brace languages (JavaScript, C, Java,
Expand Down Expand Up @@ -1938,8 +1981,8 @@ <h2>Addons</h2>
<dt><code><strong>rangeFinder</strong>: fn(CodeMirror, Pos)</code></dt>
<dd>The range-finder function to use when determining whether
something can be folded. When not
given, <a href="#getHelper"><code>getHelper</code></a> will be
used to determine a default.</dd>
given, <a href="#helper_fold_auto"><code>CodeMirror.fold.auto</code></a>
will be used as default.</dd>
</dl>
Demo <a href="../demo/folding.html">here</a>.</dd>

Expand Down Expand Up @@ -2011,9 +2054,17 @@ <h2>Addons</h2>
is an array of strings or objects (the completions),
and <code>from</code> and <code>to</code> give the start and end
of the token that is being completed as <code>{line, ch}</code>
objects. If no hinting function is given, the addon will try to
use <a href="#getHelper"><code>getHelper</code></a> with
the <code>"hint"</code> type to find one. When completions
objects.</dd>
<dd>If no hinting function is given, the addon will
use <code>CodeMirror.hint.auto</code>, with
calls <a href="#getHelpers"><code>getHelpers</code></a> with
the <code>"hint"</code> type to find applicable hinting
functions, and tries them one by one. If that fails, it looks
for a <code>"hintWords"</code> helper to fetch a list of
completable words for the mode, and
uses <code>CodeMirror.hint.fromList</code> to complete from
those.</dd>
<dd>When completions
aren't simple strings, they should be objects with the folowing
properties:
<dl>
Expand Down Expand Up @@ -2119,7 +2170,7 @@ <h2>Addons</h2>
the <a href="../demo/html5complete.html">demo</a>.</dd>

<dt id="addon_css-hint"><a href="../addon/hint/css-hint.js"><code>hint/css-hint.js</code></a></dt>
<dd>A minimal hinting function for CSS code.
<dd>A hinting function for CSS, SCSS, or LESS code.
Defines <code>CodeMirror.hint.css</code>.</dd>

<dt id="addon_python-hint"><a href="../addon/hint/python-hint.js"><code>hint/python-hint.js</code></a></dt>
Expand Down Expand Up @@ -2517,6 +2568,13 @@ <h2>Writing CodeMirror Modes</h2>
object specifying a mode, as in
the <a href="#option_mode"><code>mode</code></a> option.</p>

<p>If a mode specification wants to add some properties to the
resulting mode object, typically for use
with <a href="#getHelpers"><code>getHelpers</code></a>, it may
contain a <code>modeProps</code> property, which holds an object.
This object's properties will be copied to the actual mode
object.</p>

<p id="extendMode">Sometimes, it is useful to add or override mode
object properties from external code.
The <code><strong>CodeMirror.extendMode</strong></code> function
Expand Down
4 changes: 3 additions & 1 deletion doc/realworld.html
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ <h2>CodeMirror real-world uses</h2>
<li><a href="http://www.jshint.com/">JSHint</a> (JS linter)</li>
<li><a href="http://jumpseller.com/">Jumpseller</a> (online store builder)</li>
<li><a href="http://kl1p.com/cmtest/1">kl1p</a> (paste service)</li>
<li><a href="http://www.chris-granger.com/2012/04/12/light-table---a-new-ide-concept/">Light Table</a> (experimental IDE)</li>
<li><a href="http://kodtest.com/">Kodtest</a> (HTML/JS/CSS playground)</li>
<li><a href="http://lighttable.com/">Light Table</a> (experimental IDE)</li>
<li><a href="http://liveweave.com/">Liveweave</a> (HTML/CSS/JS scratchpad)</li>
<li><a href="http://marklighteditor.com/">Marklight editor</a> (lightweight markup editor)</li>
<li><a href="http://www.mergely.com/">Mergely</a> (interactive diffing)</li>
Expand Down Expand Up @@ -127,6 +128,7 @@ <h2>CodeMirror real-world uses</h2>
<li><a href="https://www.webkit.org/blog/2518/state-of-web-inspector/#source-code">WebKit Web inspector</a></li>
<li><a href="http://www.wescheme.org/">WeScheme</a> (learning tool)</li>
<li><a href="http://wordpress.org/extend/plugins/codemirror-for-codeeditor/">WordPress plugin</a></li>
<li><a href="https://www.writelatex.com">writeLaTeX</a> (Collaborative LaTeX Editor)</li>
<li><a href="http://www.xosystem.org/home/applications_websites/xosystem_website/xoside_EN.php">XOSide</a> (online editor)</li>
<li><a href="http://videlibri.sourceforge.net/cgi-bin/xidelcgi">XQuery tester</a></li>
<li><a href="http://q42jaap.github.io/xsd2codemirror/">xsd2codemirror</a> (convert XSD to CM XML completion info)</li>
Expand Down
13 changes: 13 additions & 0 deletions doc/releases.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ <h2>Release notes and version history</h2>

<h2>Version 3.x</h2>

<p class="rel">21-11-2013: <a href="http://codemirror.net/codemirror-3.21.zip">Version 3.21</a>:</p>

<ul class="rel-note">
<li>Auto-indenting a block will no longer add trailing whitespace to blank lines.</a>
<li>Marking text has a new option <a href="manual.html#markText"><code>clearWhenEmpty</code></a> to control auto-removal.</li>
<li>Several bugfixes in the handling of bidirectional text.</li>
<li>The <a href="../mode/xml/index.html">XML</a> and <a href="../mode/css/index.html">CSS</a> modes were largely rewritten. <a href="../mode/css/less.html">LESS</a> support was added to the CSS mode.</li>
<li>The OCaml mode was moved to an <a href="../mode/mllike/index.html">mllike</a> mode, F# support added.</li>
<li>Make it possible to fetch multiple applicable helper values with <a href="manual.html#getHelpers"><code>getHelpers</code></a>, and to register helpers matched on predicates with <a href="manual.html#registerGlobalHelper"><code>registerGlobalHelper</code></a>.</li>
<li>New theme <a href="../demo/theme.html?pastel-on-dark">pastel-on-dark</a>.</li>
<li>Better ECMAScript 6 support in <a href="../mode/javascript/index.html">JavaScript</a> mode.</li>
</ul>

<p class="rel">21-11-2013: <a href="http://codemirror.net/codemirror-3.20.zip">Version 3.20</a>:</p>

<ul class="rel-note">
Expand Down
4 changes: 2 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ <h2>This is CodeMirror</h2>
</script>
<div style="position: relative; margin: 1em 0;">
<a class="bigbutton left" href="http://codemirror.net/codemirror.zip">DOWNLOAD LATEST RELEASE</a>
<div><strong>version 3.20</strong> (<a href="doc/releases.html">Release notes</a>)</div>
<div><strong>version 3.21</strong> (<a href="doc/releases.html">Release notes</a>)</div>
<div>or use the <a href="doc/compress.html">minification helper</a></div>
<div style="position: absolute; top: 0; right: 0; text-align: right">
<span class="bigbutton right" onclick="document.getElementById('paypal').submit();">DONATE WITH PAYPAL</span>
Expand Down Expand Up @@ -133,7 +133,7 @@ <h2>Features</h2>
<li>Programmable <a href="demo/marker.html">gutters</a>
<li>Making ranges of text <a href="doc/manual.html#markText">styled, read-only, or atomic</a>
<li><a href="demo/bidi.html">Bi-directional text</a> support
<li>Many other <a href="doc/manual.html#api">methods</a> and <a href="doc/manual.html#addons">addons</a>...</a>
<li>Many other <a href="doc/manual.html#api">methods</a> and <a href="doc/manual.html#addons">addons</a>...
</ul>
</section>

Expand Down
13 changes: 12 additions & 1 deletion keymap/emacs.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@
cm.on("change", function() { cm.setExtending(false); });
}

function clearMark(cm) {
cm.setExtending(false);
cm.setCursor(cm.getCursor());
}

function getInput(cm, msg, f) {
if (cm.openDialog)
cm.openDialog(msg + ": <input type=\"text\" style=\"width: 10em\"/>", f, {bottom: true});
Expand Down Expand Up @@ -234,6 +239,11 @@
}
}

function quit(cm) {
cm.execCommand("clearSearch");
clearMark(cm);
}

// Actual keymap

var keyMap = CodeMirror.keyMap.emacs = {
Expand All @@ -249,6 +259,7 @@
}),
"Alt-W": function(cm) {
addToRing(cm.getSelection());
clearMark(cm);
},
"Ctrl-Y": function(cm) {
var start = cm.getCursor();
Expand Down Expand Up @@ -334,7 +345,7 @@
"Ctrl-/": repeated("undo"), "Shift-Ctrl--": repeated("undo"),
"Ctrl-Z": repeated("undo"), "Cmd-Z": repeated("undo"),
"Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd",
"Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": "clearSearch", "Shift-Alt-5": "replace",
"Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": quit, "Shift-Alt-5": "replace",
"Alt-/": "autocomplete",
"Ctrl-J": "newlineAndIndent", "Enter": false, "Tab": "indentAuto",

Expand Down
138 changes: 110 additions & 28 deletions keymap/vim.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
{ keys: ['<End>'], type: 'keyToKey', toKeys: ['$'] },
{ keys: ['<PageUp>'], type: 'keyToKey', toKeys: ['<C-b>'] },
{ keys: ['<PageDown>'], type: 'keyToKey', toKeys: ['<C-f>'] },
{ keys: ['<CR>'], type: 'keyToKey', toKeys: ['j', '^'], context: 'normal' },
// Motions
{ keys: ['H'], type: 'motion',
motion: 'moveToTopLine',
Expand Down Expand Up @@ -247,6 +248,12 @@
actionArgs: { forward: true }},
{ keys: ['<C-o>'], type: 'action', action: 'jumpListWalk',
actionArgs: { forward: false }},
{ keys: ['<C-e>'], type: 'action',
action: 'scroll',
actionArgs: { forward: true, linewise: true }},
{ keys: ['<C-y>'], type: 'action',
action: 'scroll',
actionArgs: { forward: false, linewise: true }},
{ keys: ['a'], type: 'action', action: 'enterInsertMode', isEdit: true,
actionArgs: { insertAt: 'charAfter' }},
{ keys: ['A'], type: 'action', action: 'enterInsertMode', isEdit: true,
Expand Down Expand Up @@ -324,12 +331,16 @@
CodeMirror.defineOption('vimMode', false, function(cm, val) {
if (val) {
cm.setOption('keyMap', 'vim');
cm.setOption('disableInput', true);
CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
cm.on('beforeSelectionChange', beforeSelectionChange);
maybeInitVimState(cm);
CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm));
} else if (cm.state.vim) {
cm.setOption('keyMap', 'default');
cm.setOption('disableInput', false);
cm.off('beforeSelectionChange', beforeSelectionChange);
CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm));
cm.state.vim = null;
}
});
Expand All @@ -342,6 +353,18 @@
head.ch--;
}
}
function getOnPasteFn(cm) {
var vim = cm.state.vim;
if (!vim.onPasteFn) {
vim.onPasteFn = function() {
if (!vim.insertMode) {
cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));
actions.enterInsertMode(cm, {}, vim);
}
};
}
return vim.onPasteFn;
}

var numberRegex = /[\d]/;
var wordRegexp = [(/\w/), (/[^\w\s]/)], bigWordRegexp = [(/\S/)];
Expand Down Expand Up @@ -549,9 +572,9 @@
maybeInitVimState_: maybeInitVimState,

InsertModeKey: InsertModeKey,
map: function(lhs, rhs) {
map: function(lhs, rhs, ctx) {
// Add user defined key bindings.
exCommandDispatcher.map(lhs, rhs);
exCommandDispatcher.map(lhs, rhs, ctx);
},
defineEx: function(name, prefix, func){
if (name.indexOf(prefix) !== 0) {
Expand Down Expand Up @@ -833,11 +856,17 @@
} else {
// Find the best match in the list of matchedCommands.
var context = vim.visualMode ? 'visual' : 'normal';
var bestMatch = matchedCommands[0]; // Default to first in the list.
var bestMatch; // Default to first in the list.
for (var i = 0; i < matchedCommands.length; i++) {
if (matchedCommands[i].context == context) {
bestMatch = matchedCommands[i];
var current = matchedCommands[i];
if (current.context == context) {
bestMatch = current;
break;
} else if (!bestMatch && !current.context) {
// Only set an imperfect match to best match if no best match is
// set and the imperfect match is not restricted to another
// context.
bestMatch = current;
}
}
return getFullyMatchedCommandOrNull(bestMatch);
Expand Down Expand Up @@ -1636,6 +1665,43 @@
markPos = markPos ? markPos : cm.getCursor();
cm.setCursor(markPos);
},
scroll: function(cm, actionArgs, vim) {
if (vim.visualMode) {
return;
}
var repeat = actionArgs.repeat || 1;
var lineHeight = cm.defaultTextHeight();
var top = cm.getScrollInfo().top;
var delta = lineHeight * repeat;
var newPos = actionArgs.forward ? top + delta : top - delta;
var cursor = cm.getCursor();
var cursorCoords = cm.charCoords(cursor, 'local');
if (actionArgs.forward) {
if (newPos > cursorCoords.top) {
cursor.line += (newPos - cursorCoords.top) / lineHeight;
cursor.line = Math.ceil(cursor.line);
cm.setCursor(cursor);
cursorCoords = cm.charCoords(cursor, 'local');
cm.scrollTo(null, cursorCoords.top);
} else {
// Cursor stays within bounds. Just reposition the scroll window.
cm.scrollTo(null, newPos);
}
} else {
var newBottom = newPos + cm.getScrollInfo().clientHeight;
if (newBottom < cursorCoords.bottom) {
cursor.line -= (cursorCoords.bottom - newBottom) / lineHeight;
cursor.line = Math.floor(cursor.line);
cm.setCursor(cursor);
cursorCoords = cm.charCoords(cursor, 'local');
cm.scrollTo(
null, cursorCoords.bottom - cm.getScrollInfo().clientHeight);
} else {
// Cursor stays within bounds. Just reposition the scroll window.
cm.scrollTo(null, newPos);
}
}
},
scrollToCursor: function(cm, actionArgs) {
var lineNum = cm.getCursor().line;
var charCoords = cm.charCoords({line: lineNum, ch: 0}, 'local');
Expand Down Expand Up @@ -1691,6 +1757,7 @@
cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
}
cm.setOption('keyMap', 'vim-insert');
cm.setOption('disableInput', false);
if (actionArgs && actionArgs.replace) {
// Handle Replace-mode as a special case of insert mode.
cm.toggleOverwrite(true);
Expand Down Expand Up @@ -2896,14 +2963,16 @@
// pair of commands that have a shared prefix, at least one of their
// shortNames must not match the prefix of the other command.
var defaultExCommandMap = [
{ name: 'map', type: 'builtIn' },
{ name: 'write', shortName: 'w', type: 'builtIn' },
{ name: 'undo', shortName: 'u', type: 'builtIn' },
{ name: 'redo', shortName: 'red', type: 'builtIn' },
{ name: 'sort', shortName: 'sor', type: 'builtIn'},
{ name: 'substitute', shortName: 's', type: 'builtIn'},
{ name: 'nohlsearch', shortName: 'noh', type: 'builtIn'},
{ name: 'delmarks', shortName: 'delm', type: 'builtin'}
{ name: 'map' },
{ name: 'nmap', shortName: 'nm' },
{ name: 'vmap', shortName: 'vm' },
{ name: 'write', shortName: 'w' },
{ name: 'undo', shortName: 'u' },
{ name: 'redo', shortName: 'red' },
{ name: 'sort', shortName: 'sor' },
{ name: 'substitute', shortName: 's' },
{ name: 'nohlsearch', shortName: 'noh' },
{ name: 'delmarks', shortName: 'delm' }
];
Vim.ExCommandDispatcher = function() {
this.buildCommandMap_();
Expand Down Expand Up @@ -2955,6 +3024,7 @@
exCommands[commandName](cm, params);
} catch(e) {
showConfirm(cm, e);
throw e;
}
},
parseInput_: function(cm, inputStream, result) {
Expand Down Expand Up @@ -3037,8 +3107,9 @@
this.commandMap_[key] = command;
}
},
map: function(lhs, rhs) {
map: function(lhs, rhs, ctx) {
if (lhs != ':' && lhs.charAt(0) == ':') {
if (ctx) { throw Error('Mode not supported for ex mappings'); }
var commandName = lhs.substring(1);
if (rhs != ':' && rhs.charAt(0) == ':') {
// Ex to Ex mapping
Expand All @@ -3058,17 +3129,21 @@
} else {
if (rhs != ':' && rhs.charAt(0) == ':') {
// Key to Ex mapping.
defaultKeymap.unshift({
var mapping = {
keys: parseKeyString(lhs),
type: 'keyToEx',
exArgs: { input: rhs.substring(1) }});
exArgs: { input: rhs.substring(1) }};
if (ctx) { mapping.context = ctx; }
defaultKeymap.unshift(mapping);
} else {
// Key to key mapping
defaultKeymap.unshift({
var mapping = {
keys: parseKeyString(lhs),
type: 'keyToKey',
toKeys: parseKeyString(rhs)
});
};
if (ctx) { mapping.context = ctx; }
defaultKeymap.unshift(mapping);
}
}
}
Expand All @@ -3090,16 +3165,18 @@
}

var exCommands = {
map: function(cm, params) {
map: function(cm, params, ctx) {
var mapArgs = params.args;
if (!mapArgs || mapArgs.length < 2) {
if (cm) {
showConfirm(cm, 'Invalid mapping: ' + params.input);
}
return;
}
exCommandDispatcher.map(mapArgs[0], mapArgs[1], cm);
exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx);
},
nmap: function(cm, params) { this.map(cm, params, 'normal'); },
vmap: function(cm, params) { this.map(cm, params, 'visual'); },
move: function(cm, params) {
commandDispatcher.processCommand(cm, cm.state.vim, {
type: 'motion',
Expand All @@ -3115,7 +3192,7 @@
var args = new CodeMirror.StringStream(params.argString);
if (args.eat('!')) { reverse = true; }
if (args.eol()) { return; }
if (!args.eatSpace()) { throw new Error('invalid arguments ' + args.match(/.*/)[0]); }
if (!args.eatSpace()) { return 'Invalid arguments'; }
var opts = args.match(/[a-z]+/);
if (opts) {
opts = opts[0];
Expand All @@ -3124,13 +3201,17 @@
var decimal = opts.indexOf('d') != -1 && 1;
var hex = opts.indexOf('x') != -1 && 1;
var octal = opts.indexOf('o') != -1 && 1;
if (decimal + hex + octal > 1) { throw new Error('invalid arguments'); }
if (decimal + hex + octal > 1) { return 'Invalid arguments'; }
number = decimal && 'decimal' || hex && 'hex' || octal && 'octal';
}
if (args.eatSpace() && args.match(/\/.*\//)) { throw new Error('patterns not supported'); }
if (args.eatSpace() && args.match(/\/.*\//)) { 'patterns not supported'; }
}
}
parseArgs();
var err = parseArgs();
if (err) {
showConfirm(cm, err + ': ' + params.argString);
return;
}
var lineStart = params.line || cm.firstLine();
var lineEnd = params.lineEnd || params.line || cm.lastLine();
if (lineStart == lineEnd) { return; }
Expand Down Expand Up @@ -3251,13 +3332,13 @@
clearSearchHighlight(cm);
},
delmarks: function(cm, params) {
if (!params.argString || !params.argString.trim()) {
if (!params.argString || !trim(params.argString)) {
showConfirm(cm, 'Argument required');
return;
}

var state = cm.state.vim;
var stream = new CodeMirror.StringStream(params.argString.trim());
var stream = new CodeMirror.StringStream(trim(params.argString));
while (!stream.eol()) {
stream.eatSpace();

Expand Down Expand Up @@ -3394,7 +3475,8 @@
// Actually do replace.
next();
if (done) {
throw new Error('No matches for ' + query.source);
showConfirm(cm, 'No matches for ' + query.source);
return;
}
if (!confirm) {
replaceAll();
Expand Down Expand Up @@ -3445,7 +3527,6 @@

var cmToVimKeymap = {
'nofallthrough': true,
'disableInput': true,
'style': 'fat-cursor'
};
function bindKeys(keys, modifier) {
Expand Down Expand Up @@ -3492,6 +3573,7 @@
cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1, true);
vim.insertMode = false;
cm.setOption('keyMap', 'vim');
cm.setOption('disableInput', true);
cm.toggleOverwrite(false); // exit replace mode if we were in it.
CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
}
Expand Down
375 changes: 240 additions & 135 deletions lib/codemirror.js

Large diffs are not rendered by default.

37 changes: 28 additions & 9 deletions mode/clike/clike.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,26 +203,43 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
return "string";
}

function mimes(ms, mode) {
for (var i = 0; i < ms.length; ++i) CodeMirror.defineMIME(ms[i], mode);
function def(mimes, mode) {
var words = [];
function add(obj) {
if (obj) for (var prop in obj) if (obj.hasOwnProperty(prop))
words.push(prop);
}
add(mode.keywords);
add(mode.builtin);
add(mode.atoms);
if (words.length) {
mode.helperType = mimes[0];
CodeMirror.registerHelper("hintWords", mimes[0], words);
}

for (var i = 0; i < mimes.length; ++i)
CodeMirror.defineMIME(mimes[i], mode);
}

mimes(["text/x-csrc", "text/x-c", "text/x-chdr"], {
def(["text/x-csrc", "text/x-c", "text/x-chdr"], {
name: "clike",
keywords: words(cKeywords),
blockKeywords: words("case do else for if switch while struct"),
atoms: words("null"),
hooks: {"#": cppHook}
hooks: {"#": cppHook},
modeProps: {fold: ["brace", "include"]}
});
mimes(["text/x-c++src", "text/x-c++hdr"], {

def(["text/x-c++src", "text/x-c++hdr"], {
name: "clike",
keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " +
"static_cast typeid catch operator template typename class friend private " +
"this using const_cast inline public throw virtual delete mutable protected " +
"wchar_t"),
blockKeywords: words("catch class do else finally for if struct switch try while"),
atoms: words("true false null"),
hooks: {"#": cppHook}
hooks: {"#": cppHook},
modeProps: {fold: ["brace", "include"]}
});
CodeMirror.defineMIME("text/x-java", {
name: "clike",
Expand All @@ -238,7 +255,8 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
stream.eatWhile(/[\w\$_]/);
return "meta";
}
}
},
modeProps: {fold: ["brace", "import"]}
});
CodeMirror.defineMIME("text/x-csharp", {
name: "clike",
Expand Down Expand Up @@ -303,7 +321,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
}
}
});
mimes(["x-shader/x-vertex", "x-shader/x-fragment"], {
def(["x-shader/x-vertex", "x-shader/x-fragment"], {
name: "clike",
keywords: words("float int bool void " +
"vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 " +
Expand Down Expand Up @@ -357,6 +375,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
"gl_MaxVertexTextureImageUnits gl_MaxTextureImageUnits " +
"gl_MaxFragmentUniformComponents gl_MaxCombineTextureImageUnits " +
"gl_MaxDrawBuffers"),
hooks: {"#": cppHook}
hooks: {"#": cppHook},
modeProps: {fold: ["brace", "include"]}
});
}());
7 changes: 3 additions & 4 deletions mode/coffeescript/coffeescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,13 @@ CodeMirror.defineMode("coffeescript", function(conf) {

// Handle strings
if (stream.match(stringPrefixes)) {
state.tokenize = tokenFactory(stream.current(), "string");
state.tokenize = tokenFactory(stream.current(), false, "string");
return state.tokenize(stream, state);
}
// Handle regex literals
if (stream.match(regexPrefixes)) {
if (stream.current() != "/" || stream.match(/^.*\//, false)) { // prevent highlight of division
state.tokenize = tokenFactory(stream.current(), "string-2");
state.tokenize = tokenFactory(stream.current(), true, "string-2");
return state.tokenize(stream, state);
} else {
stream.backUp(1);
Expand Down Expand Up @@ -161,8 +161,7 @@ CodeMirror.defineMode("coffeescript", function(conf) {
return ERRORCLASS;
}

function tokenFactory(delimiter, outclass) {
var singleline = delimiter.length == 1;
function tokenFactory(delimiter, singleline, outclass) {
return function(stream, state) {
while (!stream.eol()) {
stream.eatWhile(/[^'"\/\\]/);
Expand Down
638 changes: 341 additions & 297 deletions mode/css/css.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion mode/css/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ <h2>CSS mode</h2>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {});
</script>

<p><strong>MIME types defined:</strong> <code>text/css</code>.</p>
<p><strong>MIME types defined:</strong> <code>text/css</code>, <code>text/x-scss</code> (<a href="scss.html">demo</a>), <code>text/x-less</code> (<a href="less.html">demo</a>).</p>

<p><strong>Parsing/Highlighting Tests:</strong> <a href="../../test/index.html#css_*">normal</a>, <a href="../../test/index.html#verbose,css_*">verbose</a>.</p>

Expand Down
152 changes: 152 additions & 0 deletions mode/css/less.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<!doctype html>

<title>CodeMirror: LESS mode</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../../doc/docs.css">

<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="../../addon/edit/matchbrackets.js"></script>
<script src="css.js"></script>
<style>.CodeMirror {border: 1px solid #ddd; line-height: 1.2;}</style>
<div id=nav>
<a href="http://codemirror.net"><img id=logo src="../../doc/logo.png"></a>

<ul>
<li><a href="../../index.html">Home</a>
<li><a href="../../doc/manual.html">Manual</a>
<li><a href="https://github.com/marijnh/codemirror">Code</a>
</ul>
<ul>
<li><a href="../index.html">Language modes</a>
<li><a class=active href="#">LESS</a>
</ul>
</div>

<article>
<h2>LESS mode</h2>
<form><textarea id="code" name="code">@media screen and (device-aspect-ratio: 16/9) { … }
@media screen and (device-aspect-ratio: 1280/720) { … }
@media screen and (device-aspect-ratio: 2560/1440) { … }

html:lang(fr-be)

tr:nth-child(2n+1) /* represents every odd row of an HTML table */

img:nth-of-type(2n+1) { float: right; }
img:nth-of-type(2n) { float: left; }

body > h2:not(:first-of-type):not(:last-of-type)

html|*:not(:link):not(:visited)
*|*:not(:hover)
p::first-line { text-transform: uppercase }

@namespace foo url(http://www.example.com);
foo|h1 { color: blue } /* first rule */

span[hello="Ocean"][goodbye="Land"]

E[foo]{
padding:65px;
}

input[type="search"]::-webkit-search-decoration,
input[type="search"]::-webkit-search-cancel-button {
-webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5
}
button::-moz-focus-inner,
input::-moz-focus-inner { // Inner padding and border oddities in FF3/4
padding: 0;
border: 0;
}
.btn {
// reset here as of 2.0.3 due to Recess property order
border-color: #ccc;
border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);
}
fieldset span button, fieldset span input[type="file"] {
font-size:12px;
font-family:Arial, Helvetica, sans-serif;
}

.rounded-corners (@radius: 5px) {
border-radius: @radius;
-webkit-border-radius: @radius;
-moz-border-radius: @radius;
}

@import url("something.css");

@light-blue: hsl(190, 50%, 65%);

#menu {
position: absolute;
width: 100%;
z-index: 3;
clear: both;
display: block;
background-color: @blue;
height: 42px;
border-top: 2px solid lighten(@alpha-blue, 20%);
border-bottom: 2px solid darken(@alpha-blue, 25%);
.box-shadow(0, 1px, 8px, 0.6);
-moz-box-shadow: 0 0 0 #000; // Because firefox sucks.

&.docked {
background-color: hsla(210, 60%, 40%, 0.4);
}
&:hover {
background-color: @blue;
}

#dropdown {
margin: 0 0 0 117px;
padding: 0;
padding-top: 5px;
display: none;
width: 190px;
border-top: 2px solid @medium;
color: @highlight;
border: 2px solid darken(@medium, 25%);
border-left-color: darken(@medium, 15%);
border-right-color: darken(@medium, 15%);
border-top-width: 0;
background-color: darken(@medium, 10%);
ul {
padding: 0px;
}
li {
font-size: 14px;
display: block;
text-align: left;
padding: 0;
border: 0;
a {
display: block;
padding: 0px 15px;
text-decoration: none;
color: white;
&:hover {
background-color: darken(@medium, 15%);
text-decoration: none;
}
}
}
.border-radius(5px, bottom);
.box-shadow(0, 6px, 8px, 0.5);
}
}
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers : true,
matchBrackets : true,
mode: "text/x-less"
});
</script>

<p>The LESS mode is a sub-mode of the <a href="index.html">CSS mode</a> (defined in <code>css.js</code>.</p>

<p><strong>Parsing/Highlighting Tests:</strong> <a href="../../test/index.html#less_*">normal</a>, <a href="../../test/index.html#verbose,less_*">verbose</a>.</p>
</article>
48 changes: 48 additions & 0 deletions mode/css/less_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
(function() {
"use strict";

var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-less");
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "less"); }

MT("variable",
"[variable-2 @base]: [atom #f04615];",
"[qualifier .class] {",
" [property width]: [variable percentage]([number 0.5]); [comment // returns `50%`]",
" [property color]: [variable saturate]([variable-2 @base], [number 5%]);",
"}");

MT("amp",
"[qualifier .child], [qualifier .sibling] {",
" [qualifier .parent] [atom &] {",
" [property color]: [keyword black];",
" }",
" [atom &] + [atom &] {",
" [property color]: [keyword red];",
" }",
"}");

MT("mixin",
"[qualifier .mixin] ([variable dark]; [variable-2 @color]) {",
" [property color]: [variable darken]([variable-2 @color], [number 10%]);",
"}",
"[qualifier .mixin] ([variable light]; [variable-2 @color]) {",
" [property color]: [variable lighten]([variable-2 @color], [number 10%]);",
"}",
"[qualifier .mixin] ([variable-2 @_]; [variable-2 @color]) {",
" [property display]: [atom block];",
"}",
"[variable-2 @switch]: [variable light];",
"[qualifier .class] {",
" [qualifier .mixin]([variable-2 @switch]; [atom #888]);",
"}");

MT("nest",
"[qualifier .one] {",
" [def @media] ([property width]: [number 400px]) {",
" [property font-size]: [number 1.2em];",
" [def @media] [attribute print] [keyword and] [property color] {",
" [property color]: [keyword blue];",
" }",
" }",
"}");
})();
2 changes: 1 addition & 1 deletion mode/css/scss.html
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ <h2>SCSS mode</h2>
});
</script>

<p><strong>MIME types defined:</strong> <code>text/scss</code>.</p>
<p>The SCSS mode is a sub-mode of the <a href="index.html">CSS mode</a> (defined in <code>css.js</code>.</p>

<p><strong>Parsing/Highlighting Tests:</strong> <a href="../../test/index.html#scss_*">normal</a>, <a href="../../test/index.html#verbose,scss_*">verbose</a>.</p>

Expand Down
82 changes: 48 additions & 34 deletions mode/css/scss_test.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,33 @@
(function() {
var mode = CodeMirror.getMode({tabSize: 1}, "text/x-scss");
var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-scss");
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "scss"); }
function IT(name) { test.indentation(name, mode, Array.prototype.slice.call(arguments, 1), "scss"); }

MT('url_with_quotation',
"[tag foo] { [property background][operator :][string-2 url]([string test.jpg]) }");
"[tag foo] { [property background]:[atom url]([string test.jpg]) }");

MT('url_with_double_quotes',
"[tag foo] { [property background][operator :][string-2 url]([string \"test.jpg\"]) }");
"[tag foo] { [property background]:[atom url]([string \"test.jpg\"]) }");

MT('url_with_single_quotes',
"[tag foo] { [property background][operator :][string-2 url]([string \'test.jpg\']) }");
"[tag foo] { [property background]:[atom url]([string \'test.jpg\']) }");

MT('string',
"[def @import] [string \"compass/css3\"]");

MT('important_keyword',
"[tag foo] { [property background][operator :][string-2 url]([string \'test.jpg\']) [keyword !important] }");
"[tag foo] { [property background]:[atom url]([string \'test.jpg\']) [keyword !important] }");

MT('variable',
"[variable-2 $blue][operator :][atom #333]");
"[variable-2 $blue]:[atom #333]");

MT('variable_as_attribute',
"[tag foo] { [property color][operator :][variable-2 $blue] }");
"[tag foo] { [property color]:[variable-2 $blue] }");

MT('numbers',
"[tag foo] { [property padding][operator :][number 10px] [number 10] [number 10em] [number 8in] }");
"[tag foo] { [property padding]:[number 10px] [number 10] [number 10em] [number 8in] }");

MT('number_percentage',
"[tag foo] { [property width][operator :][number 80%] }");
"[tag foo] { [property width]:[number 80%] }");

MT('selector',
"[builtin #hello][qualifier .world]{}");
Expand All @@ -40,54 +39,69 @@
"[comment /*foobar*/]");

MT('attribute_with_hyphen',
"[tag foo] { [property font-size][operator :][number 10px] }");
"[tag foo] { [property font-size]:[number 10px] }");

MT('string_after_attribute',
"[tag foo] { [property content][operator :][string \"::\"] }");
"[tag foo] { [property content]:[string \"::\"] }");

MT('directives',
"[def @include] [qualifier .mixin]");

MT('basic_structure',
"[tag p] { [property background][operator :][keyword red]; }");
"[tag p] { [property background]:[keyword red]; }");

MT('nested_structure',
"[tag p] { [tag a] { [property color][operator :][keyword red]; } }");
"[tag p] { [tag a] { [property color]:[keyword red]; } }");

MT('mixin',
"[def @mixin] [tag table-base] {}");

MT('number_without_semicolon',
"[tag p] {[property width][operator :][number 12]}",
"[tag a] {[property color][operator :][keyword red];}");
"[tag p] {[property width]:[number 12]}",
"[tag a] {[property color]:[keyword red];}");

MT('atom_in_nested_block',
"[tag p] { [tag a] { [property color][operator :][atom #000]; } }");
"[tag p] { [tag a] { [property color]:[atom #000]; } }");

MT('interpolation_in_property',
"[tag foo] { [operator #{][variable-2 $hello][operator }:][number 2]; }");
"[tag foo] { #{[variable-2 $hello]}:[number 2]; }");

MT('interpolation_in_selector',
"[tag foo][operator #{][variable-2 $hello][operator }] { [property color][operator :][atom #000]; }");
"[tag foo]#{[variable-2 $hello]} { [property color]:[atom #000]; }");

MT('interpolation_error',
"[tag foo][operator #{][error foo][operator }] { [property color][operator :][atom #000]; }");
"[tag foo]#{[error foo]} { [property color]:[atom #000]; }");

MT("divide_operator",
"[tag foo] { [property width][operator :][number 4] [operator /] [number 2] }");
"[tag foo] { [property width]:[number 4] [operator /] [number 2] }");

MT('nested_structure_with_id_selector',
"[tag p] { [builtin #hello] { [property color][operator :][keyword red]; } }");

IT('mixin',
"@mixin container[1 (][2 $a: 10][1 , ][2 $b: 10][1 , ][2 $c: 10]) [1 {]}");

IT('nested',
"foo [1 { bar ][2 { ][1 } ]}");

IT('comma',
"foo [1 { font-family][2 : verdana, sans-serif][1 ; ]}");

IT('parentheses',
"foo [1 { color][2 : darken][3 ($blue, 9%][2 )][1 ; ]}");
"[tag p] { [builtin #hello] { [property color]:[keyword red]; } }");

MT('indent_mixin',
"[def @mixin] [tag container] (",
" [variable-2 $a]: [number 10],",
" [variable-2 $b]: [number 10])",
"{}");

MT('indent_nested',
"[tag foo] {",
" [tag bar] {",
" }",
"}");

MT('indent_parentheses',
"[tag foo] {",
" [property color]: [variable darken]([variable-2 $blue],",
" [number 9%]);",
"}");

MT('indent_vardef',
"[variable-2 $name]:",
" [string 'val'];",
"[tag tag] {",
" [tag inner] {",
" [property margin]: [number 3px];",
" }",
"}");
})();
Loading