55 changes: 39 additions & 16 deletions addon/merge/merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
constructor: DiffView,
init: function(pane, orig, options) {
this.edit = this.mv.edit;
this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: true}, copyObj(options)));
this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: !this.mv.options.allowEditingOriginals}, copyObj(options)));

this.diff = getDiff(asString(orig), asString(options.value));
this.diffOutOfDate = false;
Expand Down Expand Up @@ -71,7 +71,7 @@
function update(mode) {
if (mode == "full") {
if (dv.svg) clear(dv.svg);
clear(dv.copyButtons);
if (dv.copyButtons) clear(dv.copyButtons);
clearMarks(dv.edit, edit.marked, dv.classes);
clearMarks(dv.orig, orig.marked, dv.classes);
edit.from = edit.to = orig.from = orig.to = 0;
Expand Down Expand Up @@ -257,7 +257,7 @@
var w = dv.gap.offsetWidth;
attrs(dv.svg, "width", w, "height", dv.gap.offsetHeight);
}
clear(dv.copyButtons);
if (dv.copyButtons) clear(dv.copyButtons);

var flip = dv.type == "left";
var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport();
Expand All @@ -279,17 +279,30 @@
"d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z",
"class", dv.classes.connect);
}
var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc",
"CodeMirror-merge-copy"));
copy.title = "Revert chunk";
copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig};
copy.style.top = top + "px";
if (dv.copyButtons) {
var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc",
"CodeMirror-merge-copy"));
var editOriginals = dv.mv.options.allowEditingOriginals;
copy.title = editOriginals ? "Push to left" : "Revert chunk";
copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig};
copy.style.top = top + "px";

if (editOriginals) {
var topReverse = dv.orig.heightAtLine(topEdit, "local") - sTopEdit;
var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc",
"CodeMirror-merge-copy-reverse"));
copyReverse.title = "Push to right";
copyReverse.chunk = {topEdit: topOrig, botEdit: botOrig, topOrig: topEdit, botOrig: botEdit};
copyReverse.style.top = topReverse + "px";
dv.type == "right" ? copyReverse.style.left = "2px" : copyReverse.style.right = "2px";
}
}
});
}

function copyChunk(dv, chunk) {
function copyChunk(dv, to, from, chunk) {
if (dv.diffOutOfDate) return;
dv.edit.replaceRange(dv.orig.getRange(Pos(chunk.topOrig, 0), Pos(chunk.botOrig, 0)),
to.replaceRange(from.getRange(Pos(chunk.topOrig, 0), Pos(chunk.botOrig, 0)),
Pos(chunk.topEdit, 0), Pos(chunk.botEdit, 0));
}

Expand All @@ -298,6 +311,7 @@
var MergeView = CodeMirror.MergeView = function(node, options) {
if (!(this instanceof MergeView)) return new MergeView(node, options);

this.options = options;
var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight;
var hasLeft = origLeft != null, hasRight = origRight != null;
var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0);
Expand All @@ -323,6 +337,7 @@
(hasRight ? rightPane : editPane).className += " CodeMirror-merge-pane-rightmost";

wrap.push(elt("div", null, null, "height: 0; clear: both;"));

var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-merge CodeMirror-merge-" + panes + "pane"));
this.edit = CodeMirror(editPane, copyObj(options));

Expand All @@ -345,12 +360,20 @@
lock.title = "Toggle locked scrolling";
var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap");
CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); });
dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type);
CodeMirror.on(dv.copyButtons, "click", function(e) {
var node = e.target || e.srcElement;
if (node.chunk) copyChunk(dv, node.chunk);
});
var gapElts = [dv.copyButtons, lockWrap];
var gapElts = [lockWrap];
if (dv.mv.options.revertButtons !== false) {
dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type);
CodeMirror.on(dv.copyButtons, "click", function(e) {
var node = e.target || e.srcElement;
if (!node.chunk) return;
if (node.className == "CodeMirror-merge-copy-reverse") {
copyChunk(dv, dv.orig, dv.edit, node.chunk);
return;
}
copyChunk(dv, dv.edit, dv.orig, node.chunk);
});
gapElts.unshift(dv.copyButtons);
}
var svg = document.createElementNS && document.createElementNS(svgNS, "svg");
if (svg && !svg.createSVGRect) svg = null;
dv.svg = svg;
Expand Down
6 changes: 3 additions & 3 deletions addon/search/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
return query;
}
var queryDialog =
'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
'Search: <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
function doSearch(cm, rev) {
var state = getSearchState(cm);
if (state.query) return findNext(cm, rev);
Expand Down Expand Up @@ -106,8 +106,8 @@
});}

var replaceQueryDialog =
'Replace: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
var replacementQueryDialog = 'With: <input type="text" style="width: 10em"/>';
'Replace: <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
var replacementQueryDialog = 'With: <input type="text" style="width: 10em" class="CodeMirror-search-field"/>';
var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
function replace(cm, all) {
if (cm.getOption("readOnly")) return;
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codemirror",
"version":"4.4.0",
"version":"4.5.0",
"main": ["lib/codemirror.js", "lib/codemirror.css"],
"ignore": [
"**/.*",
Expand Down
2 changes: 2 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=4.5.0;f=">4.5</option>
<option value="http://marijnhaverbeke.nl/git/codemirror?a=blob_plain;hb=4.4.0;f=">4.4</option>
<option value="http://marijnhaverbeke.nl/git/codemirror?a=blob_plain;hb=4.3.0;f=">4.3</option>
<option value="http://marijnhaverbeke.nl/git/codemirror?a=blob_plain;hb=4.2.1;f=">4.2</option>
Expand Down Expand Up @@ -152,6 +153,7 @@ <h2>Script compression helper</h2>
<option value="http://codemirror.net/mode/scheme/scheme.js">scheme.js</option>
<option value="http://codemirror.net/mode/shell/shell.js">shell.js</option>
<option value="http://codemirror.net/mode/sieve/sieve.js">sieve.js</option>
<option value="http://codemirror.net/mode/slim/slim.js">slim.js</option>
<option value="http://codemirror.net/mode/smalltalk/smalltalk.js">smalltalk.js</option>
<option value="http://codemirror.net/mode/smarty/smarty.js">smarty.js</option>
<option value="http://codemirror.net/mode/smartymixed/smartymixed.js">smartymixed.js</option>
Expand Down
15 changes: 11 additions & 4 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 4.4.0</span>
<span style="color: #888; font-size: 1rem; position: absolute; right: 0; bottom: 0">version 4.5.0</span>
</h2>

<p>CodeMirror is a code-editor component that can be embedded in
Expand Down Expand Up @@ -838,12 +838,17 @@ <h2>Commands</h2>
<dt class=command id=command_goLineEnd><code><strong>goLineEnd</strong></code><span class=keybinding>Alt-Right (PC), Ctrl-E (Mac)</span></dt>
<dd>Move the cursor to the end of the line.</dd>

<dt class=command id=command_goLineRight><code><strong>goLineRight</strong></code><span class=keybinding>Cmd-Right (Mac)</span></dt>
<dd>Move the cursor to the right side of the visual line it is on.</dd>

<dt class=command id=command_goLineLeft><code><strong>goLineLeft</strong></code><span class=keybinding>Cmd-Left (Mac)</span></dt>
<dd>Move the cursor to the left side of the visual line it is on. If
this line is wrapped, that may not be the start of the line.</dd>

<dt class=command id=command_goLineRight><code><strong>goLineRight</strong></code><span class=keybinding>Cmd-Right (Mac)</span></dt>
<dd>Move the cursor to the right side of the visual line it is on.</dd>
<dt class=command id=command_goLineLeftSmart><code><strong>goLineLeftSmart</strong></code></dt>
<dd>Move the cursor to the left side of the visual line it is
on. If that takes it to the start of the line, behave
like <a href="#command_goLineStartSmart"><code>goLineStartSmart</code></a>.</dd>

<dt class=command id=command_goLineUp><code><strong>goLineUp</strong></code><span class=keybinding>Up, Ctrl-P (Mac)</span></dt>
<dd>Move the cursor up one line.</dd>
Expand Down Expand Up @@ -2627,7 +2632,9 @@ <h2>Addons</h2>
document, which will be shown to the left and right of the
editor in non-editable CodeMirror instances. The merge interface
will highlight changes between the editable document and the
original(s) (<a href="../demo/merge.html">demo</a>).</dd>
original(s), and, unless a <code>revertButtons</code> option
of <code>false</code> is given, show buttons that allow the user
to revert changes (<a href="../demo/merge.html">demo</a>).</dd>

<dt id="addon_tern"><a href="../addon/tern/tern.js"><code>tern/tern.js</code></a></dt>
<dd>Provides integration with
Expand Down
8 changes: 6 additions & 2 deletions doc/realworld.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ <h2>CodeMirror real-world uses</h2>
<li><a href="http://clickhelp.co/">ClickHelp</a> (technical writing tool)</li>
<li><a href="http://codeworld.info/">CodeWorld</a> (Haskell playground)</li>
<li><a href="http://complete-ly.appspot.com/playground/code.playground.html">Complete.ly playground</a></li>
<li><a href="http://www.crossui.com/">CrossUI</a> (cross-platform UI builder)</li>
<li><a href="http://rsnous.com/cruncher/">Cruncher</a> (notepad with calculation features)</li>
<li><a href="https://codeanywhere.com/">Codeanywhere</a> (multi-platform cloud editor)</li>
<li><a href="http://drupal.org/project/cpn">Code per Node</a> (Drupal module)</li>
<li><a href="http://www.codebugapp.com/">Codebug</a> (PHP Xdebug front-end)</li>
<li><a href="https://github.com/angelozerr/CodeMirror-Eclipse">CodeMirror Eclipse</a> (embed CM in Eclipse)</li>
Expand All @@ -59,6 +58,8 @@ <h2>CodeMirror real-world uses</h2>
<li><a href="http://www.communitycodecamp.com/">Community Code Camp</a> (code snippet sharing)</li>
<li><a href="http://www.compilejava.net/">compilejava.net</a> (online Java sandbox)</li>
<li><a href="http://www.ckwnc.com/">CKWNC</a> (UML editor)</li>
<li><a href="http://www.crossui.com/">CrossUI</a> (cross-platform UI builder)</li>
<li><a href="http://rsnous.com/cruncher/">Cruncher</a> (notepad with calculation features)</li>
<li><a href="http://www.crudzilla.com/">Crudzilla</a> (self-hosted web IDE)</li>
<li><a href="http://cssdeck.com/">CSSDeck</a> (CSS showcase)</li>
<li><a href="http://ireneros.com/deck/deck.js-codemirror/introduction/#textarea-code">Deck.js integration</a> (slides with editors)</li>
Expand All @@ -72,6 +73,7 @@ <h2>CodeMirror real-world uses</h2>
<li><a href="http://www.fastfig.com/">Fastfig</a> (online computation/math tool)</li>
<li><a href="https://metacpan.org/module/Farabi">Farabi</a> (modern Perl IDE)</li>
<li><a href="http://blog.pamelafox.org/2012/02/interactive-html5-slides-with-fathomjs.html">FathomJS integration</a> (slides with editors, again)</li>
<li><a href="https://phantomus.com/">Phantomus</a> (blogging platform)</li>
<li><a href="http://fiddlesalad.com/">Fiddle Salad</a> (web development environment)</li>
<li><a href="https://github.com/simogeo/Filemanager">Filemanager</a></li>
<li><a href="https://hacks.mozilla.org/2013/11/firefox-developer-tools-episode-27-edit-as-html-codemirror-more/">Firefox Developer Tools</a></li>
Expand All @@ -87,6 +89,7 @@ <h2>CodeMirror real-world uses</h2>
<li><a href="http://haxpad.com/">HaxPad</a> (editor for Win RT)</li>
<li><a href="http://megafonweblab.github.com/histone-javascript/">Histone template engine playground</a></li>
<li><a href="http://icecoder.net">ICEcoder</a> (web IDE)</li>
<li><a href="http://ipython.org/">IPython</a> (interactive computing shell)</li>
<li><a href="http://i-mos.org/imos/">i-MOS</a> (modeling and simulation platform)</li>
<li><a href="http://www.janvas.com/">Janvas</a> (vector graphics editor)</li>
<li><a href="http://extensions.joomla.org/extensions/edition/editors/8723">Joomla plugin</a></li>
Expand Down Expand Up @@ -142,6 +145,7 @@ <h2>CodeMirror real-world uses</h2>
<li><a href="http://uicod.com/">uiCod</a> (animation demo gallery and sharing)</li>
<li><a href="http://cruise.eecs.uottawa.ca/umpleonline/">UmpleOnline</a> (model-oriented programming tool)</li>
<li><a href="https://upsource.jetbrains.com/#idea/view/923f30395f2603cd9f42a32bcafd13b6c28de0ff/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/style/ReplaceAbstractClassInstanceByMapIntention.java">Upsource</a> (code viewer)</li>
<li><a href="http://wamer.net/">Wamer</a> (web application builder)</li>
<li><a href="https://github.com/brettz9/webappfind">webappfind</a> (windows file bindings for webapps)</li>
<li><a href="http://www.webglacademy.com/">WebGL academy</a> (learning WebGL)</li>
<li><a href="http://webglplayground.net/">WebGL playground</a></li>
Expand Down
10 changes: 10 additions & 0 deletions doc/releases.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ <h2>Release notes and version history</h2>

<h2 id="v4">Version 4.x</h2>

<p class="rel">21-08-2014: <a href="http://codemirror.net/codemirror-4.5.zip">Version 4.5</a>:</p>

<ul class="rel-note">
<li>Fix several serious bugs with horizontal scrolling</li>
<li>New mode: <a href="../mode/slim/index.html">Slim</a></li>
<li>New command: <a href="manual.html#command_goLineLeftSmart"><code>goLineLeftSmart</code></a></li>
<li>More fixes and extensions for the <a href="../demo/vim.html">Vim</a> visual block mode</li>
<li>Full <a href="https://github.com/marijnh/CodeMirror/compare/4.4.0...4.5.0">list of patches</a>.</li>
</ul>

<p class="rel">21-07-2014: <a href="http://codemirror.net/codemirror-4.4.zip">Version 4.4</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 @@ -85,7 +85,7 @@ <h2>This is CodeMirror</h2>
</script>
<div style="position: relative; margin: 1em 0;">
<a class="bigbutton left" href="http://codemirror.net/codemirror.zip">DOWNLOAD LATEST RELEASE</a>
<div><strong>version 4.4</strong> (<a href="doc/releases.html">Release notes</a>)</div>
<div><strong>version 4.5</strong> (<a href="doc/releases.html">Release notes</a>)</div>
<div>or use the <a href="doc/compress.html">minification helper</a></div>
<div style="position: absolute; top: 0; right: 0; text-align: right">
<span class="bigbutton right" onclick="document.getElementById('paypal').submit();">DONATE WITH PAYPAL</span>
Expand Down Expand Up @@ -185,7 +185,7 @@ <h2>Browser support</h2>
in <em>standards mode</em> (HTML5 <code>&lt;!doctype html></code>
recommended) are supported:</p>
<table style="margin-bottom: 1em">
<tr><th>Firefox</th><td>version 3 and up</td></tr>
<tr><th>Firefox</th><td>version 4 and up</td></tr>
<tr><th>Chrome</th><td>any version</td></tr>
<tr><th>Safari</th><td>version 5.2 and up</td></tr>
<tr><th style="padding-right: 1em;">Internet Explorer</th><td>version 8 and up</td></tr>
Expand Down
9 changes: 6 additions & 3 deletions keymap/sublime.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
var map = CodeMirror.keyMap.sublime = {fallthrough: "default"};
var cmds = CodeMirror.commands;
var Pos = CodeMirror.Pos;
var ctrl = CodeMirror.keyMap["default"] == CodeMirror.keyMap.pcDefault ? "Ctrl-" : "Cmd-";
var mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault;
var ctrl = mac ? "Cmd-" : "Ctrl-";

// This is not exactly Sublime's algorithm. I couldn't make heads or tails of that.
function findPosSubword(doc, start, dir) {
Expand Down Expand Up @@ -186,7 +187,9 @@
});
};

cmds[map["Shift-" + ctrl + "Up"] = "swapLineUp"] = function(cm) {
var swapLineCombo = mac ? "Cmd-Ctrl-" : "Shift-Ctrl-";

cmds[map[swapLineCombo + "Up"] = "swapLineUp"] = function(cm) {
var ranges = cm.listSelections(), linesToMove = [], at = cm.firstLine() - 1, newSels = [];
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i], from = range.from().line - 1, to = range.to().line;
Expand All @@ -212,7 +215,7 @@
});
};

cmds[map["Shift-" + ctrl + "Down"] = "swapLineDown"] = function(cm) {
cmds[map[swapLineCombo + "Down"] = "swapLineDown"] = function(cm) {
var ranges = cm.listSelections(), linesToMove = [], at = cm.lastLine() + 1;
for (var i = ranges.length - 1; i >= 0; i--) {
var range = ranges[i], from = range.to().line + 1, to = range.from().line;
Expand Down
423 changes: 333 additions & 90 deletions keymap/vim.js

Large diffs are not rendered by default.

124 changes: 77 additions & 47 deletions lib/codemirror.js
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,7 @@
function updateDisplaySimple(cm, viewport) {
var update = new DisplayUpdate(cm, viewport);
if (updateDisplayIfNeeded(cm, update)) {
updateHeightsInViewport(cm);
postUpdateDisplay(cm, update);
var barMeasure = measureForScrollbars(cm);
updateSelection(cm);
Expand Down Expand Up @@ -1280,8 +1281,7 @@
return result;
}

function updateSelection(cm, drawn) {
if (!drawn) drawn = drawSelection(cm);
function showSelection(cm, drawn) {
removeChildrenAndAdd(cm.display.cursorDiv, drawn.cursors);
removeChildrenAndAdd(cm.display.selectionDiv, drawn.selection);
if (drawn.teTop != null) {
Expand All @@ -1290,6 +1290,10 @@
}
}

function updateSelection(cm) {
showSelection(cm, drawSelection(cm));
}

// Draws a cursor for the given range
function drawSelectionCursor(cm, range, output) {
var pos = cursorCoords(cm, range.head, "div", null, null, !cm.options.singleCursorHeightPerLine);
Expand Down Expand Up @@ -1642,18 +1646,24 @@

var rect;
if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.
while (start && isExtendingChar(prepared.line.text.charAt(mStart + start))) --start;
while (mStart + end < mEnd && isExtendingChar(prepared.line.text.charAt(mStart + end))) ++end;
if (ie && ie_version < 9 && start == 0 && end == mEnd - mStart) {
rect = node.parentNode.getBoundingClientRect();
} else if (ie && cm.options.lineWrapping) {
var rects = range(node, start, end).getClientRects();
if (rects.length)
rect = rects[bias == "right" ? rects.length - 1 : 0];
else
rect = nullRect;
} else {
rect = range(node, start, end).getBoundingClientRect() || nullRect;
for (;;) {
while (start && isExtendingChar(prepared.line.text.charAt(mStart + start))) --start;
while (mStart + end < mEnd && isExtendingChar(prepared.line.text.charAt(mStart + end))) ++end;
if (ie && ie_version < 9 && start == 0 && end == mEnd - mStart) {
rect = node.parentNode.getBoundingClientRect();
} else if (ie && cm.options.lineWrapping) {
var rects = range(node, start, end).getClientRects();
if (rects.length)
rect = rects[bias == "right" ? rects.length - 1 : 0];
else
rect = nullRect;
} else {
rect = range(node, start, end).getBoundingClientRect() || nullRect;
}
if (rect.left || rect.right || start == 0) break;
end = start;
start = start - 1;
collapse = "right";
}
} else { // If it is a widget, simply get the box for the whole widget.
if (start > 0) collapse = bias = "right";
Expand Down Expand Up @@ -2029,16 +2039,17 @@
var cm = op.cm, display = cm.display;
if (op.updatedDisplay) updateHeightsInViewport(cm);

op.barMeasure = measureForScrollbars(cm);

// If the max line changed since it was last measured, measure it,
// and ensure the document's width matches it.
// updateDisplayIfNeeded will use these properties to do the actual resizing
// updateDisplay_W2 will use these properties to do the actual resizing
if (display.maxLineChanged && !cm.options.lineWrapping) {
op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left;
op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3;
op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo +
scrollerCutOff - display.scroller.clientWidth);
}

op.barMeasure = measureForScrollbars(cm);
if (op.updatedDisplay || op.selectionChanged)
op.newSelectionNodes = drawSelection(cm);
}
Expand All @@ -2050,10 +2061,11 @@
cm.display.sizer.style.minWidth = op.adjustWidthTo + "px";
if (op.maxScrollLeft < cm.doc.scrollLeft)
setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true);
cm.display.maxLineChanged = false;
}

if (op.newSelectionNodes)
updateSelection(cm, op.newSelectionNodes);
showSelection(cm, op.newSelectionNodes);
if (op.updatedDisplay)
setDocumentHeight(cm, op.barMeasure);
if (op.updatedDisplay || op.startHeight != cm.doc.height)
Expand All @@ -2068,6 +2080,9 @@
function endOperation_finish(op) {
var cm = op.cm, display = cm.display, doc = cm.doc;

if (op.adjustWidthTo != null && Math.abs(op.barMeasure.scrollWidth - cm.display.scroller.scrollWidth) > 1)
updateScrollbars(cm);

if (op.updatedDisplay) postUpdateDisplay(cm, op.update);

// Abort mouse wheel delta measurement, when scrolling explicitly
Expand Down Expand Up @@ -3553,7 +3568,7 @@

var after = i ? computeSelAfterChange(doc, change) : lst(source);
makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
if (!i && doc.cm) doc.cm.scrollIntoView(change);
if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)});
var rebased = [];

// Propagate to the linked documents
Expand Down Expand Up @@ -3742,6 +3757,7 @@
if (y1 < 0) y1 = 0;
var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop;
var screen = display.scroller.clientHeight - scrollerCutOff, result = {};
if (y2 - y1 > screen) y2 = y1 + screen;
var docBottom = cm.doc.height + paddingVert(display);
var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin;
if (y1 < screentop) {
Expand All @@ -3752,16 +3768,16 @@
}

var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft;
var screenw = display.scroller.clientWidth - scrollerCutOff;
x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth;
var gutterw = display.gutters.offsetWidth;
var atLeft = x1 < gutterw + 10;
if (x1 < screenleft + gutterw || atLeft) {
if (atLeft) x1 = 0;
result.scrollLeft = Math.max(0, x1 - 10 - gutterw);
} else if (x2 > screenw + screenleft - 3) {
result.scrollLeft = x2 + 10 - screenw;
}
var screenw = display.scroller.clientWidth - scrollerCutOff - display.gutters.offsetWidth;
var tooWide = x2 - x1 > screenw;
if (tooWide) x2 = y1 + screen;
if (x1 < 10)
result.scrollLeft = 0;
else if (x1 < screenleft)
result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10));
else if (x2 > screenw + screenleft - 3)
result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw;

return result;
}

Expand Down Expand Up @@ -4070,11 +4086,14 @@
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
if (!range.empty()) {
var start = Math.max(end, range.from().line);
var to = range.to();
var from = range.from(), to = range.to();
var start = Math.max(end, from.line);
end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1;
for (var j = start; j < end; ++j)
indentLine(this, j, how);
var newRanges = this.doc.sel.ranges;
if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0)
replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll);
} else if (range.head.line > end) {
indentLine(this, range.head.line, how, true);
end = range.head.line;
Expand Down Expand Up @@ -4470,7 +4489,7 @@
clearCaches(cm);
regChange(cm);
}, true);
option("specialChars", /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\ufeff]/g, function(cm, val) {
option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val) {
cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
cm.refresh();
}, true);
Expand Down Expand Up @@ -4731,15 +4750,7 @@
},
goLineStartSmart: function(cm) {
cm.extendSelectionsBy(function(range) {
var start = lineStart(cm, range.head.line);
var line = cm.getLineHandle(start.line);
var order = getOrder(line);
if (!order || order[0].level == 0) {
var firstNonWS = Math.max(0, line.text.search(/\S/));
var inWS = range.head.line == start.line && range.head.ch <= firstNonWS && range.head.ch;
return Pos(start.line, inWS ? 0 : firstNonWS);
}
return start;
return lineStartSmart(cm, range.head);
}, {origin: "+move", bias: 1});
},
goLineEnd: function(cm) {
Expand All @@ -4758,6 +4769,14 @@
return cm.coordsChar({left: 0, top: top}, "div");
}, sel_move);
},
goLineLeftSmart: function(cm) {
cm.extendSelectionsBy(function(range) {
var top = cm.charCoords(range.head, "div").top + 5;
var pos = cm.coordsChar({left: 0, top: top}, "div");
if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head);
return pos;
}, sel_move);
},
goLineUp: function(cm) {cm.moveV(-1, "line");},
goLineDown: function(cm) {cm.moveV(1, "line");},
goPageUp: function(cm) {cm.moveV(-1, "page");},
Expand Down Expand Up @@ -6495,7 +6514,7 @@
},
changeGeneration: function(forceSplit) {
if (forceSplit)
this.history.lastOp = this.history.lastOrigin = null;
this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null;
return this.history.generation;
},
isClean: function (gen) {
Expand Down Expand Up @@ -6812,7 +6831,7 @@
// Used to track when changes can be merged into a single undo
// event
this.lastModTime = this.lastSelTime = 0;
this.lastOp = null;
this.lastOp = this.lastSelOp = null;
this.lastOrigin = this.lastSelOrigin = null;
// Used by the isClean() method
this.generation = this.maxGeneration = startGen || 1;
Expand Down Expand Up @@ -6890,7 +6909,7 @@
hist.done.push(selAfter);
hist.generation = ++hist.maxGeneration;
hist.lastModTime = hist.lastSelTime = time;
hist.lastOp = opId;
hist.lastOp = hist.lastSelOp = opId;
hist.lastOrigin = hist.lastSelOrigin = change.origin;

if (!last) signal(doc, "historyAdded");
Expand All @@ -6916,7 +6935,7 @@
// the current, or the origins don't allow matching. Origins
// starting with * are always merged, those starting with + are
// merged when similar and close together in time.
if (opId == hist.lastOp ||
if (opId == hist.lastSelOp ||
(origin && hist.lastSelOrigin == origin &&
(hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin ||
selectionEventCanBeMerged(doc, origin, lst(hist.done), sel))))
Expand All @@ -6926,7 +6945,7 @@

hist.lastSelTime = +new Date;
hist.lastSelOrigin = origin;
hist.lastOp = opId;
hist.lastSelOp = opId;
if (options && options.clearRedo !== false)
clearSelectionEvents(hist.undone);
}
Expand Down Expand Up @@ -7556,6 +7575,17 @@
var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line);
return Pos(lineN == null ? lineNo(line) : lineN, ch);
}
function lineStartSmart(cm, pos) {
var start = lineStart(cm, pos.line);
var line = getLine(cm.doc, start.line);
var order = getOrder(line);
if (!order || order[0].level == 0) {
var firstNonWS = Math.max(0, line.text.search(/\S/));
var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch;
return Pos(start.line, inWS ? 0 : firstNonWS);
}
return start;
}

function compareBidiLevel(order, a, b) {
var linedir = order[0].level;
Expand Down Expand Up @@ -7795,7 +7825,7 @@

// THE END

CodeMirror.version = "4.4.0";
CodeMirror.version = "4.5.0";

return CodeMirror;
});
11 changes: 11 additions & 0 deletions mode/clike/clike.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,4 +437,15 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
modeProps: {fold: ["brace", "include"]}
});

def("text/x-nesc", {
name: "clike",
keywords: words(cKeywords + "as atomic async call command component components configuration event generic " +
"implementation includes interface module new norace nx_struct nx_union post provides " +
"signal task uses abstract extends"),
blockKeywords: words("case do else for if switch while struct"),
atoms: words("null"),
hooks: {"#": cppHook},
modeProps: {fold: ["brace", "include"]}
});

});
2 changes: 1 addition & 1 deletion mode/clojure/clojure.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ CodeMirror.defineMode("clojure", function (options) {
var first = stream.next();
// Read special literals: backspace, newline, space, return.
// Just read all lowercase letters.
if (first.match(/[a-z]/) && stream.match(/[a-z]+/, true)) {
if (first && first.match(/[a-z]/) && stream.match(/[a-z]+/, true)) {
return;
}
// Read unicode character: \u1000 \uA0a1
Expand Down
4 changes: 2 additions & 2 deletions mode/haml/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@
" [comment -# this is a comment]",
" [comment and this is a comment too]",
" Date/Time",
" [operator -] [variable now] [operator =] [tag DateTime][operator .][variable now]",
" [operator -] [variable now] [operator =] [tag DateTime][operator .][property now]",
" [tag %strong=] [variable now]",
" [operator -] [keyword if] [variable now] [operator >] [tag DateTime][operator .][variable parse]([string \"December 31, 2006\"])",
" [operator -] [keyword if] [variable now] [operator >] [tag DateTime][operator .][property parse]([string \"December 31, 2006\"])",
" [operator =][string \"Happy\"]",
" [operator =][string \"Belated\"]",
" [operator =][string \"Birthday\"]");
Expand Down
1 change: 1 addition & 0 deletions mode/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ <h2>Language modes</h2>
<li><a href="css/scss.html">SCSS</a></li>
<li><a href="shell/index.html">Shell</a></li>
<li><a href="sieve/index.html">Sieve</a></li>
<li><a href="slim/index.html">Slim</a></li>
<li><a href="smalltalk/index.html">Smalltalk</a></li>
<li><a href="smarty/index.html">Smarty</a></li>
<li><a href="smartymixed/index.html">Smarty/HTML mixed</a></li>
Expand Down
5 changes: 5 additions & 0 deletions mode/javascript/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ <h2>JavaScript mode</h2>
<li><code>statementIndent</code> which (given a number) will
determine the amount of indentation to use for statements
continued on a new line.</li>
<li><code>wordCharacters</code>, a regexp that indicates which
characters should be considered part of an identifier.
Defaults to <code>/[\w$]/</code>, which does not handle
non-ASCII identifiers. Can be set to something more elaborate
to improve Unicode support.</li>
</ul>
</p>

Expand Down
9 changes: 5 additions & 4 deletions mode/javascript/javascript.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
var jsonldMode = parserConfig.jsonld;
var jsonMode = parserConfig.json || jsonldMode;
var isTS = parserConfig.typescript;
var wordRE = parserConfig.wordCharacters || /[\w$]/;

// Tokenizer

Expand Down Expand Up @@ -132,8 +133,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
} else if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return ret("operator", "operator", stream.current());
} else {
stream.eatWhile(/[\w\$_]/);
} else if (wordRE.test(ch)) {
stream.eatWhile(wordRE);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
ret("variable", "variable", word);
Expand Down Expand Up @@ -202,7 +203,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (--depth == 0) break;
} else if (bracket >= 3 && bracket < 6) {
++depth;
} else if (/[$\w]/.test(ch)) {
} else if (wordRE.test(ch)) {
sawSomething = true;
} else if (sawSomething && !depth) {
++pos;
Expand Down Expand Up @@ -669,7 +670,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
};
});

CodeMirror.registerHelper("wordChars", "javascript", /[\\w$]/);
CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);

CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("text/ecmascript", "javascript");
Expand Down
1 change: 1 addition & 0 deletions mode/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ CodeMirror.modeInfo = [
{name: "SCSS", mime: "text/x-scss", mode: "css"},
{name: "Shell", mime: "text/x-sh", mode: "shell"},
{name: "Sieve", mime: "application/sieve", mode: "sieve"},
{name: "Slim", mime: "text/x-slim", mode: "slim"},
{name: "Smalltalk", mime: "text/x-stsrc", mode: "smalltalk"},
{name: "Smarty", mime: "text/x-smarty", mode: "smarty"},
{name: "SmartyMixed", mime: "text/x-smarty", mode: "smartymixed"},
Expand Down
2 changes: 1 addition & 1 deletion mode/perl/perl.js
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ CodeMirror.defineMode("perl",function(){
return (state.tokenize||tokenPerl)(stream,state);},
electricChars:"{}"};});

CodeMirror.registerHelper("wordChars", "perl", /[\\w$]/);
CodeMirror.registerHelper("wordChars", "perl", /[\w$]/);

CodeMirror.defineMIME("text/x-perl", "perl");

Expand Down
4 changes: 2 additions & 2 deletions mode/php/php.js

Large diffs are not rendered by default.

24 changes: 15 additions & 9 deletions mode/ruby/ruby.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ CodeMirror.defineMode("ruby", function(config) {
} else if (ch == "-" && stream.eat(">")) {
return "arrow";
} else if (/[=+\-\/*:\.^%<>~|]/.test(ch)) {
stream.eatWhile(/[=+\-\/*:\.^%<>~|]/);
var more = stream.eatWhile(/[=+\-\/*:\.^%<>~|]/);
if (ch == "." && !more) curPunc = ".";
return "operator";
} else {
return null;
Expand Down Expand Up @@ -232,20 +233,25 @@ CodeMirror.defineMode("ruby", function(config) {
token: function(stream, state) {
if (stream.sol()) state.indented = stream.indentation();
var style = state.tokenize[state.tokenize.length-1](stream, state), kwtype;
var thisTok = curPunc;
if (style == "ident") {
var word = stream.current();
style = keywords.propertyIsEnumerable(stream.current()) ? "keyword"
style = state.lastTok == "." ? "property"
: keywords.propertyIsEnumerable(stream.current()) ? "keyword"
: /^[A-Z]/.test(word) ? "tag"
: (state.lastTok == "def" || state.lastTok == "class" || state.varList) ? "def"
: "variable";
if (indentWords.propertyIsEnumerable(word)) kwtype = "indent";
else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent";
else if ((word == "if" || word == "unless") && stream.column() == stream.indentation())
kwtype = "indent";
else if (word == "do" && state.context.indented < state.indented)
kwtype = "indent";
if (style == "keyword") {
thisTok = word;
if (indentWords.propertyIsEnumerable(word)) kwtype = "indent";
else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent";
else if ((word == "if" || word == "unless") && stream.column() == stream.indentation())
kwtype = "indent";
else if (word == "do" && state.context.indented < state.indented)
kwtype = "indent";
}
}
if (curPunc || (style && style != "comment")) state.lastTok = word || curPunc || style;
if (curPunc || (style && style != "comment")) state.lastTok = thisTok;
if (curPunc == "|") state.varList = !state.varList;

if (kwtype == "indent" || /[\(\[\{]/.test(curPunc))
Expand Down
201 changes: 91 additions & 110 deletions mode/sass/sass.js

Large diffs are not rendered by default.

96 changes: 96 additions & 0 deletions mode/slim/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<!doctype html>

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

<link rel="stylesheet" href="../../lib/codemirror.css">
<link rel="stylesheet" href="../../theme/ambiance.css">
<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.11.0/jquery-ui.min.js"></script>
<link rel="stylesheet" href="https://code.jquery.com/ui/1.11.0/themes/smoothness/jquery-ui.css">
<script src="../../lib/codemirror.js"></script>
<script src="../xml/xml.js"></script>
<script src="../htmlembedded/htmlembedded.js"></script>
<script src="../htmlmixed/htmlmixed.js"></script>
<script src="../coffeescript/coffeescript.js"></script>
<script src="../javascript/javascript.js"></script>
<script src="../ruby/ruby.js"></script>
<script src="../markdown/markdown.js"></script>
<script src="slim.js"></script>
<style>.CodeMirror {background: #f8f8f8;}</style>
<div id=nav>
<a href="http://codemirror.net"><img id=logo src="../../doc/logo.png"></a>

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

<article>
<h2>SLIM mode</h2>
<form><textarea id="code" name="code">
body
table
- for user in users
td id="user_#{user.id}" class=user.role
a href=user_action(user, :edit) Edit #{user.name}
a href=(path_to_user user) = user.name
body
h1(id="logo") = page_logo
h2[id="tagline" class="small tagline"] = page_tagline

h2[id="tagline"
class="small tagline"] = page_tagline

h1 id = "logo" = page_logo
h2 [ id = "tagline" ] = page_tagline

/ comment
second line
/! html comment
second line
<!-- html comment -->
<a href="#{'hello' if set}">link</a>
a.slim href="work" disabled=false running==:atom Text <b>bold</b>
.clazz data-id="test" == 'hello' unless quark
| Text mode #{12}
Second line
= x ||= :ruby_atom
#menu.left
- @env.each do |x|
li: a = x
*@dyntag attr="val"
.first *{:class => [:second, :third]} Text
.second class=["text","more"]
.third class=:text,:symbol

</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
theme: "ambiance",
mode: "application/x-slim"
});
$('.CodeMirror').resizable({
resize: function() {
editor.setSize($(this).width(), $(this).height());
//editor.refresh();
}
});
</script>

<p><strong>MIME types defined:</strong> <code>application/x-slim</code>.</p>

<p>
<strong>Parsing/Highlighting Tests:</strong>
<a href="../../test/index.html#slim_*">normal</a>,
<a href="../../test/index.html#verbose,slim_*">verbose</a>.
</p>
</article>
575 changes: 575 additions & 0 deletions mode/slim/slim.js

Large diffs are not rendered by default.

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

// Slim Highlighting for CodeMirror copyright (c) HicknHack Software Gmbh

(function() {
var mode = CodeMirror.getMode({tabSize: 4, indentUnit: 2}, "slim");
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }

// Requires at least one media query
MT("elementName",
"[tag h1] Hey There");

MT("oneElementPerLine",
"[tag h1] Hey There .h2");

MT("idShortcut",
"[attribute&def #test] Hey There");

MT("tagWithIdShortcuts",
"[tag h1][attribute&def #test] Hey There");

MT("classShortcut",
"[attribute&qualifier .hello] Hey There");

MT("tagWithIdAndClassShortcuts",
"[tag h1][attribute&def #test][attribute&qualifier .hello] Hey There");

MT("docType",
"[keyword doctype] xml");

MT("comment",
"[comment / Hello WORLD]");

MT("notComment",
"[tag h1] This is not a / comment ");

MT("attributes",
"[tag a]([attribute title]=[string \"test\"]) [attribute href]=[string \"link\"]}");

MT("multiLineAttributes",
"[tag a]([attribute title]=[string \"test\"]",
" ) [attribute href]=[string \"link\"]}");

MT("htmlCode",
"[tag&bracket <][tag h1][tag&bracket >]Title[tag&bracket </][tag h1][tag&bracket >]");

MT("rubyBlock",
"[operator&special =][variable-2 @item]");

MT("selectorRubyBlock",
"[tag a][attribute&qualifier .test][operator&special =] [variable-2 @item]");

MT("nestedRubyBlock",
"[tag a]",
" [operator&special =][variable puts] [string \"test\"]");

MT("multilinePlaintext",
"[tag p]",
" | Hello,",
" World");

MT("multilineRuby",
"[tag p]",
" [comment /# this is a comment]",
" [comment and this is a comment too]",
" | Date/Time",
" [operator&special -] [variable now] [operator =] [tag DateTime][operator .][property now]",
" [tag strong][operator&special =] [variable now]",
" [operator&special -] [keyword if] [variable now] [operator >] [tag DateTime][operator .][property parse]([string \"December 31, 2006\"])",
" [operator&special =][string \"Happy\"]",
" [operator&special =][string \"Belated\"]",
" [operator&special =][string \"Birthday\"]");

MT("multilineComment",
"[comment /]",
" [comment Multiline]",
" [comment Comment]");

MT("hamlAfterRubyTag",
"[attribute&qualifier .block]",
" [tag strong][operator&special =] [variable now]",
" [attribute&qualifier .test]",
" [operator&special =][variable now]",
" [attribute&qualifier .right]");

MT("stretchedRuby",
"[operator&special =] [variable puts] [string \"Hello\"],",
" [string \"World\"]");

MT("interpolationInHashAttribute",
"[tag div]{[attribute id] = [string \"]#{[variable test]}[string _]#{[variable ting]}[string \"]} test");

MT("interpolationInHTMLAttribute",
"[tag div]([attribute title]=[string \"]#{[variable test]}[string _]#{[variable ting]()}[string \"]) Test");
})();
10 changes: 9 additions & 1 deletion mode/sql/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
<link rel="stylesheet" href="../../lib/codemirror.css" />
<script src="../../lib/codemirror.js"></script>
<script src="sql.js"></script>
<link rel="stylesheet" href="../../addon/hint/show-hint.css" />
<script src="../../addon/hint/show-hint.js"></script>
<script src="../../addon/hint/sql-hint.js"></script>
<style>
.CodeMirror {
border-top: 1px solid black;
Expand Down Expand Up @@ -68,7 +71,12 @@ <h2>SQL Mode for CodeMirror</h2>
smartIndent: true,
lineNumbers: true,
matchBrackets : true,
autofocus: true
autofocus: true,
extraKeys: {"Ctrl-Space": "autocomplete"},
hintOptions: {tables: {
users: {name: null, score: null, birthDate: null},
countries: {name: null, population: null, size: null}
}}
});
};
</script>
Expand Down
144 changes: 133 additions & 11 deletions mode/verilog/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
var mode = CodeMirror.getMode({indentUnit: 4}, "verilog");
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }

MT("Binary literals",
MT("binary_literals",
"[number 1'b0]",
"[number 1'b1]",
"[number 1'bx]",
Expand All @@ -30,14 +30,14 @@
"[number 'b0101]"
);

MT("Octal literals",
MT("octal_literals",
"[number 3'o7]",
"[number 3'O7]",
"[number 3'so7]",
"[number 3'SO7]"
);

MT("Decimal literals",
MT("decimal_literals",
"[number 0]",
"[number 1]",
"[number 7]",
Expand All @@ -52,7 +52,7 @@
"[number 32 'd 123]"
);

MT("Hex literals",
MT("hex_literals",
"[number 4'h0]",
"[number 4'ha]",
"[number 4'hF]",
Expand All @@ -69,7 +69,7 @@
"[number 32'hFFF?]"
);

MT("Real number literals",
MT("real_number_literals",
"[number 1.2]",
"[number 0.1]",
"[number 2394.26331]",
Expand All @@ -82,36 +82,158 @@
"[number 236.123_763_e-12]"
);

MT("Operators",
MT("operators",
"[meta ^]"
);

MT("Keywords",
MT("keywords",
"[keyword logic]",
"[keyword logic] [variable foo]",
"[keyword reg] [variable abc]"
);

MT("Variables",
MT("variables",
"[variable _leading_underscore]",
"[variable _if]",
"[number 12] [variable foo]",
"[variable foo] [number 14]"
);

MT("Tick defines",
MT("tick_defines",
"[def `FOO]",
"[def `foo]",
"[def `FOO_bar]"
);

MT("System calls",
MT("system_calls",
"[meta $display]",
"[meta $vpi_printf]"
);

MT("Line comment", "[comment // Hello world]");
MT("line_comment", "[comment // Hello world]");

// Alignment tests
MT("align_port_map_style1",
/**
* mod mod(.a(a),
* .b(b)
* );
*/
"[variable mod] [variable mod][bracket (].[variable a][bracket (][variable a][bracket )],",
" .[variable b][bracket (][variable b][bracket )]",
" [bracket )];",
""
);

MT("align_port_map_style2",
/**
* mod mod(
* .a(a),
* .b(b)
* );
*/
"[variable mod] [variable mod][bracket (]",
" .[variable a][bracket (][variable a][bracket )],",
" .[variable b][bracket (][variable b][bracket )]",
"[bracket )];",
""
);

// Indentation tests
MT("indent_single_statement_if",
"[keyword if] [bracket (][variable foo][bracket )]",
" [keyword break];",
""
);

MT("no_indent_after_single_line_if",
"[keyword if] [bracket (][variable foo][bracket )] [keyword break];",
""
);

MT("indent_after_if_begin_same_line",
"[keyword if] [bracket (][variable foo][bracket )] [keyword begin]",
" [keyword break];",
" [keyword break];",
"[keyword end]",
""
);

MT("indent_after_if_begin_next_line",
"[keyword if] [bracket (][variable foo][bracket )]",
" [keyword begin]",
" [keyword break];",
" [keyword break];",
" [keyword end]",
""
);

MT("indent_single_statement_if_else",
"[keyword if] [bracket (][variable foo][bracket )]",
" [keyword break];",
"[keyword else]",
" [keyword break];",
""
);

MT("indent_if_else_begin_same_line",
"[keyword if] [bracket (][variable foo][bracket )] [keyword begin]",
" [keyword break];",
" [keyword break];",
"[keyword end] [keyword else] [keyword begin]",
" [keyword break];",
" [keyword break];",
"[keyword end]",
""
);

MT("indent_if_else_begin_next_line",
"[keyword if] [bracket (][variable foo][bracket )]",
" [keyword begin]",
" [keyword break];",
" [keyword break];",
" [keyword end]",
"[keyword else]",
" [keyword begin]",
" [keyword break];",
" [keyword break];",
" [keyword end]",
""
);

MT("indent_if_nested_without_begin",
"[keyword if] [bracket (][variable foo][bracket )]",
" [keyword if] [bracket (][variable foo][bracket )]",
" [keyword if] [bracket (][variable foo][bracket )]",
" [keyword break];",
""
);

MT("indent_case",
"[keyword case] [bracket (][variable state][bracket )]",
" [variable FOO]:",
" [keyword break];",
" [variable BAR]:",
" [keyword break];",
"[keyword endcase]",
""
);

MT("unindent_after_end_with_preceding_text",
"[keyword begin]",
" [keyword break]; [keyword end]",
""
);

MT("export_function_does_not_indent",
"[keyword export] [string \"DPI-C\"] [keyword function] [variable helloFromSV];",
""
);

MT("export_task_does_not_indent",
"[keyword export] [string \"DPI-C\"] [keyword task] [variable helloFromSV];",
""
);


})();
5 changes: 5 additions & 0 deletions mode/verilog/verilog.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ CodeMirror.defineMode("verilog", function(config, parserConfig) {
openClose["do" ] = "while";
openClose["fork" ] = "join;join_any;join_none";

// This is a bit of a hack but will work to not indent after import/epxort statements
// as long as the function/task name is on the same line
openClose["import"] = "function;task";
openClose["export"] = "function;task";

for (var i in noIndentKeywords) {
var keyword = noIndentKeywords[i];
if (openClose[keyword]) {
Expand Down
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":"4.4.0",
"version":"4.5.0",
"main": "lib/codemirror.js",
"description": "In-browser code editing made bearable",
"licenses": [{"type": "MIT",
Expand Down
2 changes: 2 additions & 0 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ <h2>Test Suite</h2>
<script src="../mode/haml/test.js"></script>
<script src="../mode/markdown/markdown.js"></script>
<script src="../mode/markdown/test.js"></script>
<script src="../mode/slim/slim.js"></script>
<script src="../mode/slim/test.js"></script>
<script src="../mode/gfm/gfm.js"></script>
<script src="../mode/gfm/test.js"></script>
<script src="../mode/shell/shell.js"></script>
Expand Down
9 changes: 9 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,15 @@ testCM("undoSelectionAsBefore", function(cm) {
eq(cm.getSelection(), "abc");
});

testCM("selectionChangeConfusesHistory", function(cm) {
cm.replaceSelection("abc", null, "dontmerge");
cm.operation(function() {
cm.setCursor(Pos(0, 0));
cm.replaceSelection("abc", null, "dontmerge");
});
eq(cm.historySize().undo, 2);
});

testCM("markTextSingleLine", function(cm) {
forEach([{a: 0, b: 1, c: "", f: 2, t: 5},
{a: 0, b: 4, c: "", f: 0, t: 2},
Expand Down
95 changes: 94 additions & 1 deletion test/vim_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,19 @@ testVim('c_visual_block', function(cm, vim, helpers) {
cm.replaceSelections(replacement);
eq('1hworld\n5hworld\nahworld', cm.getValue());
}, {value: '1234\n5678\nabcdefg'});
testVim('c_visual_block_replay', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('<C-v>', '2', 'j', 'l', 'c');
var replacement = new Array(cm.listSelections().length+1).join('fo ').split(' ');
replacement.pop();
cm.replaceSelections(replacement);
eq('1fo4\n5fo8\nafodefg', cm.getValue());
helpers.doInsertModeKeys('Esc');
cm.setCursor(0, 0);
helpers.doKeys('.');
eq('foo4\nfoo8\nfoodefg', cm.getValue());
}, {value: '1234\n5678\nabcdefg'});

// Swapcase commands edit in place and do not modify registers.
testVim('g~w_repeat', function(cm, vim, helpers) {
// Assert that dw does delete newline if it should go to the next line, and
Expand Down Expand Up @@ -1214,6 +1227,14 @@ testVim('A', function(cm, vim, helpers) {
helpers.assertCursorAt(0, lines[0].length);
eq('vim-insert', cm.getOption('keyMap'));
});
testVim('A_visual_block', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'A');
var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' ');
replacement.pop();
cm.replaceSelections(replacement);
eq('testhello\nmehello\npleahellose', cm.getValue());
}, {value: 'test\nme\nplease'});
testVim('I', function(cm, vim, helpers) {
cm.setCursor(0, 4);
helpers.doKeys('I');
Expand All @@ -1228,6 +1249,14 @@ testVim('I_repeat', function(cm, vim, helpers) {
eq('testtesttestblah', cm.getValue());
helpers.assertCursorAt(0, 11);
}, { value: 'blah' });
testVim('I_visual_block', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'I');
var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' ');
replacement.pop();
cm.replaceSelections(replacement);
eq('hellotest\nhellome\nhelloplease', cm.getValue());
}, {value: 'test\nme\nplease'});
testVim('o', function(cm, vim, helpers) {
cm.setCursor(0, 4);
helpers.doKeys('o');
Expand Down Expand Up @@ -1622,6 +1651,11 @@ testVim('visual', function(cm, vim, helpers) {
helpers.doKeys('d');
eq('15', cm.getValue());
}, { value: '12345' });
testVim('visual_exit', function(cm, vim, helpers) {
helpers.doKeys('<C-v>', 'l', 'j', 'j', '<Esc>');
eq(cm.getCursor('anchor'), cm.getCursor('head'));
eq(vim.visualMode, false);
}, { value: 'hello\nworld\nfoo' });
testVim('visual_line', function(cm, vim, helpers) {
helpers.doKeys('l', 'V', 'l', 'j', 'j', 'd');
eq(' 4\n 5', cm.getValue());
Expand All @@ -1638,7 +1672,7 @@ testVim('visual_block', function(cm, vim, helpers) {
cm.replaceRange('hello world\n{\nthis is\nsparta!', cm.getCursor());
cm.setCursor(3, 4);
helpers.doKeys('<C-v>', 'l', 'k', 'k', 'd');
eq('hello world\n{\nt is\nsta!', cm.getValue());
eq('hello world\n{\ntis\nsa!', cm.getValue());
cm.replaceRange('12345\n67891\nabcde', {line: 0, ch: 0}, {line: cm.lastLine(), ch: 6});
cm.setCursor(1, 2);
helpers.doKeys('<C-v>', '2', 'l', 'k');
Expand Down Expand Up @@ -1670,6 +1704,22 @@ testVim('visual_block', function(cm, vim, helpers) {
selections = cm.getSelections();
eq('67891\nabcde', selections.join(''));
}, {value: '1234\n5678\nabcdefg'});
testVim('visual_block_crossing_short_line', function(cm, vim, helpers) {
// visual block with long and short lines
cm.setCursor(0, 3);
helpers.doKeys('<C-v>', 'j', 'j', 'j');
var selections = cm.getSelections().join();
eq('4,,d,b', selections);
helpers.doKeys('3', 'k');
selections = cm.getSelections().join();
eq('4', selections);
}, {value: '123456\n78\nabcdefg\nfoobar'});
testVim('visual_block_curPos_on_exit', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', '3' , 'l', '<Esc>');
eqPos(makeCursor(0, 3), cm.getCursor());
}, {value: '123456\n78\nabcdefg\nfoobar'});

testVim('visual_marks', function(cm, vim, helpers) {
helpers.doKeys('l', 'v', 'l', 'l', 'j', 'j', 'v');
// Test visual mode marks
Expand Down Expand Up @@ -1831,6 +1881,49 @@ testVim('S_normal', function(cm, vim, helpers) {
helpers.assertCursorAt(1, 0);
eq('aa\n\ncc', cm.getValue());
}, { value: 'aa\nbb\ncc'});
testVim('blockwise_paste', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', '3', 'j', 'l', 'y');
cm.setCursor(0, 2);
// paste one char after the current cursor position
helpers.doKeys('p');
eq('helhelo\nworwold\nfoofo\nbarba', cm.getValue());
cm.setCursor(0, 0);
helpers.doKeys('v', '4', 'l', 'y');
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', '3', 'j', 'p');
eq('helheelhelo\norwold\noofo\narba', cm.getValue());
}, { value: 'hello\nworld\nfoo\nbar'});
testVim('blockwise_paste_long/short_line', function(cm, vim, helpers) {
// extend short lines in case of different line lengths.
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', 'j', 'j', 'y');
cm.setCursor(0, 3);
helpers.doKeys('p');
eq('hellho\nfoo f\nbar b', cm.getValue());
}, { value: 'hello\nfoo\nbar'});
testVim('blockwise_paste_cut_paste', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', '2', 'j', 'x');
cm.setCursor(0, 0);
helpers.doKeys('P');
eq('cut\nand\npaste\nme', cm.getValue());
}, { value: 'cut\nand\npaste\nme'});
testVim('blockwise_paste_from_register', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', '2', 'j', '"', 'a', 'y');
cm.setCursor(0, 3);
helpers.doKeys('"', 'a', 'p');
eq('foobfar\nhellho\nworlwd', cm.getValue());
}, { value: 'foobar\nhello\nworld'});
testVim('blockwise_paste_last_line', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('<C-v>', '2', 'j', 'l', 'y');
cm.setCursor(3, 0);
helpers.doKeys('p');
eq('cut\nand\npaste\nmcue\n an\n pa', cm.getValue());
}, { value: 'cut\nand\npaste\nme'});

testVim('S_visual', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('v', 'j', 'S');
Expand Down