23 changes: 14 additions & 9 deletions addon/mode/multiplex.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@
CodeMirror.multiplexingMode = function(outer /*, others */) {
// Others should be {open, close, mode [, delimStyle] [, innerStyle]} objects
var others = Array.prototype.slice.call(arguments, 1);
var n_others = others.length;

function indexOf(string, pattern, from) {
if (typeof pattern == "string") return string.indexOf(pattern, from);
function indexOf(string, pattern, from, returnEnd) {
if (typeof pattern == "string") {
var found = string.indexOf(pattern, from);
return returnEnd && found > -1 ? found + pattern.length : found;
}
var m = pattern.exec(from ? string.slice(from) : string);
return m ? m.index + from : -1;
return m ? m.index + from + (returnEnd ? m[0].length : 0) : -1;
}

return {
Expand All @@ -42,11 +44,11 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
token: function(stream, state) {
if (!state.innerActive) {
var cutOff = Infinity, oldContent = stream.string;
for (var i = 0; i < n_others; ++i) {
for (var i = 0; i < others.length; ++i) {
var other = others[i];
var found = indexOf(oldContent, other.open, stream.pos);
if (found == stream.pos) {
stream.match(other.open);
if (!other.parseDelimiters) stream.match(other.open);
state.innerActive = other;
state.inner = CodeMirror.startState(other.mode, outer.indent ? outer.indent(state.outer, "") : 0);
return other.delimStyle;
Expand All @@ -64,8 +66,8 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
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) {
var found = curInner.close ? indexOf(oldContent, curInner.close, stream.pos, curInner.parseDelimiters) : -1;
if (found == stream.pos && !curInner.parseDelimiters) {
stream.match(curInner.close);
state.innerActive = state.inner = null;
return curInner.delimStyle;
Expand All @@ -74,6 +76,9 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
var innerToken = curInner.mode.token(stream, state.inner);
if (found > -1) stream.string = oldContent;

if (found == stream.pos && curInner.parseDelimiters)
state.innerActive = state.inner = null;

if (curInner.innerStyle) {
if (innerToken) innerToken = innerToken + ' ' + curInner.innerStyle;
else innerToken = curInner.innerStyle;
Expand All @@ -95,7 +100,7 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
mode.blankLine(state.innerActive ? state.inner : state.outer);
}
if (!state.innerActive) {
for (var i = 0; i < n_others; ++i) {
for (var i = 0; i < others.length; ++i) {
var other = others[i];
if (other.open === "\n") {
state.innerActive = other;
Expand Down
23 changes: 19 additions & 4 deletions addon/scroll/annotatescrollbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,30 @@
var cm = this.cm, hScale = this.hScale;

var frag = document.createDocumentFragment(), anns = this.annotations;

var wrapping = cm.getOption("lineWrapping");
var singleLineH = wrapping && cm.defaultTextHeight() * 1.5;
var curLine = null, curLineObj = null;
function getY(pos, top) {
if (curLine != pos.line) {
curLine = pos.line;
curLineObj = cm.getLineHandle(curLine);
}
if (wrapping && curLineObj.height > singleLineH)
return cm.charCoords(pos, "local")[top ? "top" : "bottom"];
var topY = cm.heightAtLine(curLineObj, "local");
return topY + (top ? 0 : curLineObj.height);
}

if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) {
var ann = anns[i];
var top = nextTop || cm.charCoords(ann.from, "local").top * hScale;
var bottom = cm.charCoords(ann.to, "local").bottom * hScale;
var top = nextTop || getY(ann.from, true) * hScale;
var bottom = getY(ann.to, false) * hScale;
while (i < anns.length - 1) {
nextTop = cm.charCoords(anns[i + 1].from, "local").top * hScale;
nextTop = getY(anns[i + 1].from, true) * hScale;
if (nextTop > bottom + .9) break;
ann = anns[++i];
bottom = cm.charCoords(ann.to, "local").bottom * hScale;
bottom = getY(ann.to, false) * hScale;
}
if (bottom == top) continue;
var height = Math.max(bottom - top, 3);
Expand Down
4 changes: 3 additions & 1 deletion addon/search/matchesonscrollbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

function SearchAnnotation(cm, query, caseFold, options) {
this.cm = cm;
this.options = options;
var annotateOptions = {listenForChanges: false};
for (var prop in options) annotateOptions[prop] = options[prop];
if (!annotateOptions.className) annotateOptions.className = "CodeMirror-search-match";
Expand Down Expand Up @@ -46,11 +47,12 @@
if (match.to.line >= this.gap.from) this.matches.splice(i--, 1);
}
var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), this.caseFold);
var maxMatches = this.options && this.options.maxMatches || MAX_MATCHES;
while (cursor.findNext()) {
var match = {from: cursor.from(), to: cursor.to()};
if (match.from.line >= this.gap.to) break;
this.matches.splice(i++, 0, match);
if (this.matches.length > MAX_MATCHES) break;
if (this.matches.length > maxMatches) break;
}
this.gap = null;
};
Expand Down
11 changes: 7 additions & 4 deletions addon/search/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
}

function SearchState() {
this.posFrom = this.posTo = this.query = null;
this.posFrom = this.posTo = this.lastQuery = this.query = null;
this.overlay = null;
}
function getSearchState(cm) {
Expand All @@ -53,7 +53,7 @@
return cm.getSearchCursor(query, pos, queryCaseInsensitive(query));
}
function dialog(cm, text, shortText, deflt, f) {
if (cm.openDialog) cm.openDialog(text, f, {value: deflt});
if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true});
else f(prompt(shortText, deflt));
}
function confirmDialog(cm, text, shortText, fs) {
Expand All @@ -75,7 +75,8 @@
function doSearch(cm, rev) {
var state = getSearchState(cm);
if (state.query) return findNext(cm, rev);
dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) {
var q = cm.getSelection() || state.lastQuery;
dialog(cm, queryDialog, "Search for:", q, function(query) {
cm.operation(function() {
if (!query || state.query) return;
state.query = parseQuery(query);
Expand Down Expand Up @@ -104,6 +105,7 @@
});}
function clearSearch(cm) {cm.operation(function() {
var state = getSearchState(cm);
state.lastQuery = state.query;
if (!state.query) return;
state.query = null;
cm.removeOverlay(state.overlay);
Expand All @@ -116,7 +118,8 @@
var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
function replace(cm, all) {
if (cm.getOption("readOnly")) return;
dialog(cm, replaceQueryDialog, "Replace:", cm.getSelection(), function(query) {
var query = cm.getSelection() || getSearchState().lastQuery;
dialog(cm, replaceQueryDialog, "Replace:", query, function(query) {
if (!query) return;
query = parseQuery(query);
dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) {
Expand Down
4 changes: 2 additions & 2 deletions addon/search/searchcursor.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,10 @@
from: function() {if (this.atOccurrence) return this.pos.from;},
to: function() {if (this.atOccurrence) return this.pos.to;},

replace: function(newText) {
replace: function(newText, origin) {
if (!this.atOccurrence) return;
var lines = CodeMirror.splitLines(newText);
this.doc.replaceRange(lines, this.pos.from, this.pos.to);
this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin);
this.pos.to = Pos(this.pos.from.line + lines.length - 1,
lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
}
Expand Down
6 changes: 4 additions & 2 deletions bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codemirror",
"version":"5.1.0",
"version":"5.2.0",
"main": ["lib/codemirror.js", "lib/codemirror.css"],
"ignore": [
"**/.*",
Expand All @@ -11,6 +11,8 @@
"doc",
"test",
"index.html",
"package.json"
"package.json",
"mode/*/*test.js",
"mode/*/*.html"
]
}
122 changes: 97 additions & 25 deletions demo/panel.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,36 @@
<link rel="stylesheet" href="../lib/codemirror.css">
<script src="../lib/codemirror.js"></script>
<script src="../mode/javascript/javascript.js"></script>
<script src="../mode/xml/xml.js"></script>
<script src="../mode/htmlmixed/htmlmixed.js"></script>
<script src="../addon/display/panel.js"></script>
<style type="text/css">
.border {border: 1px solid black; border-bottom: 1px solid black;}
.add { background: orange; padding: 1px 3px; color: white !important; border-radius: 4px; }
.border {
border: 1px solid #f7f7f7;
}
.add-panel {
background: orange;
padding: 3px 6px;
color: white !important;
border-radius: 3px;
}
.add-panel, .remove-panel {
cursor: pointer;
}
.remove-panel {
float: right;
}
.panel {
background-image: linear-gradient(to bottom, #ffffaa, #ffffdd);
background: #f7f7f7;
padding: 3px 7px;
font-size: 0.85em;
}
.panel.top, .panel.after-top {
border-bottom: 1px solid #ddd;
}
.panel.bottom, .panel.before-bottom {
border-top: 1px solid #ddd;
}
.panel.top { border-bottom: 1px solid #dd6; }
.panel.bottom { border-top: 1px solid #dd6; }
.panel span { cursor: pointer; }
</style>

<div id=nav>
Expand All @@ -34,31 +53,84 @@
</div>

<article>

<h2>Panel Demo</h2>
<form><div class="border"><textarea id="code" name="code"></textarea></div></form>

<script id="localscript">var textarea = document.getElementById("code");
var script = document.getElementById("localscript");
textarea.value = (script.textContent ||
script.innerText ||
script.innerHTML);
<div class="border">
<textarea id="code" name="code"></textarea>
</div>

<p>
The <a href="../doc/manual.html#addon_panel"><code>panel</code></a>
addon allows you to display panels above or below an editor.
<br>
Click the links below to add panels at the given position:
</p>

<div id="demo">
<p>
<a class="add-panel" onclick="addPanel('top')">top</a>
<a class="add-panel" onclick="addPanel('after-top')">after-top</a>
<a class="add-panel" onclick="addPanel('before-bottom')">before-bottom</a>
<a class="add-panel" onclick="addPanel('bottom')">bottom</a>
</p>
<p>
You can also replace an existing panel:
</p>
<form onsubmit="return replacePanel(this);" name="replace_panel">
<input type="submit" value="Replace panel n°" />
<input type="number" name="panel_id" min="1" value="1" />
</form>

<script>
var textarea = document.getElementById("code");
var demo = document.getElementById("demo");
var numPanels = 0;
var panels = {};
var editor;

textarea.value = demo.innerHTML.trim();
editor = CodeMirror.fromTextArea(textarea, {
lineNumbers: true
lineNumbers: true,
mode: "htmlmixed"
});

function addPanel(where) {
function makePanel(where) {
var node = document.createElement("div");
var id = ++numPanels;
var widget, close, label;

node.id = "panel-" + id;
node.className = "panel " + where;
var close = node.appendChild(document.createElement("span"));
close.textContent = "✖ Remove this panel";
var widget = editor.addPanel(node, {position: where});
CodeMirror.on(close, "click", function() { widget.clear(); });
}</script>

<p>The <a href="../doc/manual.html#addon_panel"><code>panel</code></a>
addon allows you to display panels <a class=add
href="javascript:addPanel('top')">above</a> or <a class=add
href="javascript:addPanel('bottom')">below</a> an editor. Click the
links in the previous paragraph to add panels to the editor.</p>
close = node.appendChild(document.createElement("a"));
close.setAttribute("title", "Remove me!");
close.setAttribute("class", "remove-panel");
close.textContent = "✖";
CodeMirror.on(close, "click", function() {
panels[node.id].clear();
});
label = node.appendChild(document.createElement("span"));
label.textContent = "I'm panel n°" + id;
return node;
}
function addPanel(where) {
var node = makePanel(where);
panels[node.id] = editor.addPanel(node, {position: where});
}

addPanel("top");
addPanel("bottom");

function replacePanel(form) {
var id = form.elements.panel_id.value;
var panel = panels["panel-" + id];
var node = makePanel("");

panels[node.id] = editor.addPanel(node, {replace: panel, position: "after-top"});
return false;
}
</script>

</div>

</article>
6 changes: 3 additions & 3 deletions demo/tern.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
<script src="../addon/dialog/dialog.js"></script>
<script src="../addon/hint/show-hint.js"></script>
<script src="../addon/tern/tern.js"></script>
<script src="http://marijnhaverbeke.nl/acorn/acorn.js"></script>
<script src="http://marijnhaverbeke.nl/acorn/acorn_loose.js"></script>
<script src="http://marijnhaverbeke.nl/acorn/util/walk.js"></script>
<script src="http://ternjs.net/node_modules/acorn/dist/acorn.js"></script>
<script src="http://ternjs.net/node_modules/acorn/dist/acorn_loose.js"></script>
<script src="http://ternjs.net/node_modules/acorn/dist/walk.js"></script>
<script src="http://ternjs.net/doc/demo/polyfill.js"></script>
<script src="http://ternjs.net/lib/signal.js"></script>
<script src="http://ternjs.net/lib/tern.js"></script>
Expand Down
2 changes: 2 additions & 0 deletions demo/theme.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<link rel="stylesheet" href="../theme/elegant.css">
<link rel="stylesheet" href="../theme/erlang-dark.css">
<link rel="stylesheet" href="../theme/lesser-dark.css">
<link rel="stylesheet" href="../theme/liquibyte.css">
<link rel="stylesheet" href="../theme/mbo.css">
<link rel="stylesheet" href="../theme/mdn-like.css">
<link rel="stylesheet" href="../theme/midnight.css">
Expand Down Expand Up @@ -87,6 +88,7 @@ <h2>Theme Demo</h2>
<option>elegant</option>
<option>erlang-dark</option>
<option>lesser-dark</option>
<option>liquibyte</option>
<option>mbo</option>
<option>mdn-like</option>
<option>midnight</option>
Expand Down
3 changes: 3 additions & 0 deletions doc/compress.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,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=5.2.0;f=">5.2</option>
<option value="http://marijnhaverbeke.nl/git/codemirror?a=blob_plain;hb=5.1.0;f=">5.1</option>
<option value="http://marijnhaverbeke.nl/git/codemirror?a=blob_plain;hb=5.0.0;f=">5.0</option>
<option value="http://marijnhaverbeke.nl/git/codemirror?a=blob_plain;hb=4.13.0;f=">4.13</option>
Expand Down Expand Up @@ -132,6 +133,7 @@ <h2>Script compression helper</h2>
<option value="http://codemirror.net/mode/go/go.js">go.js</option>
<option value="http://codemirror.net/mode/groovy/groovy.js">groovy.js</option>
<option value="http://codemirror.net/mode/haml/haml.js">haml.js</option>
<option value="http://codemirror.net/mode/handlebars/handlebars.js">handlebars.js</option>
<option value="http://codemirror.net/mode/haskell/haskell.js">haskell.js</option>
<option value="http://codemirror.net/mode/haxe/haxe.js">haxe.js</option>
<option value="http://codemirror.net/mode/htmlembedded/htmlembedded.js">htmlembedded.js</option>
Expand All @@ -149,6 +151,7 @@ <h2>Script compression helper</h2>
<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/modelica/modelica.js">modelica.js</option>
<option value="http://codemirror.net/mode/mumps/mumps.js">mumps.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/octave/octave.js">octave.js</option>
Expand Down
78 changes: 53 additions & 25 deletions doc/manual.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
<section class=first id=overview>
<h2 style="position: relative">
User manual and reference guide
<span style="color: #888; font-size: 1rem; position: absolute; right: 0; bottom: 0">version 5.1.0</span>
<span style="color: #888; font-size: 1rem; position: absolute; right: 0; bottom: 0">version 5.2.0</span>
</h2>

<p>CodeMirror is a code-editor component that can be embedded in
Expand Down Expand Up @@ -354,6 +354,11 @@ <h2>Configuration</h2>
<dd>Whether the cursor should be drawn when a selection is
active. Defaults to false.</dd>

<dt id="option_lineWiseCopyCut"><code><strong>lineWiseCopyCut</strong>: boolean</code></dt>
<dd>When enabled, which is the default, doing copy or cut when
there is no selection will copy or cut the whole lines that have
cursors on them.</dd>

<dt id="option_undoDepth"><code><strong>undoDepth</strong>: integer</code></dt>
<dd>The maximum number of undo levels that the editor stores.
Note that this includes selection change events. Defaults to
Expand Down Expand Up @@ -445,18 +450,6 @@ <h2>Configuration</h2>
10 000. You can set this to <code>Infinity</code> to turn off
this behavior.</dd>

<dt id="option_crudeMeasuringFrom"><code><strong>crudeMeasuringFrom</strong>: number</code></dt>
<dd>When measuring the character positions in long lines, any
line longer than this number (default is 10 000),
when <a href="#option_lineWrapping">line wrapping</a>
is <strong>off</strong>, will simply be assumed to consist of
same-sized characters. This means that, on the one hand,
measuring will be inaccurate when characters of varying size,
right-to-left text, markers, or other irregular elements are
present. On the other hand, it means that having such a line
won't freeze the user interface because of the expensiveness of
the measurements.</dd>

<dt id="option_viewportMargin"><code><strong>viewportMargin</strong>: integer</code></dt>
<dd>Specifies the amount of lines that are rendered above and
below the part of the document that's currently scrolled into
Expand Down Expand Up @@ -544,6 +537,11 @@ <h2>Events</h2>
<dd>Fired whenever new input is read from the hidden textarea
(typed or pasted by the user).</dd>

<dt id="event_electricInput"><code><strong>"electrictInput"</strong> (instance: CodeMirror, line: integer)</code></dt>
<dd>Fired if text input matched the
mode's <a href="#option_electricChars">electric</a> patterns,
and this caused the line's indentation to change.</dd>

<dt id="event_beforeSelectionChange"><code><strong>"beforeSelectionChange"</strong> (instance: CodeMirror, obj: {ranges, update})</code></dt>
<dd>This event is fired before the selection is moved. Its
handler may inspect the set of selection ranges, present as an
Expand Down Expand Up @@ -859,10 +857,10 @@ <h2>Commands</h2>
<dd>Redo the last change to the selection, or the last text change if
no selection changes remain.</dd>

<dt class=command id=command_goDocStart><code><strong>goDocStart</strong></code><span class=keybinding>Ctrl-Up (PC), Cmd-Up (Mac), Cmd-Home (Mac)</span></dt>
<dt class=command id=command_goDocStart><code><strong>goDocStart</strong></code><span class=keybinding>Ctrl-Home (PC), Cmd-Up (Mac), Cmd-Home (Mac)</span></dt>
<dd>Move the cursor to the start of the document.</dd>

<dt class=command id=command_goDocEnd><code><strong>goDocEnd</strong></code><span class=keybinding>Ctrl-Down (PC), Cmd-End (Mac), Cmd-Down (Mac)</span></dt>
<dt class=command id=command_goDocEnd><code><strong>goDocEnd</strong></code><span class=keybinding>Ctrl-End (PC), Cmd-End (Mac), Cmd-Down (Mac)</span></dt>
<dd>Move the cursor to the end of the document.</dd>

<dt class=command id=command_goLineStart><code><strong>goLineStart</strong></code><span class=keybinding>Alt-Left (PC), Ctrl-A (Mac)</span></dt>
Expand Down Expand Up @@ -1806,7 +1804,7 @@ <h3 id="api_sizing">Sizing, scrolling and positioning methods</h3>
height. <code>mode</code> can be one of the same strings
that <a href="#coordsChar"><code>coordsChar</code></a>
accepts.</dd>
<dt id="heightAtLine"><code><strong>cm.heightAtLine</strong>(line: number, ?mode: string) → number</code></dt>
<dt id="heightAtLine"><code><strong>cm.heightAtLine</strong>(line: integer|LineHandle, ?mode: string) → number</code></dt>
<dd>Computes the height of the top of a line, in the coordinate
system specified by <code>mode</code>
(see <a href="#coordsChar"><code>coordsChar</code></a>), which
Expand Down Expand Up @@ -1851,7 +1849,7 @@ <h3 id="api_mode">Mode, state, and token-related methods</h3>
the mode specification, rather than the resolved, instantiated
<a href="#defineMode">mode object</a>.</dd>

<dt id="getModeAt"><code><strong>doc.getModeAt</strong>(pos: {line, ch}) → object</code></dt>
<dt id="getModeAt"><code><strong>cm.getModeAt</strong>(pos: {line, ch}) → object</code></dt>
<dd>Gets the inner mode at a given position. This will return
the same as <a href="#getMode"><code>getMode</code></a> for
simple modes, but will return an inner mode for nesting modes
Expand Down Expand Up @@ -2175,7 +2173,7 @@ <h2 id="addons">Addons</h2>
to <code>findNext</code> or <code>findPrevious</code> did
not return false. They will return <code>{line, ch}</code>
objects pointing at the start and end of the match.</dd>
<dt><code><strong>replace</strong>(text: string)</code></dt>
<dt><code><strong>replace</strong>(text: string, ?origin: string)</code></dt>
<dd>Replaces the currently found match with the given text
and adjusts the cursor position to reflect the
replacement.</dd>
Expand Down Expand Up @@ -2433,7 +2431,7 @@ <h2 id="addons">Addons</h2>
between several modes.
Defines <code>CodeMirror.multiplexingMode</code> which, when
given as first argument a mode object, and as other arguments
any number of <code>{open, close, mode [, delimStyle, innerStyle]}</code>
any number of <code>{open, close, mode [, delimStyle, innerStyle, parseDelimiters]}</code>
objects, will return a mode object that starts parsing using the
mode passed as first argument, but will switch to another mode
as soon as it encounters a string that occurs in one of
Expand All @@ -2445,8 +2443,11 @@ <h2 id="addons">Addons</h2>
<ul><li>When <code>delimStyle</code> is specified, it will be the token
style returned for the delimiter tokens.</li>
<li>When <code>innerStyle</code> is specified, it will be the token
style added for each inner mode token.</li></ul>
The outer mode will not see the content between the delimiters.
style added for each inner mode token.</li>
<li>When <code>parseDelimiters</code> is true, the content of
the delimiters will also be passed to the inner mode.
(And <code>delimStyle</code> is ignored.)</li></ul> The outer
mode will not see the content between the delimiters.
See <a href="../demo/multiplex.html">this demo</a> for an
example.</dd>

Expand Down Expand Up @@ -2764,13 +2765,40 @@ <h2 id="addons">Addons</h2>
instances, which places a DOM node above or below an editor, and
shrinks the editor to make room for the node. The method takes
as first argument as DOM node, and as second an optional options
object. By default, the panel ends up above the editor. This can
be changed by passing a <code>position</code> option with the
value <code>"bottom"</code>. The object returned by this method
object. The <code>Panel</code> object returned by this method
has a <code>clear</code> method that is used to remove the
panel, and a <code>changed</code> method that can be used to
notify the addon when the size of the panel's DOM node has
changed.</dd>
changed.<br/>
The method accepts the following options:
<dl>
<dt><code><strong>position</strong> : string</code></dt>
<dd>Controls the position of the newly added panel. The
following values are recognized:
<dl>
<dt><code><strong>top</strong> (default)</code></dt>
<dd>Adds the panel at the very top.</dd>
<dt><code><strong>after-top</strong></code></dt>
<dd>Adds the panel at the bottom of the top panels.</dd>
<dt><code><strong>bottom</strong></code></dt>
<dd>Adds the panel at the very bottom.</dd>
<dt><code><strong>before-bottom</strong></code></dt>
<dd>Adds the panel at the top of the bottom panels.</dd>
</dl>
</dd>
<dt><code><strong>before</strong> : Panel</code></dt>
<dd>The new panel will be added before the given panel.</dd>
<dt><code><strong>after</strong> : Panel</code></dt>
<dd>The new panel will be added after the given panel.</dd>
<dt><code><strong>replace</strong> : Panel</code></dt>
<dd>The new panel will replace the given panel.</dd>
</dl>
When using the <code>after</code>, <code>before</code> or <code>replace</code> options,
if the panel doesn't exists or has been removed,
the value of the <code>position</code> option will be used as a fallback.
<br>
A demo of the addon is available <a href="../demo/panel.html">here</a>.
</dd>

<dt id="addon_hardwrap"><a href="../addon/wrap/hardwrap.js"><code>wrap/hardwrap.js</code></a></dt>
<dd>Addon to perform hard line wrapping/breaking for paragraphs
Expand Down
2 changes: 2 additions & 0 deletions doc/realworld.html
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ <h2>CodeMirror real-world uses</h2>
<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://kodtest.com/">Kodtest</a> (HTML/JS/CSS playground)</li>
<li><a href="http://try.kotlinlang.org">Kotlin</a> (web-based mini-IDE for Kotlin)</li>
<li><a href="https://laborate.io/">Laborate</a> (collaborative coding)</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>
Expand Down Expand Up @@ -138,6 +139,7 @@ <h2>CodeMirror real-world uses</h2>
<li><a href="http://snaptomato.appspot.com/editor.html">Snap Tomato</a> (HTML editing/testing page)</li>
<li><a href="http://snippets.pro/">Snippets.pro</a> (code snippet sharing)</li>
<li><a href="http://www.solidshops.com/">SolidShops</a> (hosted e-commerce platform)</li>
<li><a href="http://www.cemetech.net/sc/">SourceCoder 3</a> (online calculator IDE and editor)</li>
<li><a href="http://sqlfiddle.com">SQLFiddle</a> (SQL playground)</li>
<li><a href="http://www.subte.org/page/programar-ta-te-ti-online/">SubTe</a> (AI bot programming environment)</li>
<li><a href="http://xuanji.appspot.com/isicp/">Structure and Interpretation of Computer Programs</a>, Interactive Version</li>
Expand Down
19 changes: 17 additions & 2 deletions doc/releases.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,23 @@ <h2>Release notes and version history</h2>

<h2>Version 5.x</h2>

<p class="rel">20-04-2015: <a href="http://codemirror.net/codemirror-5.2.zip">Version 5.2</a>:</p>

<ul class="rel-note">
<li>Fix several race conditions
in <a href="manual.html#addon_show-hint"><code>show-hint</code></a>'s
asynchronous mode</li>
<li>Fix backspace binding in <a href="../demo/sublime.html">Sublime bindings</a></li>
<li>Change the way IME is handled in the <code>"textarea"</code> <a href="manual.html#option_inputStyle">input style</a></li>

<li>New modes: <a href="../mode/mumps/index.html">MUMPS</a>, <a href="../mode/handlebars/index.html">Handlebars</a></li>
<li>Rewritten modes: <a href="../mode/django/index.html">Django</a>, <a href="../mode/z80/index.html">Z80</a></li>
<li>Theme: <a href="../demo/theme.html?liquibyte">Liquibyte</a></li>
<li>New option: <a href="manual.html#option_lineWiseCopyCut"><code>lineWiseCopyCut</code></a></li>
<li>The <a href="../demo/vim.html">Vim mode</a> now supports buffer-local options and the <code>filetype</code> setting</li>
<li>Full <a href="https://github.com/codemirror/CodeMirror/compare/5.1.0...5.2.0">list of patches</a></li>
</ul>

<p class="rel">23-03-2015: <a href="http://codemirror.net/codemirror-5.1.zip">Version 5.1</a>:</p>

<ul class="rel-note">
Expand All @@ -41,8 +58,6 @@ <h2>Version 5.x</h2>
<li>Full <a href="https://github.com/codemirror/CodeMirror/compare/5.0.0...5.1.0">list of patches</a>.</li>
</ul>

<h2>Version 5.x</h2>

<p class="rel">20-02-2015: <a href="http://codemirror.net/codemirror-5.0.zip">Version 5.0</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 @@ -96,7 +96,7 @@ <h2>This is CodeMirror</h2>
</div>
</div>
<div class=actionsleft>
Get the current version: <a href="http://codemirror.net/codemirror.zip">5.1</a>.<br>
Get the current version: <a href="http://codemirror.net/codemirror.zip">5.2</a>.<br>
You can see the <a href="https://github.com/codemirror/codemirror" title="Github repository">code</a> or<br>
read the <a href="doc/releases.html">release notes</a>.<br>
There is a <a href="doc/compress.html">minification helper</a>.
Expand All @@ -119,7 +119,7 @@ <h2>This is CodeMirror</h2>
<section id=features>
<h2>Features</h2>
<ul>
<li>Support for <a href="mode/index.html">over 90 languages</a> out of the box
<li>Support for <a href="mode/index.html">over 100 languages</a> out of the box
<li>A powerful, <a href="mode/htmlmixed/index.html">composable</a> language mode <a href="doc/manual.html#modeapi">system</a>
<li><a href="doc/manual.html#addon_show-hint">Autocompletion</a> (<a href="demo/xmlcomplete.html">XML</a>)
<li><a href="doc/manual.html#addon_foldcode">Code folding</a>
Expand Down
2 changes: 1 addition & 1 deletion keymap/sublime.js
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@
var toStartOfLine = cm.getRange({line: cursor.line, ch: 0}, cursor);
var column = CodeMirror.countColumn(toStartOfLine, null, cm.getOption("tabSize"));

if (!/\S/.test(toStartOfLine) && column % cm.getOption("indentUnit") == 0)
if (toStartOfLine && !/\S/.test(toStartOfLine) && column % cm.getOption("indentUnit") == 0)
return cm.indentSelection("subtract");
else
return CodeMirror.Pass;
Expand Down
140 changes: 117 additions & 23 deletions keymap/vim.js
Original file line number Diff line number Diff line change
Expand Up @@ -382,18 +382,30 @@
}

var options = {};
function defineOption(name, defaultValue, type) {
if (defaultValue === undefined) { throw Error('defaultValue is required'); }
function defineOption(name, defaultValue, type, aliases, callback) {
if (defaultValue === undefined && !callback) {
throw Error('defaultValue is required unless callback is provided');
}
if (!type) { type = 'string'; }
options[name] = {
type: type,
defaultValue: defaultValue
defaultValue: defaultValue,
callback: callback
};
setOption(name, defaultValue);
if (aliases) {
for (var i = 0; i < aliases.length; i++) {
options[aliases[i]] = options[name];
}
}
if (defaultValue) {
setOption(name, defaultValue);
}
}

function setOption(name, value) {
function setOption(name, value, cm, cfg) {
var option = options[name];
cfg = cfg || {};
var scope = cfg.scope;
if (!option) {
throw Error('Unknown option: ' + name);
}
Expand All @@ -405,17 +417,60 @@
value = true;
}
}
option.value = option.type == 'boolean' ? !!value : value;
if (option.callback) {
if (scope !== 'local') {
option.callback(value, undefined);
}
if (scope !== 'global' && cm) {
option.callback(value, cm);
}
} else {
if (scope !== 'local') {
option.value = option.type == 'boolean' ? !!value : value;
}
if (scope !== 'global' && cm) {
cm.state.vim.options[name] = {value: value};
}
}
}

function getOption(name) {
function getOption(name, cm, cfg) {
var option = options[name];
cfg = cfg || {};
var scope = cfg.scope;
if (!option) {
throw Error('Unknown option: ' + name);
}
return option.value;
if (option.callback) {
var local = cm && option.callback(undefined, cm);
if (scope !== 'global' && local !== undefined) {
return local;
}
if (scope !== 'local') {
return option.callback();
}
return;
} else {
var local = (scope !== 'global') && (cm && cm.state.vim.options[name]);
return (local || (scope !== 'local') && option || {}).value;
}
}

defineOption('filetype', undefined, 'string', ['ft'], function(name, cm) {
// Option is local. Do nothing for global.
if (cm === undefined) {
return;
}
// The 'filetype' option proxies to the CodeMirror 'mode' option.
if (name === undefined) {
var mode = cm.getMode().name;
return mode == 'null' ? '' : mode;
} else {
var mode = name == '' ? 'null' : name;
cm.setOption('mode', mode);
}
});

var createCircularJumpList = function() {
var size = 100;
var pointer = -1;
Expand Down Expand Up @@ -568,8 +623,9 @@
visualBlock: false,
lastSelection: null,
lastPastedText: null,
sel: {
}
sel: {},
// Buffer-local/window-local values of vim options.
options: {}
};
}
return cm.state.vim;
Expand Down Expand Up @@ -627,6 +683,8 @@
// Add user defined key bindings.
exCommandDispatcher.map(lhs, rhs, ctx);
},
// TODO: Expose setOption and getOption as instance methods. Need to decide how to namespace
// them, or somehow make them work with the existing CodeMirror setOption/getOption API.
setOption: setOption,
getOption: getOption,
defineOption: defineOption,
Expand Down Expand Up @@ -1178,7 +1236,8 @@
}
function onPromptKeyDown(e, query, close) {
var keyName = CodeMirror.keyName(e);
if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') {
if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' ||
(keyName == 'Backspace' && query == '')) {
vimGlobalState.searchHistoryController.pushInput(query);
vimGlobalState.searchHistoryController.reset();
updateSearchQuery(cm, originalQuery);
Expand All @@ -1188,6 +1247,10 @@
clearInputState(cm);
close();
cm.focus();
} else if (keyName == 'Ctrl-U') {
// Ctrl-U clears input.
CodeMirror.e_stop(e);
close('');
}
}
switch (command.searchArgs.querySrc) {
Expand Down Expand Up @@ -1248,7 +1311,8 @@
}
function onPromptKeyDown(e, input, close) {
var keyName = CodeMirror.keyName(e), up;
if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') {
if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' ||
(keyName == 'Backspace' && input == '')) {
vimGlobalState.exCommandHistoryController.pushInput(input);
vimGlobalState.exCommandHistoryController.reset();
CodeMirror.e_stop(e);
Expand All @@ -1260,6 +1324,10 @@
up = keyName == 'Up' ? true : false;
input = vimGlobalState.exCommandHistoryController.nextMatch(input, up) || '';
close(input);
} else if (keyName == 'Ctrl-U') {
// Ctrl-U clears input.
CodeMirror.e_stop(e);
close('');
} else {
if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift')
vimGlobalState.exCommandHistoryController.reset();
Expand Down Expand Up @@ -1289,8 +1357,8 @@
var registerName = inputState.registerName;
var sel = vim.sel;
// TODO: Make sure cm and vim selections are identical outside visual mode.
var origHead = copyCursor(vim.visualMode ? sel.head: cm.getCursor('head'));
var origAnchor = copyCursor(vim.visualMode ? sel.anchor : cm.getCursor('anchor'));
var origHead = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.head): cm.getCursor('head'));
var origAnchor = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.anchor) : cm.getCursor('anchor'));
var oldHead = copyCursor(origHead);
var oldAnchor = copyCursor(origAnchor);
var newHead, newAnchor;
Expand Down Expand Up @@ -1856,10 +1924,11 @@
var anchor = ranges[0].anchor,
head = ranges[0].head;
text = cm.getRange(anchor, head);
if (!isWhiteSpaceString(text)) {
var lastState = vim.lastEditInputState || {};
if (lastState.motion == "moveByWords" && !isWhiteSpaceString(text)) {
// Exclude trailing whitespace if the range is not all whitespace.
var match = (/\s+$/).exec(text);
if (match) {
if (match && lastState.motionArgs && lastState.motionArgs.forward) {
head = offsetCursor(head, 0, - match[0].length);
text = text.slice(0, - match[0].length);
}
Expand Down Expand Up @@ -3849,6 +3918,7 @@
// 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: 'colorscheme', shortName: 'colo' },
{ name: 'map' },
{ name: 'imap', shortName: 'im' },
{ name: 'nmap', shortName: 'nm' },
Expand All @@ -3857,7 +3927,10 @@
{ name: 'write', shortName: 'w' },
{ name: 'undo', shortName: 'u' },
{ name: 'redo', shortName: 'red' },
{ name: 'set', shortName: 'set' },
{ name: 'set', shortName: 'se' },
{ name: 'set', shortName: 'se' },
{ name: 'setlocal', shortName: 'setl' },
{ name: 'setglobal', shortName: 'setg' },
{ name: 'sort', shortName: 'sor' },
{ name: 'substitute', shortName: 's', possiblyAsync: true },
{ name: 'nohlsearch', shortName: 'noh' },
Expand Down Expand Up @@ -4089,6 +4162,13 @@
};

var exCommands = {
colorscheme: function(cm, params) {
if (!params.args || params.args.length < 1) {
showConfirm(cm, cm.getOption('theme'));
return;
}
cm.setOption('theme', params.args[0]);
},
map: function(cm, params, ctx) {
var mapArgs = params.args;
if (!mapArgs || mapArgs.length < 2) {
Expand Down Expand Up @@ -4122,6 +4202,9 @@
},
set: function(cm, params) {
var setArgs = params.args;
// Options passed through to the setOption/getOption calls. May be passed in by the
// local/global versions of the set command
var setCfg = params.setCfg || {};
if (!setArgs || setArgs.length < 1) {
if (cm) {
showConfirm(cm, 'Invalid mapping: ' + params.input);
Expand All @@ -4145,24 +4228,35 @@
optionName = optionName.substring(2);
value = false;
}

var optionIsBoolean = options[optionName] && options[optionName].type == 'boolean';
if (optionIsBoolean && value == undefined) {
// Calling set with a boolean option sets it to true.
value = true;
}
if (!optionIsBoolean && !value || forceGet) {
var oldValue = getOption(optionName);
// If no value is provided, then we assume this is a get.
// If no value is provided, then we assume this is a get.
if (!optionIsBoolean && value === undefined || forceGet) {
var oldValue = getOption(optionName, cm, setCfg);
if (oldValue === true || oldValue === false) {
showConfirm(cm, ' ' + (oldValue ? '' : 'no') + optionName);
} else {
showConfirm(cm, ' ' + optionName + '=' + oldValue);
}
} else {
setOption(optionName, value);
setOption(optionName, value, cm, setCfg);
}
},
registers: function(cm,params) {
setlocal: function (cm, params) {
// setCfg is passed through to setOption
params.setCfg = {scope: 'local'};
this.set(cm, params);
},
setglobal: function (cm, params) {
// setCfg is passed through to setOption
params.setCfg = {scope: 'global'};
this.set(cm, params);
},
registers: function(cm, params) {
var regArgs = params.args;
var registers = vimGlobalState.registerController.registers;
var regInfo = '----------Registers----------<br><br>';
Expand Down Expand Up @@ -4784,7 +4878,7 @@
}
function updateFakeCursor(cm) {
var vim = cm.state.vim;
var from = copyCursor(vim.sel.head);
var from = clipCursorToContent(cm, copyCursor(vim.sel.head));
var to = offsetCursor(from, 0, 1);
if (vim.fakeCursor) {
vim.fakeCursor.clear();
Expand Down
2 changes: 2 additions & 0 deletions lib/codemirror.css
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ div.CodeMirror-overwrite div.CodeMirror-cursor {}
.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}

.CodeMirror-composing { border-bottom: 2px solid; }

/* Default styles for common addons */

div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
Expand Down
131 changes: 92 additions & 39 deletions lib/codemirror.js
Original file line number Diff line number Diff line change
Expand Up @@ -1079,7 +1079,7 @@
// was made out of.
var lastCopied = null;

function applyTextInput(cm, inserted, deleted, sel) {
function applyTextInput(cm, inserted, deleted, sel, origin) {
var doc = cm.doc;
cm.display.shift = false;
if (!sel) sel = doc.sel;
Expand All @@ -1105,7 +1105,7 @@
}
var updateInput = cm.curOp.updateInput;
var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines,
origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"};
origin: origin || (cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input")};
makeChange(cm.doc, changeEvent);
signalLater(cm, "inputRead", cm, changeEvent);
// When an 'electric' character is inserted, immediately trigger a reindent
Expand All @@ -1114,16 +1114,18 @@
(!i || sel.ranges[i - 1].head.line != range.head.line)) {
var mode = cm.getModeAt(range.head);
var end = changeEnd(changeEvent);
var indented = false;
if (mode.electricChars) {
for (var j = 0; j < mode.electricChars.length; j++)
if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
indentLine(cm, end.line, "smart");
indented = indentLine(cm, end.line, "smart");
break;
}
} else if (mode.electricInput) {
if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.ch)))
indentLine(cm, end.line, "smart");
indented = indentLine(cm, end.line, "smart");
}
if (indented) signalLater(cm, "electricInput", cm, end.line);
}
}
ensureCursorVisible(cm);
Expand Down Expand Up @@ -1167,6 +1169,7 @@
this.inaccurateSelection = false;
// Used to work around IE issue with selection being forgotten when focus moves away from textarea
this.hasSelection = false;
this.composing = null;
};

function hiddenTextarea() {
Expand Down Expand Up @@ -1231,6 +1234,8 @@
te.value = lastCopied.join("\n");
selectInput(te);
}
} else if (!cm.options.lineWiseCopyCut) {
return;
} else {
var ranges = copyableRanges(cm);
lastCopied = ranges.text;
Expand All @@ -1257,6 +1262,21 @@
on(display.lineSpace, "selectstart", function(e) {
if (!eventInWidget(display, e)) e_preventDefault(e);
});

on(te, "compositionstart", function() {
var start = cm.getCursor("from");
input.composing = {
start: start,
range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"})
};
});
on(te, "compositionend", function() {
if (input.composing) {
input.poll();
input.composing.range.clear();
input.composing = null;
}
});
},

prepareSelection: function() {
Expand Down Expand Up @@ -1385,24 +1405,28 @@
}

if (cm.doc.sel == cm.display.selForContextMenu) {
if (text.charCodeAt(0) == 0x200b) {
if (!prevInput) prevInput = "\u200b";
} else if (prevInput == "\u200b") {
text = text.slice(1);
prevInput = "";
}
var first = text.charCodeAt(0);
if (first == 0x200b && !prevInput) prevInput = "\u200b";
if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo"); }
}
// Find the part of the input that is actually new
var same = 0, l = Math.min(prevInput.length, text.length);
while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;

var self = this;
runInOp(cm, function() {
applyTextInput(cm, text.slice(same), prevInput.length - same);
applyTextInput(cm, text.slice(same), prevInput.length - same,
null, self.composing ? "*compose" : null);

// Don't leave long text in the textarea, since it makes further polling slow
if (text.length > 1000 || text.indexOf("\n") > -1) input.value = self.prevInput = "";
else self.prevInput = text;

if (self.composing) {
self.composing.range.clear();
self.composing.range = cm.markText(self.composing.start, cm.getCursor("to"),
{className: "CodeMirror-composing"});
}
});
return true;
},
Expand Down Expand Up @@ -1449,7 +1473,9 @@
function prepareSelectAllHack() {
if (te.selectionStart != null) {
var selected = cm.somethingSelected();
var extval = te.value = "\u200b" + (selected ? te.value : "");
var extval = "\u200b" + (selected ? te.value : "");
te.value = "\u21da"; // Used to catch context-menu undo
te.value = extval;
input.prevInput = selected ? "" : "\u200b";
te.selectionStart = 1; te.selectionEnd = extval.length;
// Re-set this, in case some other handler touched the
Expand All @@ -1467,7 +1493,8 @@
if (te.selectionStart != null) {
if (!ie || (ie && ie_version < 9)) prepareSelectAllHack();
var i = 0, poll = function() {
if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && input.prevInput == "\u200b")
if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 &&
te.selectionEnd > 0 && input.prevInput == "\u200b")
operation(cm, commands.selectAll)(cm);
else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500);
else display.input.reset();
Expand Down Expand Up @@ -1562,6 +1589,8 @@
if (cm.somethingSelected()) {
lastCopied = cm.getSelections();
if (e.type == "cut") cm.replaceSelection("", null, "cut");
} else if (!cm.options.lineWiseCopyCut) {
return;
} else {
var ranges = copyableRanges(cm);
lastCopied = ranges.text;
Expand Down Expand Up @@ -2919,6 +2948,7 @@
updateMaxLine: false, // Set when the widest line needs to be determined anew
scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet
scrollToPos: null, // Used to scroll to a specific position
focus: false,
id: ++nextOpId // Unique ID
};
if (operationGroup) {
Expand Down Expand Up @@ -3036,6 +3066,7 @@

if (cm.state.focused && op.updateInput)
cm.display.input.reset(op.typing);
if (op.focus && op.focus == activeElt()) ensureFocus(op.cm);
}

function endOperation_finish(op) {
Expand Down Expand Up @@ -3400,15 +3431,11 @@
// Prevent wrapper from ever scrolling
on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });

function drag_(e) {
if (!signalDOMEvent(cm, e)) e_stop(e);
}
if (cm.options.dragDrop) {
on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
on(d.scroller, "dragenter", drag_);
on(d.scroller, "dragover", drag_);
on(d.scroller, "drop", operation(cm, onDrop));
}
d.dragFunctions = {
simple: function(e) {if (!signalDOMEvent(cm, e)) e_stop(e);},
start: function(e){onDragStart(cm, e);},
drop: operation(cm, onDrop)
};

var inp = d.input.getField();
on(inp, "keyup", function(e) { onKeyUp.call(cm, e); });
Expand All @@ -3418,6 +3445,18 @@
on(inp, "blur", bind(onBlur, cm));
}

function dragDropChanged(cm, value, old) {
var wasOn = old && old != CodeMirror.Init;
if (!value != !wasOn) {
var funcs = cm.display.dragFunctions;
var toggle = value ? on : off;
toggle(cm.display.scroller, "dragstart", funcs.start);
toggle(cm.display.scroller, "dragenter", funcs.simple);
toggle(cm.display.scroller, "dragover", funcs.simple);
toggle(cm.display.scroller, "drop", funcs.drop);
}
}

// Called when the window resizes
function onResize(cm) {
var d = cm.display;
Expand Down Expand Up @@ -3507,7 +3546,7 @@
var lastClick, lastDoubleClick;
function leftButtonDown(cm, e, start) {
if (ie) setTimeout(bind(ensureFocus, cm), 0);
else ensureFocus(cm);
else cm.curOp.focus = activeElt();

var now = +new Date, type;
if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) {
Expand All @@ -3532,15 +3571,15 @@
// Start a text drag. When it ends, see if any dragging actually
// happen, and treat as a click if it didn't.
function leftButtonStartDrag(cm, e, start, modifier) {
var display = cm.display;
var display = cm.display, startTime = +new Date;
var dragEnd = operation(cm, function(e2) {
if (webkit) display.scroller.draggable = false;
cm.state.draggingText = false;
off(document, "mouseup", dragEnd);
off(display.scroller, "drop", dragEnd);
if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
e_preventDefault(e2);
if (!modifier)
if (!modifier && +new Date - 200 < startTime)
extendSelection(cm.doc, start);
// Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081)
if (webkit || ie && ie_version == 9)
Expand Down Expand Up @@ -3667,7 +3706,7 @@
var cur = posFromMouse(cm, e, true, type == "rect");
if (!cur) return;
if (cmp(cur, lastPos) != 0) {
ensureFocus(cm);
cm.curOp.focus = activeElt();
extendTo(cur);
var visible = visibleLines(display, doc);
if (cur.line >= visible.to || cur.line < visible.from)
Expand Down Expand Up @@ -3770,7 +3809,7 @@
try {
var text = e.dataTransfer.getData("Text");
if (text) {
if (cm.state.draggingText && !(mac ? e.metaKey : e.ctrlKey))
if (cm.state.draggingText && !(mac ? e.altKey : e.ctrlKey))
var selected = cm.listSelections();
setSelectionNoUndo(cm.doc, simpleSelection(pos, pos));
if (selected) for (var i = 0; i < selected.length; ++i)
Expand Down Expand Up @@ -4025,7 +4064,7 @@
var lastStoppedKey = null;
function onKeyDown(e) {
var cm = this;
ensureFocus(cm);
cm.curOp.focus = activeElt();
if (signalDOMEvent(cm, e)) return;
// IE does strange things with escape.
if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false;
Expand Down Expand Up @@ -4613,6 +4652,8 @@

if (indentString != curSpaceString) {
replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
line.stateAfter = null;
return true;
} else {
// Ensure that, if the cursor was in the whitespace at the start
// of the line, it is moved to the end of that space.
Expand All @@ -4625,7 +4666,6 @@
}
}
}
line.stateAfter = null;
}

// Utility for applying a change to a line by handle or number,
Expand Down Expand Up @@ -4915,10 +4955,15 @@
return lineAtHeight(this.doc, height + this.display.viewOffset);
},
heightAtLine: function(line, mode) {
var end = false, last = this.doc.first + this.doc.size - 1;
if (line < this.doc.first) line = this.doc.first;
else if (line > last) { line = last; end = true; }
var lineObj = getLine(this.doc, line);
var end = false, lineObj;
if (typeof line == "number") {
var last = this.doc.first + this.doc.size - 1;
if (line < this.doc.first) line = this.doc.first;
else if (line > last) { line = last; end = true; }
lineObj = getLine(this.doc, line);
} else {
lineObj = line;
}
return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").top +
(end ? this.doc.height - heightAtLine(lineObj) : 0);
},
Expand Down Expand Up @@ -5270,6 +5315,7 @@
option("showCursorWhenSelecting", false, updateSelection, true);

option("resetSelectionOnContextMenu", true);
option("lineWiseCopyCut", true);

option("readOnly", false, function(cm, val) {
if (val == "nocursor") {
Expand All @@ -5282,7 +5328,7 @@
}
});
option("disableInput", false, function(cm, val) {if (!val) cm.display.input.reset();}, true);
option("dragDrop", true);
option("dragDrop", true, dragDropChanged);

option("cursorBlinkRate", 530);
option("cursorScrollMargin", 0);
Expand Down Expand Up @@ -6922,8 +6968,13 @@
var foundBookmarks = [];
for (var j = 0; j < spans.length; ++j) {
var sp = spans[j], m = sp.marker;
if (sp.from <= pos && (sp.to == null || sp.to > pos)) {
if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; }
if (m.type == "bookmark" && sp.from == pos && m.widgetNode) {
foundBookmarks.push(m);
} else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) {
if (sp.to != null && sp.to != pos && nextChange > sp.to) {
nextChange = sp.to;
spanEndStyle = "";
}
if (m.className) spanStyle += " " + m.className;
if (m.css) css = m.css;
if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
Expand All @@ -6934,12 +6985,12 @@
} else if (sp.from > pos && nextChange > sp.from) {
nextChange = sp.from;
}
if (m.type == "bookmark" && sp.from == pos && m.widgetNode) foundBookmarks.push(m);
}
if (collapsed && (collapsed.from || 0) == pos) {
buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos,
collapsed.marker, collapsed.from == null);
if (collapsed.to == null) return;
if (collapsed.to == pos) collapsed = false;
}
if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j)
buildCollapsedSpan(builder, 0, foundBookmarks[j]);
Expand Down Expand Up @@ -8148,7 +8199,7 @@
return function(){return f.apply(null, args);};
}

var nonASCIISingleCaseWordChar = /[\u00df\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
var isWordCharBasic = CodeMirror.isWordChar = function(ch) {
return /\w/.test(ch) || ch > "\x80" &&
(ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
Expand Down Expand Up @@ -8670,6 +8721,8 @@
lst(order).to -= m[0].length;
order.push(new BidiSpan(0, len - m[0].length, len));
}
if (order[0].level == 2)
order.unshift(new BidiSpan(1, order[0].to, order[0].to));
if (order[0].level != lst(order).level)
order.push(new BidiSpan(order[0].level, len, len));

Expand All @@ -8679,7 +8732,7 @@

// THE END

CodeMirror.version = "5.1.0";
CodeMirror.version = "5.2.0";

return CodeMirror;
});
2 changes: 1 addition & 1 deletion mode/clike/clike.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
return obj;
}
var cKeywords = "auto if break int case long char register continue return default short do sizeof " +
"double static else struct entry switch extern typedef float union for unsigned " +
"double static else struct switch extern typedef float union for unsigned " +
"goto while enum void const signed volatile";

function cppHook(stream, state) {
Expand Down
4 changes: 3 additions & 1 deletion mode/css/css.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
states.interpolation = function(type, stream, state) {
if (type == "}") return popContext(state);
if (type == "{" || type == ";") return popAndPass(type, stream, state);
if (type != "variable") override = "error";
if (type == "word") override = "variable";
else if (type != "variable") override = "error";
return "interpolation";
};

Expand Down Expand Up @@ -750,6 +751,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
}
},
"@": function(stream) {
if (stream.eat("{")) return [null, "interpolation"];
if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/, false)) return false;
stream.eatWhile(/[\w\\\-]/);
if (stream.match(/^\s*:/, false))
Expand Down
3 changes: 3 additions & 0 deletions mode/css/less_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,7 @@
" }",
" }",
"}");


MT("interpolation", ".@{[variable foo]} { [property font-weight]: [atom bold]; }");
})();
2 changes: 1 addition & 1 deletion mode/css/scss_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"[tag foo]#{[variable-2 $hello]} { [property color]:[atom #000]; }");

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

MT("divide_operator",
"[tag foo] { [property width]:[number 4] [operator /] [number 2] }");
Expand Down
331 changes: 307 additions & 24 deletions mode/django/django.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,46 +14,329 @@
"use strict";

CodeMirror.defineMode("django:inner", function() {
var keywords = ["block", "endblock", "for", "endfor", "in", "true", "false",
"loop", "none", "self", "super", "if", "endif", "as", "not", "and",
var keywords = ["block", "endblock", "for", "endfor", "true", "false",
"loop", "none", "self", "super", "if", "endif", "as",
"else", "import", "with", "endwith", "without", "context", "ifequal", "endifequal",
"ifnotequal", "endifnotequal", "extends", "include", "load", "length", "comment",
"endcomment", "empty"];
keywords = new RegExp("^((" + keywords.join(")|(") + "))\\b");
"ifnotequal", "endifnotequal", "extends", "include", "load", "comment",
"endcomment", "empty", "url", "static", "trans", "blocktrans", "now", "regroup",
"lorem", "ifchanged", "endifchanged", "firstof", "debug", "cycle", "csrf_token",
"autoescape", "endautoescape", "spaceless", "ssi", "templatetag",
"verbatim", "endverbatim", "widthratio"],
filters = ["add", "addslashes", "capfirst", "center", "cut", "date",
"default", "default_if_none", "dictsort",
"dictsortreversed", "divisibleby", "escape", "escapejs",
"filesizeformat", "first", "floatformat", "force_escape",
"get_digit", "iriencode", "join", "last", "length",
"length_is", "linebreaks", "linebreaksbr", "linenumbers",
"ljust", "lower", "make_list", "phone2numeric", "pluralize",
"pprint", "random", "removetags", "rjust", "safe",
"safeseq", "slice", "slugify", "stringformat", "striptags",
"time", "timesince", "timeuntil", "title", "truncatechars",
"truncatechars_html", "truncatewords", "truncatewords_html",
"unordered_list", "upper", "urlencode", "urlize",
"urlizetrunc", "wordcount", "wordwrap", "yesno"],
operators = ["==", "!=", "<", ">", "<=", ">=", "in", "not", "or", "and"];

keywords = new RegExp("^\\b(" + keywords.join("|") + ")\\b");
filters = new RegExp("^\\b(" + filters.join("|") + ")\\b");
operators = new RegExp("^\\b(" + operators.join("|") + ")\\b");

// We have to return "null" instead of null, in order to avoid string
// styling as the default, when using Django templates inside HTML
// element attributes
function tokenBase (stream, state) {
stream.eatWhile(/[^\{]/);
var ch = stream.next();
if (ch == "{") {
if (ch = stream.eat(/\{|%|#/)) {
state.tokenize = inTag(ch);
return "tag";
// Attempt to identify a variable, template or comment tag respectively
if (stream.match("{{")) {
state.tokenize = inVariable;
return "tag";
} else if (stream.match("{%")) {
state.tokenize = inTag;
return "tag";
} else if (stream.match("{#")) {
state.tokenize = inComment;
return "comment";
}

// Ignore completely any stream series that do not match the
// Django template opening tags.
while (stream.next() != null && !stream.match("{{", false) && !stream.match("{%", false)) {}
return null;
}

// A string can be included in either single or double quotes (this is
// the delimeter). Mark everything as a string until the start delimeter
// occurs again.
function inString (delimeter, previousTokenizer) {
return function (stream, state) {
if (!state.escapeNext && stream.eat(delimeter)) {
state.tokenize = previousTokenizer;
} else {
if (state.escapeNext) {
state.escapeNext = false;
}

var ch = stream.next();

// Take into account the backslash for escaping characters, such as
// the string delimeter.
if (ch == "\\") {
state.escapeNext = true;
}
}

return "string";
};
}

// Apply Django template variable syntax highlighting
function inVariable (stream, state) {
// Attempt to match a dot that precedes a property
if (state.waitDot) {
state.waitDot = false;

if (stream.peek() != ".") {
return "null";
}

// Dot folowed by a non-word character should be considered an error.
if (stream.match(/\.\W+/)) {
return "error";
} else if (stream.eat(".")) {
state.waitProperty = true;
return "null";
} else {
throw Error ("Unexpected error while waiting for property.");
}
}

// Attempt to match a pipe that precedes a filter
if (state.waitPipe) {
state.waitPipe = false;

if (stream.peek() != "|") {
return "null";
}

// Pipe folowed by a non-word character should be considered an error.
if (stream.match(/\.\W+/)) {
return "error";
} else if (stream.eat("|")) {
state.waitFilter = true;
return "null";
} else {
throw Error ("Unexpected error while waiting for filter.");
}
}

// Highlight properties
if (state.waitProperty) {
state.waitProperty = false;
if (stream.match(/\b(\w+)\b/)) {
state.waitDot = true; // A property can be followed by another property
state.waitPipe = true; // A property can be followed by a filter
return "property";
}
}

// Highlight filters
if (state.waitFilter) {
state.waitFilter = false;
if (stream.match(filters)) {
return "variable-2";
}
}

// Ignore all white spaces
if (stream.eatSpace()) {
state.waitProperty = false;
return "null";
}

// Identify numbers
if (stream.match(/\b\d+(\.\d+)?\b/)) {
return "number";
}

// Identify strings
if (stream.match("'")) {
state.tokenize = inString("'", state.tokenize);
return "string";
} else if (stream.match('"')) {
state.tokenize = inString('"', state.tokenize);
return "string";
}

// Attempt to find the variable
if (stream.match(/\b(\w+)\b/) && !state.foundVariable) {
state.waitDot = true;
state.waitPipe = true; // A property can be followed by a filter
return "variable";
}

// If found closing tag reset
if (stream.match("}}")) {
state.waitProperty = null;
state.waitFilter = null;
state.waitDot = null;
state.waitPipe = null;
state.tokenize = tokenBase;
return "tag";
}

// If nothing was found, advance to the next character
stream.next();
return "null";
}
function inTag (close) {
if (close == "{") {
close = "}";

function inTag (stream, state) {
// Attempt to match a dot that precedes a property
if (state.waitDot) {
state.waitDot = false;

if (stream.peek() != ".") {
return "null";
}

// Dot folowed by a non-word character should be considered an error.
if (stream.match(/\.\W+/)) {
return "error";
} else if (stream.eat(".")) {
state.waitProperty = true;
return "null";
} else {
throw Error ("Unexpected error while waiting for property.");
}
}
return function (stream, state) {
var ch = stream.next();
if ((ch == close) && stream.eat("}")) {
state.tokenize = tokenBase;
return "tag";

// Attempt to match a pipe that precedes a filter
if (state.waitPipe) {
state.waitPipe = false;

if (stream.peek() != "|") {
return "null";
}
if (stream.match(keywords)) {
return "keyword";

// Pipe folowed by a non-word character should be considered an error.
if (stream.match(/\.\W+/)) {
return "error";
} else if (stream.eat("|")) {
state.waitFilter = true;
return "null";
} else {
throw Error ("Unexpected error while waiting for filter.");
}
return close == "#" ? "comment" : "string";
};
}

// Highlight properties
if (state.waitProperty) {
state.waitProperty = false;
if (stream.match(/\b(\w+)\b/)) {
state.waitDot = true; // A property can be followed by another property
state.waitPipe = true; // A property can be followed by a filter
return "property";
}
}

// Highlight filters
if (state.waitFilter) {
state.waitFilter = false;
if (stream.match(filters)) {
return "variable-2";
}
}

// Ignore all white spaces
if (stream.eatSpace()) {
state.waitProperty = false;
return "null";
}

// Identify numbers
if (stream.match(/\b\d+(\.\d+)?\b/)) {
return "number";
}

// Identify strings
if (stream.match("'")) {
state.tokenize = inString("'", state.tokenize);
return "string";
} else if (stream.match('"')) {
state.tokenize = inString('"', state.tokenize);
return "string";
}

// Attempt to match an operator
if (stream.match(operators)) {
return "operator";
}

// Attempt to match a keyword
var keywordMatch = stream.match(keywords);
if (keywordMatch) {
if (keywordMatch[0] == "comment") {
state.blockCommentTag = true;
}
return "keyword";
}

// Attempt to match a variable
if (stream.match(/\b(\w+)\b/)) {
state.waitDot = true;
state.waitPipe = true; // A property can be followed by a filter
return "variable";
}

// If found closing tag reset
if (stream.match("%}")) {
state.waitProperty = null;
state.waitFilter = null;
state.waitDot = null;
state.waitPipe = null;
// If the tag that closes is a block comment tag, we want to mark the
// following code as comment, until the tag closes.
if (state.blockCommentTag) {
state.blockCommentTag = false; // Release the "lock"
state.tokenize = inBlockComment;
} else {
state.tokenize = tokenBase;
}
return "tag";
}

// If nothing was found, advance to the next character
stream.next();
return "null";
}

// Mark everything as comment inside the tag and the tag itself.
function inComment (stream, state) {
if (stream.match("#}")) {
state.tokenize = tokenBase;
}
return "comment";
}

// Mark everything as a comment until the `blockcomment` tag closes.
function inBlockComment (stream, state) {
if (stream.match(/\{%\s*endcomment\s*%\}/, false)) {
state.tokenize = inTag;
stream.match("{%");
return "tag";
} else {
stream.next();
return "comment";
}
}

return {
startState: function () {
return {tokenize: tokenBase};
},
token: function (stream, state) {
return state.tokenize(stream, state);
}
},
blockCommentStart: "{% comment %}",
blockCommentEnd: "{% endcomment %}"
};
});

Expand Down
44 changes: 27 additions & 17 deletions mode/django/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<link rel=stylesheet href="../../doc/docs.css">

<link rel="stylesheet" href="../../lib/codemirror.css">
<link rel="stylesheet" href="../../theme/mdn-like.css">
<script src="../../lib/codemirror.js"></script>
<script src="../../addon/mode/overlay.js"></script>
<script src="../xml/xml.js"></script>
Expand All @@ -30,30 +31,39 @@ <h2>Django template mode</h2>
<form><textarea id="code" name="code">
<!doctype html>
<html>
<head>
<title>My Django web application</title>
</head>
<body>
<h1>
{{ page.title }}
</h1>
<ul class="my-list">
{% for item in items %}
<li>{% item.name %}</li>
{% empty %}
<li>You have no items in your list.</li>
{% endfor %}
</ul>
</body>
<head>
<title>My Django web application</title>
</head>
<body>
<h1>
{{ page.title|capfirst }}
</h1>
<ul class="my-list">
{# traverse a list of items and produce links to their views. #}
{% for item in items %}
<li>
<a href="{% url 'item_view' item.name|slugify %}">
{{ item.name }}
</a>
</li>
{% empty %}
<li>You have no items in your list.</li>
{% endfor %}
</ul>
{% comment "this is a forgotten footer" %}
<footer></footer>
{% endcomment %}
</body>
</html>
</textarea></form>

<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
mode: "django",
indentUnit: 4,
indentWithTabs: true
indentUnit: 2,
indentWithTabs: true,
theme: "mdn-like"
});
</script>

Expand Down
53 changes: 53 additions & 0 deletions mode/handlebars/handlebars.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../../addon/mode/simple"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../../addon/mode/simple"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";

CodeMirror.defineSimpleMode("handlebars", {
start: [
{ regex: /\{\{!--/, push: "dash_comment", token: "comment" },
{ regex: /\{\{!/, push: "comment", token: "comment" },
{ regex: /\{\{/, push: "handlebars", token: "tag" }
],
handlebars: [
{ regex: /\}\}/, pop: true, token: "tag" },

// Double and single quotes
{ regex: /"(?:[^\\]|\\.)*?"/, token: "string" },
{ regex: /'(?:[^\\]|\\.)*?'/, token: "string" },

// Handlebars keywords
{ regex: />|[#\/]([A-Za-z_]\w*)/, token: "keyword" },
{ regex: /(?:else|this)\b/, token: "keyword" },

// Numeral
{ regex: /\d+/i, token: "number" },

// Atoms like = and .
{ regex: /=|~|@|true|false/, token: "atom" },

// Paths
{ regex: /(?:\.\.\/)*(?:[A-Za-z_][\w\.]*)+/, token: "variable-2" }
],
dash_comment: [
{ regex: /--\}\}/, pop: true, token: "comment" },

// Commented code
{ regex: /./, token: "comment"}
],
comment: [
{ regex: /\}\}/, pop: true, token: "comment" },
{ regex: /./, token: "comment" }
]
});

CodeMirror.defineMIME("text/x-handlebars-template", "handlebars");
});
83 changes: 83 additions & 0 deletions mode/handlebars/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<!doctype html>

<title>CodeMirror: Handlebars 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/mode/simple.js"></script>
<script src="../../addon/mode/multiplex.js"></script>
<script src="../xml/xml.js"></script>
<script src="handlebars.js"></script>
<style>.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<div id=nav>
<a href="http://codemirror.net"><h1>CodeMirror</h1><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/codemirror/codemirror">Code</a>
</ul>
<ul>
<li><a href="../index.html">Language modes</a>
<li><a class=active href="#">HTML mixed</a>
</ul>
</div>

<article>
<h2>Handlebars</h2>
<form><textarea id="code" name="code">
{{> breadcrumbs}}

{{!--
You can use the t function to get
content translated to the current locale, es:
{{t 'article_list'}}
--}}

<h1>{{t 'article_list'}}</h1>

{{! one line comment }}

{{#each articles}}
{{~title}}
<p>{{excerpt body size=120 ellipsis=true}}</p>

{{#with author}}
written by {{first_name}} {{last_name}}
from category: {{../category.title}}
{{#if @../last}}foobar!{{/if}}
{{/with~}}

{{#if promoted.latest}}Read this one! {{else}} This is ok! {{/if}}

{{#if @last}}<hr>{{/if}}
{{/each}}

{{#form new_comment}}
<input type="text" name="body">
{{/form}}

</textarea></form>
<script>
CodeMirror.defineMode("htmlhandlebars", function(config) {
return CodeMirror.multiplexingMode(
CodeMirror.getMode(config, "text/html"),
{open: "{{", close: "}}",
mode: CodeMirror.getMode(config, "handlebars"),
parseDelimiters: true});
});

var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
matchBrackets: true,
mode: "htmlhandlebars"
});
</script>
</script>

<p>Handlebars syntax highlighting for CodeMirror.</p>

<p><strong>MIME types defined:</strong> <code>text/x-handlebars-template</code></p>
</article>
2 changes: 2 additions & 0 deletions mode/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ <h2>Language modes</h2>
<li><a href="go/index.html">Go</a></li>
<li><a href="groovy/index.html">Groovy</a></li>
<li><a href="haml/index.html">HAML</a></li>
<li><a href="handlebars/index.html">Handlebars</a></li>
<li><a href="haskell/index.html">Haskell</a></li>
<li><a href="haxe/index.html">Haxe</a></li>
<li><a href="htmlmixed/index.html">HTML mixed-mode</a></li>
Expand All @@ -77,6 +78,7 @@ <h2>Language modes</h2>
<li><a href="markdown/index.html">Markdown</a> (<a href="gfm/index.html">GitHub-flavour</a>)</li>
<li><a href="mirc/index.html">mIRC</a></li>
<li><a href="modelica/index.html">Modelica</a></li>
<li><a href="mumps/index.html">MUMPS</a></li>
<li><a href="nginx/index.html">Nginx</a></li>
<li><a href="ntriples/index.html">NTriples</a></li>
<li><a href="clike/index.html">Objective C</a></li>
Expand Down
6 changes: 5 additions & 1 deletion mode/javascript/javascript.js
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,11 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
function importSpec(type, value) {
if (type == "{") return contCommasep(importSpec, "}");
if (type == "variable") register(value);
return cont();
if (value == "*") cx.marked = "keyword";
return cont(maybeAs);
}
function maybeAs(_type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
}
function maybeFrom(_type, value) {
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
Expand Down
28 changes: 15 additions & 13 deletions mode/markdown/markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
, ulRE = /^[*\-+]\s+/
, olRE = /^[0-9]+\.\s+/
, taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE
, atxHeaderRE = /^#+/
, atxHeaderRE = /^#+ ?/
, setextHeaderRE = /^(?:\={1,}|-{1,})$/
, textRE = /^[^#!\[\]*_\\<>` "'(~]+/;

Expand Down Expand Up @@ -116,18 +116,20 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {

var sol = stream.sol();

var prevLineIsList = (state.list !== false);
if (state.list !== false && state.indentationDiff >= 0) { // Continued list
if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block
state.indentation -= state.indentationDiff;
var prevLineIsList = state.list !== false;
if (prevLineIsList) {
if (state.indentationDiff >= 0) { // Continued list
if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block
state.indentation -= state.indentationDiff;
}
state.list = null;
} else if (state.indentation > 0) {
state.list = null;
state.listDepth = Math.floor(state.indentation / 4);
} else { // No longer a list
state.list = false;
state.listDepth = 0;
}
state.list = null;
} else if (state.list !== false && state.indentation > 0) {
state.list = null;
state.listDepth = Math.floor(state.indentation / 4);
} else if (state.list !== false) { // No longer a list
state.list = false;
state.listDepth = 0;
}

var match = null;
Expand All @@ -138,7 +140,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
} else if (stream.eatSpace()) {
return null;
} else if (match = stream.match(atxHeaderRE)) {
state.header = match[0].length <= 6 ? match[0].length : 6;
state.header = Math.min(6, match[0].indexOf(" ") !== -1 ? match[0].length - 1 : match[0].length);
if (modeCfg.highlightFormatting) state.formatting = "header";
state.f = state.inline;
return getType(state);
Expand Down
2 changes: 1 addition & 1 deletion mode/markdown/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"[comment&formatting&formatting-code ``][comment foo ` bar][comment&formatting&formatting-code ``]");

FT("formatting_atxHeader",
"[header&header-1&formatting&formatting-header&formatting-header-1 #][header&header-1 foo # bar ][header&header-1&formatting&formatting-header&formatting-header-1 #]");
"[header&header-1&formatting&formatting-header&formatting-header-1 # ][header&header-1 foo # bar ][header&header-1&formatting&formatting-header&formatting-header-1 #]");

FT("formatting_setextHeader",
"foo",
Expand Down
1 change: 1 addition & 0 deletions mode/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
{name: "mIRC", mime: "text/mirc", mode: "mirc"},
{name: "MariaDB SQL", mime: "text/x-mariadb", mode: "sql"},
{name: "Modelica", mime: "text/x-modelica", mode: "modelica", ext: ["mo"]},
{name: "MUMPS", mime: "text/x-mumps", mode: "mumps"},
{name: "MS SQL", mime: "text/x-mssql", mode: "sql"},
{name: "MySQL", mime: "text/x-mysql", mode: "sql"},
{name: "Nginx", mime: "text/x-nginx-conf", mode: "nginx", file: /nginx.*\.conf$/i},
Expand Down
85 changes: 85 additions & 0 deletions mode/mumps/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<!doctype html>

<title>CodeMirror: MUMPS 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="mumps.js"></script>
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<div id=nav>
<a href="http://codemirror.net"><h1>CodeMirror</h1><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/codemirror/codemirror">Code</a>
</ul>
<ul>
<li><a href="../index.html">Language modes</a>
<li><a class=active href="#">MUMPS</a>
</ul>
</div>

<article>
<h2>MUMPS mode</h2>


<div><textarea id="code" name="code">
; Lloyd Milligan
; 03-30-2015
;
; MUMPS support for Code Mirror - Excerpts below from routine ^XUS
;
CHECKAV(X1) ;Check A/V code return DUZ or Zero. (Called from XUSRB)
N %,%1,X,Y,IEN,DA,DIK
S IEN=0
;Start CCOW
I $E(X1,1,7)="~~TOK~~" D Q:IEN>0 IEN
. I $E(X1,8,9)="~1" S IEN=$$CHKASH^XUSRB4($E(X1,8,255))
. I $E(X1,8,9)="~2" S IEN=$$CHKCCOW^XUSRB4($E(X1,8,255))
. Q
;End CCOW
S X1=$$UP(X1) S:X1[":" XUTT=1,X1=$TR(X1,":")
S X=$P(X1,";") Q:X="^" -1 S:XUF %1="Access: "_X
Q:X'?1.20ANP 0
S X=$$EN^XUSHSH(X) I '$D(^VA(200,"A",X)) D LBAV Q 0
S %1="",IEN=$O(^VA(200,"A",X,0)),XUF(.3)=IEN D USER(IEN)
S X=$P(X1,";",2) S:XUF %1="Verify: "_X S X=$$EN^XUSHSH(X)
I $P(XUSER(1),"^",2)'=X D LBAV Q 0
I $G(XUFAC(1)) S DIK="^XUSEC(4,",DA=XUFAC(1) D ^DIK
Q IEN
;
; Spell out commands
;
SET2() ;EF. Return error code (also called from XUSRB)
NEW %,X
SET XUNOW=$$HTFM^XLFDT($H),DT=$P(XUNOW,".")
KILL DUZ,XUSER
SET (DUZ,DUZ(2))=0,(DUZ(0),DUZ("AG"),XUSER(0),XUSER(1),XUTT,%UCI)=""
SET %=$$INHIBIT^XUSRB() IF %>0 QUIT %
SET X=$G(^%ZIS(1,XUDEV,"XUS")),XU1=$G(^(1))
IF $L(X) FOR I=1:1:15 IF $L($P(X,U,I)) SET $P(XOPT,U,I)=$P(X,U,I)
SET DTIME=600
IF '$P(XOPT,U,11),$D(^%ZIS(1,XUDEV,90)),^(90)>2800000,^(90)'>DT QUIT 8
QUIT 0
;
; Spell out commands and functions
;
IF $PIECE(XUSER(0),U,11),$PIECE(XUSER(0),U,11)'>DT QUIT 11 ;Terminated
IF $DATA(DUZ("ASH")) QUIT 0 ;If auto handle, Allow to sign-on p434
IF $PIECE(XUSER(0),U,7) QUIT 5 ;Disuser flag set
IF '$LENGTH($PIECE(XUSER(1),U,2)) QUIT 21 ;p419, p434
Q 0
;
</textarea>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
mode: "mumps",
lineNumbers: true,
lineWrapping: true
});
</script>

</article>
148 changes: 148 additions & 0 deletions mode/mumps/mumps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

/*
This MUMPS Language script was constructed using vbscript.js as a template.
*/

(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";

CodeMirror.defineMode("mumps", function() {
function wordRegexp(words) {
return new RegExp("^((" + words.join(")|(") + "))\\b", "i");
}

var singleOperators = new RegExp("^[\\+\\-\\*/&#!_?\\\\<>=\\'\\[\\]]");
var doubleOperators = new RegExp("^(('=)|(<=)|(>=)|('>)|('<)|([[)|(]])|(^$))");
var singleDelimiters = new RegExp("^[\\.,:]");
var brackets = new RegExp("[()]");
var identifiers = new RegExp("^[%A-Za-z][A-Za-z0-9]*");
var commandKeywords = ["break","close","do","else","for","goto", "halt", "hang", "if", "job","kill","lock","merge","new","open", "quit", "read", "set", "tcommit", "trollback", "tstart", "use", "view", "write", "xecute", "b","c","d","e","f","g", "h", "i", "j","k","l","m","n","o", "q", "r", "s", "tc", "tro", "ts", "u", "v", "w", "x"];
// The following list includes instrinsic functions _and_ special variables
var intrinsicFuncsWords = ["\\$ascii", "\\$char", "\\$data", "\\$ecode", "\\$estack", "\\$etrap", "\\$extract", "\\$find", "\\$fnumber", "\\$get", "\\$horolog", "\\$io", "\\$increment", "\\$job", "\\$justify", "\\$length", "\\$name", "\\$next", "\\$order", "\\$piece", "\\$qlength", "\\$qsubscript", "\\$query", "\\$quit", "\\$random", "\\$reverse", "\\$select", "\\$stack", "\\$test", "\\$text", "\\$translate", "\\$view", "\\$x", "\\$y", "\\$a", "\\$c", "\\$d", "\\$e", "\\$ec", "\\$es", "\\$et", "\\$f", "\\$fn", "\\$g", "\\$h", "\\$i", "\\$j", "\\$l", "\\$n", "\\$na", "\\$o", "\\$p", "\\$q", "\\$ql", "\\$qs", "\\$r", "\\$re", "\\$s", "\\$st", "\\$t", "\\$tr", "\\$v", "\\$z"];
var intrinsicFuncs = wordRegexp(intrinsicFuncsWords);
var command = wordRegexp(commandKeywords);

function tokenBase(stream, state) {
if (stream.sol()) {
state.label = true;
state.commandMode = 0;
}

// The <space> character has meaning in MUMPS. Ignoring consecutive
// spaces would interfere with interpreting whether the next non-space
// character belongs to the command or argument context.

// Examine each character and update a mode variable whose interpretation is:
// >0 => command 0 => argument <0 => command post-conditional
var ch = stream.peek();

if (ch == " " || ch == "\t") { // Pre-process <space>
state.label = false;
if (state.commandMode == 0)
state.commandMode = 1;
else if ((state.commandMode < 0) || (state.commandMode == 2))
state.commandMode = 0;
} else if ((ch != ".") && (state.commandMode > 0)) {
if (ch == ":")
state.commandMode = -1; // SIS - Command post-conditional
else
state.commandMode = 2;
}

// Do not color parameter list as line tag
if ((ch === "(") || (ch === "\u0009"))
state.label = false;

// MUMPS comment starts with ";"
if (ch === ";") {
stream.skipToEnd();
return "comment";
}

// Number Literals // SIS/RLM - MUMPS permits canonic number followed by concatenate operator
if (stream.match(/^[-+]?\d+(\.\d+)?([eE][-+]?\d+)?/))
return "number";

// Handle Strings
if (ch == '"') {
if (stream.skipTo('"')) {
stream.next();
return "string";
} else {
stream.skipToEnd();
return "error";
}
}

// Handle operators and Delimiters
if (stream.match(doubleOperators) || stream.match(singleOperators))
return "operator";

// Prevents leading "." in DO block from falling through to error
if (stream.match(singleDelimiters))
return null;

if (brackets.test(ch)) {
stream.next();
return "bracket";
}

if (state.commandMode > 0 && stream.match(command))
return "variable-2";

if (stream.match(intrinsicFuncs))
return "builtin";

if (stream.match(identifiers))
return "variable";

// Detect dollar-sign when not a documented intrinsic function
// "^" may introduce a GVN or SSVN - Color same as function
if (ch === "$" || ch === "^") {
stream.next();
return "builtin";
}

// MUMPS Indirection
if (ch === "@") {
stream.next();
return "string-2";
}

if (/[\w%]/.test(ch)) {
stream.eatWhile(/[\w%]/);
return "variable";
}

// Handle non-detected items
stream.next();
return "error";
}

return {
startState: function() {
return {
label: false,
commandMode: 0
};
},

token: function(stream, state) {
var style = tokenBase(stream, state);
if (state.label) return "tag";
return style;
}
};
});

CodeMirror.defineMIME("text/x-mumps", "mumps");
});
6 changes: 2 additions & 4 deletions mode/python/python.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,13 @@
if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters))
return null;

if (stream.match(doubleOperators)
|| stream.match(singleOperators)
|| stream.match(wordOperators))
if (stream.match(doubleOperators) || stream.match(singleOperators))
return "operator";

if (stream.match(singleDelimiters))
return null;

if (stream.match(keywords))
if (stream.match(keywords) || stream.match(wordOperators))
return "keyword";

if (stream.match(builtins))
Expand Down
23 changes: 17 additions & 6 deletions mode/smarty/smarty.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,27 @@
}

function tokenTop(stream, state) {
if (stream.match(leftDelimiter, true)) {
var string = stream.string;
for (var scan = stream.pos;;) {
var nextMatch = string.indexOf(leftDelimiter, scan);
scan = nextMatch + leftDelimiter.length;
if (nextMatch == -1 || !doesNotCount(stream, nextMatch + leftDelimiter.length)) break;
}
if (nextMatch == stream.pos) {
stream.match(leftDelimiter);
if (stream.eat("*")) {
return chain(stream, state, tokenBlock("comment", "*" + rightDelimiter));
} else if (!doesNotCount(stream)) {
} else {
state.depth++;
state.tokenize = tokenSmarty;
last = "startTag";
return "tag";
}
}

if (nextMatch > -1) stream.string = string.slice(0, nextMatch);
var token = baseMode.token(stream, state.base);
var text = stream.current();
var found = text.indexOf(leftDelimiter);
if (found > -1 && !doesNotCount(stream, stream.start + found + 1))
stream.backUp(text.length - found);
if (nextMatch > -1) stream.string = string;
return token;
}

Expand Down Expand Up @@ -205,6 +210,12 @@
state.last = last;
return style;
},
indent: function(state, text) {
if (state.tokenize == tokenTop && baseMode.indent)
return baseMode.indent(state.base, text);
else
return CodeMirror.Pass;
},
blockCommentStart: leftDelimiter + "*",
blockCommentEnd: "*" + rightDelimiter
};
Expand Down
6 changes: 3 additions & 3 deletions mode/soy/soy.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
else state.indent -= (stream.current() == "/}" || indentingTags.indexOf(state.tag) == -1 ? 2 : 1) * config.indentUnit;
state.soyState.pop();
return "keyword";
} else if (stream.match(/^(\w+)(?==)/)) {
} else if (stream.match(/^([\w?]+)(?==)/)) {
if (stream.current() == "kind" && (match = stream.match(/^="([^"]+)/, false))) {
var kind = match[1];
state.kind.push(kind);
Expand Down Expand Up @@ -134,15 +134,15 @@
return "comment";
} else if (stream.match(stream.sol() ? /^\s*\/\/.*/ : /^\s+\/\/.*/)) {
return "comment";
} else if (stream.match(/^\{\$\w*/)) {
} else if (stream.match(/^\{\$[\w?]*/)) {
state.indent += 2 * config.indentUnit;
state.soyState.push("variable");
return "variable-2";
} else if (stream.match(/^\{literal}/)) {
state.indent += config.indentUnit;
state.soyState.push("literal");
return "keyword";
} else if (match = stream.match(/^\{([\/@\\]?\w*)/)) {
} else if (match = stream.match(/^\{([\/@\\]?[\w?]*)/)) {
if (match[1] != "/switch")
state.indent += (/^(\/|(else|elseif|case|default)$)/.test(match[1]) && state.tag != "switch" ? 1 : 2) * config.indentUnit;
state.tag = match[1];
Expand Down
8 changes: 4 additions & 4 deletions mode/sql/sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
CodeMirror.defineMIME("text/x-mssql", {
name: "sql",
client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"),
keywords: set(sqlKeywords + "begin trigger proc view index for add constraint key primary foreign collate clustered nonclustered"),
keywords: set(sqlKeywords + "begin trigger proc view index for add constraint key primary foreign collate clustered nonclustered declare"),
builtin: set("bigint numeric bit smallint decimal smallmoney int tinyint money float real char varchar text nchar nvarchar ntext binary varbinary image cursor timestamp hierarchyid uniqueidentifier sql_variant xml table "),
atoms: set("false true null unknown"),
operatorChars: /^[*+\-%<>!=]/,
Expand Down Expand Up @@ -327,9 +327,9 @@ CodeMirror.defineMode("sql", function(config, parserConfig) {
CodeMirror.defineMIME("text/x-cassandra", {
name: "sql",
client: { },
keywords: set("use select from using consistency where limit first reversed first and in insert into values using consistency ttl update set delete truncate begin batch apply create keyspace with columnfamily primary key index on drop alter type add any one quorum all local_quorum each_quorum"),
builtin: set("ascii bigint blob boolean counter decimal double float int text timestamp uuid varchar varint"),
atoms: set("false true"),
keywords: set("add all allow alter and any apply as asc authorize batch begin by clustering columnfamily compact consistency count create custom delete desc distinct drop each_quorum exists filtering from grant if in index insert into key keyspace keyspaces level limit local_one local_quorum modify nan norecursive nosuperuser not of on one order password permission permissions primary quorum rename revoke schema select set storage superuser table three to token truncate ttl two type unlogged update use user users using values where with writetime"),
builtin: set("ascii bigint blob boolean counter decimal double float frozen inet int list map static text timestamp timeuuid tuple uuid varchar varint"),
atoms: set("false true infinity NaN"),
operatorChars: /^[<>=]/,
dateSQL: { },
support: set("commentSlashSlash decimallessFloat"),
Expand Down
11 changes: 8 additions & 3 deletions mode/stylus/stylus.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,12 @@
return pushContext(state, stream, "block", 0);
}
if (type == "variable-name") {
return pushContext(state, stream, "variableName");
if ((stream.indentation() == 0 && startOfLine(stream)) || wordIsBlock(firstWordOfLine(stream))) {
return pushContext(state, stream, "variableName");
}
else {
return pushContext(state, stream, "variableName", 0);
}
}
if (type == "=") {
if (!endOfLine(stream) && !wordIsBlock(firstWordOfLine(stream))) {
Expand Down Expand Up @@ -445,10 +450,10 @@
wordIsTag(firstWordOfLine(stream)))) {
return pushContext(state, stream, "block");
}
if (stream.string.match(/^-?[a-z][\w-\.\[\]\'\"]*\s*=/) ||
if (stream.string.match(/^[\$-]?[a-z][\w-\.\[\]\'\"]*\s*=/) ||
stream.string.match(/^\s*(\(|\)|[0-9])/) ||
stream.string.match(/^\s+[a-z][\w-]*\(/i) ||
stream.string.match(/^\s+-?[a-z]/i)) {
stream.string.match(/^\s+[\$-]?[a-z]/i)) {
return pushContext(state, stream, "block", 0);
}
if (endOfLine(stream)) return pushContext(state, stream, "block");
Expand Down
15 changes: 8 additions & 7 deletions mode/z80/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,18 @@ <h2>Z80 assembly mode</h2>
<div><textarea id="code" name="code">
#include "ti83plus.inc"
#define progStart $9D95
.org progStart-2
.db $BB,$6D
.org progStart-2
.db $BB,$6D

bcall(_ClrLCDFull)
ld HL, 0
ld (PenCol), HL
ld HL, Message
ld hl,0
ld (CurCol),hl
ld hl,Message
bcall(_PutS) ; Displays the string
bcall(_NewLine)
ret
Message:
.db "Hello world!",0
.db "Hello world!",0
</textarea></div>

<script>
Expand All @@ -48,5 +49,5 @@ <h2>Z80 assembly mode</h2>
});
</script>

<p><strong>MIME type defined:</strong> <code>text/x-z80</code>.</p>
<p><strong>MIME types defined:</strong> <code>text/x-z80</code>, <code>text/x-ez80</code>.</p>
</article>
52 changes: 34 additions & 18 deletions mode/z80/z80.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,35 @@

(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";

CodeMirror.defineMode('z80', function() {
var keywords1 = /^(exx?|(ld|cp|in)([di]r?)?|pop|push|ad[cd]|cpl|daa|dec|inc|neg|sbc|sub|and|bit|[cs]cf|x?or|res|set|r[lr]c?a?|r[lr]d|s[lr]a|srl|djnz|nop|rst|[de]i|halt|im|ot[di]r|out[di]?)\b/i;
var keywords2 = /^(call|j[pr]|ret[in]?)\b/i;
var keywords3 = /^b_?(call|jump)\b/i;
CodeMirror.defineMode('z80', function(_config, parserConfig) {
var ez80 = parserConfig.ez80;
var keywords1, keywords2;
if (ez80) {
keywords1 = /^(exx?|(ld|cp)([di]r?)?|[lp]ea|pop|push|ad[cd]|cpl|daa|dec|inc|neg|sbc|sub|and|bit|[cs]cf|x?or|res|set|r[lr]c?a?|r[lr]d|s[lr]a|srl|djnz|nop|[de]i|halt|im|in([di]mr?|ir?|irx|2r?)|ot(dmr?|[id]rx|imr?)|out(0?|[di]r?|[di]2r?)|tst(io)?|slp)(\.([sl]?i)?[sl])?\b/i;
keywords2 = /^(((call|j[pr]|rst|ret[in]?)(\.([sl]?i)?[sl])?)|(rs|st)mix)\b/i;
} else {
keywords1 = /^(exx?|(ld|cp|in)([di]r?)?|pop|push|ad[cd]|cpl|daa|dec|inc|neg|sbc|sub|and|bit|[cs]cf|x?or|res|set|r[lr]c?a?|r[lr]d|s[lr]a|srl|djnz|nop|rst|[de]i|halt|im|ot[di]r|out[di]?)\b/i;
keywords2 = /^(call|j[pr]|ret[in]?|b_?(call|jump))\b/i;
}

var variables1 = /^(af?|bc?|c|de?|e|hl?|l|i[xy]?|r|sp)\b/i;
var variables2 = /^(n?[zc]|p[oe]?|m)\b/i;
var errors = /^([hl][xy]|i[xy][hl]|slia|sll)\b/i;
var numbers = /^([\da-f]+h|[0-7]+o|[01]+b|\d+)\b/i;
var numbers = /^([\da-f]+h|[0-7]+o|[01]+b|\d+d?)\b/i;

return {
startState: function() {
return {context: 0};
return {
context: 0
};
},
token: function(stream, state) {
if (!stream.column())
Expand All @@ -34,29 +43,35 @@ CodeMirror.defineMode('z80', function() {
var w;

if (stream.eatWhile(/\w/)) {
if (ez80 && stream.eat('.')) {
stream.eatWhile(/\w/);
}
w = stream.current();

if (stream.indentation()) {
if (state.context == 1 && variables1.test(w))
return 'variable-2';
if ((state.context == 1 || state.context == 4) && variables1.test(w)) {
state.context = 4;
return 'var2';
}

if (state.context == 2 && variables2.test(w))
return 'variable-3';
if (state.context == 2 && variables2.test(w)) {
state.context = 4;
return 'var3';
}

if (keywords1.test(w)) {
state.context = 1;
return 'keyword';
} else if (keywords2.test(w)) {
state.context = 2;
return 'keyword';
} else if (keywords3.test(w)) {
state.context = 3;
return 'keyword';
} else if (state.context == 4 && numbers.test(w)) {
return 'number';
}

if (errors.test(w))
return 'error';
} else if (numbers.test(w)) {
} else if (stream.match(numbers)) {
return 'number';
} else {
return null;
Expand All @@ -77,7 +92,7 @@ CodeMirror.defineMode('z80', function() {
if (stream.match(/\\?.'/))
return 'number';
} else if (stream.eat('.') || stream.sol() && stream.eat('#')) {
state.context = 4;
state.context = 5;

if (stream.eatWhile(/\w/))
return 'def';
Expand All @@ -96,5 +111,6 @@ CodeMirror.defineMode('z80', function() {
});

CodeMirror.defineMIME("text/x-z80", "z80");
CodeMirror.defineMIME("text/x-ez80", { name: "z80", ez80: true });

});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codemirror",
"version":"5.1.0",
"version":"5.2.0",
"main": "lib/codemirror.js",
"description": "In-browser code editing made bearable",
"licenses": [{"type": "MIT",
Expand Down
Loading