From 3d787908adcba42a8565bf9ee45d39505ab5b8f8 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke Getting the code
href="LICENSE">MIT-style license. To get it, you can download
the latest
release or the current development
+ href="http://codemirror.net/codemirror-latest.zip">development
snapshot as zip files. To create a custom minified script file,
you can use the compression API.
Version:
+ (classCodeMirror-scroll) element.
+ The setSize method is the best
+ way to dynamically change size at runtime.
The actual lines, as well as the cursor, are represented
by pre elements. By default no text styling (such as
@@ -613,25 +619,19 @@
setMarker(line, text, className) → lineHandletext and className are
- optional. Setting text to a Unicode character like
- ● tends to give a nice effect. To put a picture in the gutter,
- set text to a space and className to
- something that sets a background image. If you
- specify text, the given text (which may contain
- HTML) will, by default, replace the line number for that line.
- If this is not what you want, you can include the
- string %N% in the text, which will be replaced by
- the line number.clearMarker(line)setMarker. line can be either a
- number or a handle returned by setMarker (since a
- number may now refer to a different line if something was added
- or deleted).setGutterMarker(line, gutterID, value) → lineHandlegutters option)
+ to the given value. Value can be either null, to
+ clear the marker, or a DOM element, to set it. The DOM element
+ will be shown in the specified gutter next to the specified
+ line.clearGutter(gutterID)setLineClass(line, className, backgroundClassName) → lineHandleline
can be a number or a line handle (as returned
@@ -658,8 +658,9 @@ setMarker. The returned object has the
- structure {line, handle, text, markerText, markerClass,
- lineClass, bgClass}.{line, handle, text, gutterMarkers, lineClass,
+ bgClass}, where gutterMarkers is an object
+ mapping gutter IDs to marker elements.getLineHandle(num) → lineHandlerefresh method
afterwards.)
getGutterElement() → nodegetStateAfter(line) → statenull
or undefined to have no effect.getScrollInfo(){x, y, width, height} object that
+ {left, top, width, height} object that
represents the current scroll position and scrollable area size
of the editor.cursorCoords(start, mode) → object{x, y, yBot} object containing the
+ {left, top, bottom} object containing the
coordinates of the cursor. If mode
is "local", they will be relative to the top-left
corner of the editable document. If it is "page" or
not given, they are relative to the top-left corner of the
- page. yBot is the coordinate of the bottom of the
+ page. bottom is the coordinate of the bottom of the
cursor. start is a boolean indicating whether you
want the start or the end of the selection.charCoords(pos, mode) → objectpos should be
a {line, ch} object.
coordsChar(object) → pos{x, y} object (in page coordinates),
+ {left, top} object (in page coordinates),
returns the {line, ch} position that corresponds to
it.cursorCoords(start, mode) → object{left, top, bottom} object containing the
- coordinates of the cursor. If mode
- is "local", they will be relative to the top-left
- corner of the editable document. If it is "page" or
- not given, they are relative to the top-left corner of the
- page. bottom is the coordinate of the bottom of the
- cursor. start is a boolean indicating whether you
- want the start or the end of the selection.{left, top, bottom} object
+ containing the coordinates of the cursor position.
+ If mode is "local", they will be
+ relative to the top-left corner of the editable document. If it
+ is "page" or not given, they are relative to the
+ top-left corner of the page. bottom is the
+ coordinate of the bottom of the cursor. start is a
+ boolean indicating whether you want the start or the end of the
+ selection, or, if a {line, ch} object is given, it
+ specifies the precise position at which you want to measure.charCoords(pos, mode) → objectcursorCoords, but returns the position of
an arbitrary characters. pos should be
- a {line, ch} object.{line, ch} object. This differs
+ from cursorCoords in that it'll give the size of
+ the whole character, rather than just the position that the
+ cursor would have when it would sit at that position.
coordsChar(object) → pos{left, top} object (in page coordinates),
returns the {line, ch} position that corresponds to
diff --git a/lib/codemirror.css b/lib/codemirror.css
index 623c799885..53e1ea0eb1 100644
--- a/lib/codemirror.css
+++ b/lib/codemirror.css
@@ -117,6 +117,9 @@
border-right: none;
width: 0;
}
+.CodeMirror pre.CodeMirror-secondarycursor {
+ border-left: 1px solid silver;
+}
.cm-keymap-fat-cursor pre.CodeMirror-cursor {
width: auto;
border: 0;
diff --git a/lib/codemirror.js b/lib/codemirror.js
index 75fe2bb017..bcb48fb2e0 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -26,9 +26,10 @@ window.CodeMirror = (function() {
var lineDiv = elt("div"), selectionDiv = elt("div", null, null, "position: relative; z-index: -1");
// Blinky cursor, and element used to ensure cursor fits at the end of a line
var cursor = elt("pre", "\u00a0", "CodeMirror-cursor"), widthForcer = elt("pre", "\u00a0", "CodeMirror-cursor", "visibility: hidden");
+ var otherCursor = elt("pre", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor");
// Used to measure text size
var measure = elt("div", null, "CodeMirror-measure");
- var lineSpace = elt("div", [measure, cursor, widthForcer, selectionDiv, lineDiv], null, "position: relative; z-index: 0");
+ var lineSpace = elt("div", [measure, cursor, otherCursor, widthForcer, selectionDiv, lineDiv], null, "position: relative; z-index: 0");
// Moved around its parent to cover visible view
var mover = elt("div", [elt("div", [lineSpace], "CodeMirror-lines")], null, "position: relative");
var gutters = elt("div", null, "CodeMirror-gutters"), lineGutter;
@@ -206,14 +207,14 @@ window.CodeMirror = (function() {
return getStateBefore(line + 1);
},
cursorCoords: function(start, mode) {
+ var pos;
if (start == null) start = sel.inverted;
- return this.charCoords(start ? sel.from : sel.to, mode);
+ if (typeof start == "object") pos = clipPos(start);
+ else pos = start ? sel.from : sel.to;
+ return cursorCoords(pos, mode || "page");
},
charCoords: function(pos, mode) {
- pos = clipPos(pos);
- if (mode == "local") return localCoords(pos, false);
- if (mode == "div") return localCoords(pos, true);
- return pageCoords(pos);
+ return charCoords(clipPos(pos), mode || "page");
},
coordsChar: function(coords) {
var off = eltOffset(lineSpace);
@@ -238,7 +239,7 @@ window.CodeMirror = (function() {
lineInfo: lineInfo,
getViewport: function() { return {from: showingFrom, to: showingTo};},
addWidget: function(pos, node, scroll, vert, horiz) {
- pos = localCoords(clipPos(pos));
+ pos = cursorCoords(clipPos(pos));
var top = pos.top, left = pos.left;
node.style.position = "absolute";
sizer.appendChild(node);
@@ -880,6 +881,7 @@ window.CodeMirror = (function() {
function replaceRange(code, from, to) {
from = clipPos(from);
if (!to) to = from; else to = clipPos(to);
+ if (posLess(to, from)) { var tmp = to; to = from; from = tmp; }
code = splitLines(code);
function adjustPos(pos) {
if (posLess(pos, from)) return pos;
@@ -979,7 +981,7 @@ window.CodeMirror = (function() {
}
function scrollCursorIntoView() {
- var coords = localCoords(sel.inverted ? sel.from : sel.to);
+ var coords = cursorCoords(sel.inverted ? sel.from : sel.to);
scrollIntoView(coords.left, coords.top, coords.left, coords.bottom);
if (!focused) return;
var box = sizer.getBoundingClientRect(), doScroll = null;
@@ -1262,41 +1264,80 @@ window.CodeMirror = (function() {
}
function updateSelection() {
- var collapsed = posEq(sel.from, sel.to);
- var fromPos = localCoords(sel.from, true);
- var toPos = collapsed ? fromPos : localCoords(sel.to, true);
- var headPos = sel.inverted ? fromPos : toPos;
- var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv);
- inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.top + lineOff.top - wrapOff.top)) + "px";
- inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.left + lineOff.left - wrapOff.left)) + "px";
- if (collapsed) {
- cursor.style.top = headPos.top + "px";
- cursor.style.left = (options.lineWrapping ? Math.min(headPos.left, lineSpace.offsetWidth) : headPos.left) + "px";
- cursor.style.height = (headPos.bottom - headPos.top) * .85 + "px";
+ var headPos;
+ if (posEq(sel.from, sel.to)) { // No selection, single cursor
+ var pos = headPos = cursorCoords(sel.from, "div");
+ cursor.style.left = pos.left + "px";
+ cursor.style.top = pos.top + "px";
+ cursor.style.height = (pos.bottom - pos.top) * .85 + "px";
cursor.style.display = "";
selectionDiv.style.display = "none";
+
+ if (pos.other) {
+ otherCursor.style.display = "";
+ otherCursor.style.left = pos.other.left + "px";
+ otherCursor.style.top = pos.other.top + "px";
+ otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
+ } else { otherCursor.style.display = "none"; }
} else {
- fromPos.top = Math.max(0, fromPos.top); toPos.top = Math.max(0, toPos.top);
- var sameLine = fromPos.top == toPos.top, fragment = document.createDocumentFragment();
+ headPos = cursorCoords(sel.inverted ? sel.from : sel.to, "div");
+ var fragment = document.createDocumentFragment();
var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth;
- var add = function(left, top, right, height) {
+ var add = function(left, top, right, bottom) {
+ if (top < 0) top = 0;
var rstyle = quirksMode ? "width: " + (!right ? clientWidth : clientWidth - right - left) + "px"
: "right: " + right + "px";
fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
- "px; top: " + top + "px; " + rstyle + "; height: " + height + "px"));
+ "px; top: " + top + "px; " + rstyle + "; height: " + (bottom - top) + "px"));
+ };
+
+ var middleFrom = sel.from.line + 1, middleTo = sel.to.line - 1, sameLine = sel.from.line == sel.to.line;
+ var drawForLine = function(line, from, toArg) {
+ var lineObj = getLine(line), lineLen = lineObj.text.length;
+ var coords = function(ch) { return charCoords({line: line, ch: ch}, "div", lineObj); };
+ iterateBidiSections(getOrder(lineObj), from, toArg == null ? lineLen : toArg, function(from, to, dir) {
+ var leftPos = coords(dir == "rtl" ? to - 1 : from);
+ var rightPos = coords(dir == "rtl" ? from : to - 1);
+ var left = leftPos.left, right = rightPos.right;
+ if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
+ add(left, leftPos.top, 0, leftPos.bottom);
+ left = paddingLeft();
+ }
+ if (toArg == null && to == lineLen) right = clientWidth;
+ add(left, rightPos.top, clientWidth - right, rightPos.bottom);
+ });
};
- var right = sameLine ? clientWidth - toPos.left : 0;
- add(fromPos.left, fromPos.top, right, fromPos.bottom - fromPos.top);
- var middleStart = Math.max(0, fromPos.bottom - .5);
- var middleHeight = toPos.top + 1 - middleStart;
- if (middleHeight > 2)
- add(paddingLeft(), middleStart, 0, middleHeight);
- if ((!sameLine || !sel.from.ch) && sel.to.ch)
- add(paddingLeft(), toPos.top, clientWidth - toPos.left, toPos.bottom - toPos.top);
+
+ if (sel.from.ch || sameLine)
+ // Draw the first line of selection.
+ drawForLine(sel.from.line, sel.from.ch, sameLine ? sel.to.ch : null);
+ else
+ // Simply include it in the middle block.
+ middleFrom = sel.from.line;
+
+ if (middleFrom <= middleTo) {
+ // Draw the middle
+ var botLine = getLine(middleTo),
+ bottom = charCoords({line: middleTo, ch: botLine.text.length}, "div", botLine);
+ // Kludge to try and prevent fetching coordinates twice if
+ // start end end are on same line.
+ var top = (middleFrom != middleTo || botLine.height > bottom.bottom - bottom.top) ?
+ charCoords({line: middleFrom, ch: 0}, "div") : bottom;
+ add(paddingLeft(), top.top, 0, bottom.bottom);
+ }
+
+ if (!sameLine && sel.to.ch)
+ drawForLine(sel.to.line, 0, sel.to.ch);
+
removeChildrenAndAdd(selectionDiv, fragment);
- cursor.style.display = "none";
+ cursor.style.display = otherCursor.style.display = "none";
selectionDiv.style.display = "";
}
+
+ // Move the hidden textarea near the cursor to prevent scrolling artifacts
+ var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv);
+ inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.top + lineOff.top - wrapOff.top)) + "px";
+ inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.left + lineOff.left - wrapOff.left)) + "px";
}
function setShift(val) {
@@ -1385,7 +1426,7 @@ window.CodeMirror = (function() {
else return pos;
}
- function findPosH(dir, unit) {
+ function findPosH(dir, unit, visually) {
var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch;
var lineObj = getLine(line);
function findNextLine() {
@@ -1395,10 +1436,13 @@ window.CodeMirror = (function() {
}
}
function moveOnce(boundToLine) {
- if (ch == (dir < 0 ? 0 : lineObj.text.length)) {
- if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0;
- else return false;
- } else ch += dir;
+ var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir);
+ if (next == null) {
+ if (!boundToLine && findNextLine()) {
+ if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
+ else ch = dir < 0 ? lineObj.text.length : 0;
+ } else return false;
+ } else ch = next;
return true;
}
if (unit == "char") moveOnce();
@@ -1416,17 +1460,16 @@ window.CodeMirror = (function() {
}
function moveH(dir, unit) {
var pos = dir < 0 ? sel.from : sel.to;
- if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit);
+ if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit, true);
setCursor(pos.line, pos.ch, true);
}
function deleteH(dir, unit) {
if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to);
- else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to);
- else replaceRange("", sel.from, findPosH(dir, unit));
+ else replaceRange("", sel.from, findPosH(dir, unit, false));
userSelChange = true;
}
function moveV(dir, unit) {
- var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true);
+ var dist = 0, pos = cursorCoords(sel.inverted ? sel.from : sel.to, "div"); // FIXME cursorCoords
var x = pos.left, y;
if (goalColumn != null) x = goalColumn;
if (unit == "page") {
@@ -1436,7 +1479,7 @@ window.CodeMirror = (function() {
y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
}
var target = coordsChar(x, y);
- if (unit == "page") scrollbar.scrollTop += localCoords(target, true).top - pos.top;
+ if (unit == "page") scrollbar.scrollTop += charCoords(target, "div").top - pos.top;
setCursor(target.line, target.ch, true);
goalColumn = x;
}
@@ -1697,8 +1740,8 @@ window.CodeMirror = (function() {
}
function measureLine(line, ch) {
- var atEnd = ch == line.text.length;
- var pre = line.getElement(makeTab, atEnd && ch ? ch - 1 : ch, options.lineWrapping);
+ var atEnd = ch && ch == line.text.length;
+ var pre = line.getElement(makeTab, atEnd ? ch - 1 : ch, options.lineWrapping);
removeChildrenAndAdd(measure, pre);
var anchor = pre.anchor;
// We'll sample once at the top, once at the bottom of the line,
@@ -1726,24 +1769,65 @@ window.CodeMirror = (function() {
var bottom = anchor.offsetTop + anchor.offsetHeight;
return {top: top, bottom: bottom, left: atEnd ? right : left, right: right};
}
- function localCoords(pos, inLineWrap) {
- var off = heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0);
- var sp = measureLine(getLine(pos.line), pos.ch);
- sp.top += off; sp.bottom += off;
- return sp;
+
+ function intoCoordSystem(pos, rect, context) {
+ if (context == "line") return rect;
+ var yOff = heightAtLine(doc, pos.line) - (context == "div" ? displayOffset : 0);
+ if (context == "page") {
+ var lOff = eltOffset(lineSpace);
+ yOff += lOff.top; rect.left += lOff.left; rect.right += lOff.right;
+ }
+ rect.top += yOff; rect.bottom += yOff;
+ return rect;
+ }
+
+ function charCoords(pos, context, lineObj) {
+ return intoCoordSystem(pos, measureLine(lineObj || getLine(pos.line), pos.ch), context);
+ }
+
+ function cursorCoords(pos, context, lineObj) {
+ lineObj = lineObj || getLine(pos.line);
+ function get(ch, right) {
+ var m = measureLine(lineObj, ch);
+ if (right) m.left = m.right; else m.right = m.left;
+ return intoCoordSystem(pos, m, context);
+ }
+ var order = getOrder(lineObj), ch = pos.ch;
+ if (!order) return get(ch);
+ var main, other, linedir = order[0].level, last = order[order.length-1];
+ // Special case for inconsistence between start and end order.
+ if (!linedir && last.level % 2 != linedir && ch == lineObj.text.length)
+ return get(last.from, true);
+ for (var i = 0; i < order.length; ++i) {
+ var part = order[i], rtl = part.level % 2;
+ if (part.from < ch && part.to > ch) return get(ch, rtl);
+ var left = rtl ? part.to : part.from, right = rtl ? part.from : part.to;
+ if (left == ch) {
+ var here = get(rtl ? ch - 1 : ch);
+ if (rtl == linedir) main = here; else other = here;
+ } else if (right == ch) {
+ var here = get(rtl ? ch : ch - 1, true);
+ if (rtl == linedir) main = here; else other = here;
+ }
+ }
+ if (!main) return other;
+ if (other) main.other = other;
+ return main;
}
+
// Coords must be lineSpace-local
function coordsChar(x, y) {
var cw = charWidth(), heightPos = displayOffset + y;
if (heightPos < 0) return {line: 0, ch: 0};
var lineNo = lineAtHeight(doc, heightPos);
if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length};
- var lineObj = getLine(lineNo), text = lineObj.text;
+ var lineObj = getLine(lineNo);
+ if (!lineObj.text.length) return {line: lineNo, ch: 0};
var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0;
if (x < 0) x = 0;
var wrongLine = false;
- function getX(len) {
- var sp = measureLine(lineObj, len);
+ function getX(ch) {
+ var sp = cursorCoords({line: lineNo, ch: ch}, "line", lineObj);
if (tw) {
wrongLine = true;
if (innerOff > sp.bottom) return Math.max(0, sp.left - scroller.clientWidth);
@@ -1752,34 +1836,38 @@ window.CodeMirror = (function() {
}
return sp.left;
}
- var from = 0, fromX = 0, to = text.length, toX;
- // Guess a suitable upper bound for our search.
- var estimated = Math.min(to, Math.ceil((x + Math.floor(innerOff / textHeight()) * scroller.clientWidth * .9) / cw));
- for (;;) {
- var estX = getX(estimated);
- if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
- else {toX = estX; to = estimated; break;}
- }
+ var bidi = getOrder(lineObj), dist = lineObj.text.length;
+ var from = lineLeft(lineObj), fromX = 0, to = lineRight(lineObj), toX;
+ if (!bidi) {
+ // Guess a suitable upper bound for our search.
+ var estimated = Math.min(to, Math.ceil((x + Math.floor(innerOff / textHeight()) * scroller.clientWidth * .9) / cw));
+ for (;;) {
+ var estX = getX(estimated);
+ if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
+ else {toX = estX; to = estimated; break;}
+ }
+ // Try to guess a suitable lower bound as well.
+ estimated = Math.floor(to * 0.8); estX = getX(estimated);
+ if (estX < x) {from = estimated; fromX = estX;}
+ dist = to - from;
+ } else toX = getX(to);
if (x > toX) return {line: lineNo, ch: to};
- // Try to guess a suitable lower bound as well.
- estimated = Math.floor(to * 0.8); estX = getX(estimated);
- if (estX < x) {from = estimated; fromX = estX;}
// Do a binary search between these bounds.
for (;;) {
- if (to - from <= 1) {
+ if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
var after = x - fromX < toX - x;
return {line: lineNo, ch: after ? from : to, after: after};
}
- var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
- if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; }
- else {from = middle; fromX = middleX;}
+ var step = Math.ceil(dist / 2), middle = from + step;
+ if (bidi) {
+ middle = from;
+ for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
+ }
+ var middleX = getX(middle);
+ if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; dist -= step;}
+ else {from = middle; fromX = middleX; dist = step;}
}
}
- function pageCoords(pos) {
- var local = localCoords(pos, true), off = eltOffset(lineSpace);
- local.left += off.left; local.right += off.left; local.top += off.top; local.bottom += off.top;
- return local;
- }
var cachedHeight, cachedHeightFor, measurePre;
function textHeight() {
@@ -1863,9 +1951,9 @@ window.CodeMirror = (function() {
function restartBlink() {
clearInterval(blinker);
var on = true;
- cursor.style.visibility = "";
+ cursor.style.visibility = otherCursor.style.visibility = "";
blinker = setInterval(function() {
- cursor.style.visibility = (on = !on) ? "" : "hidden";
+ cursor.style.visibility = otherCursor.style.visibility = (on = !on) ? "" : "hidden";
}, options.cursorBlinkRate);
}
@@ -2015,7 +2103,7 @@ window.CodeMirror = (function() {
}
var newScrollPos, updated;
if (selectionChanged) {
- var coords = localCoords(sel.inverted ? sel.from : sel.to);
+ var coords = cursorCoords(sel.inverted ? sel.from : sel.to);
newScrollPos = calculateScrollPos(coords.left, coords.top, coords.left, coords.bottom);
}
if (changes.length || newScrollPos && newScrollPos.scrollTop != null)
@@ -2166,13 +2254,21 @@ window.CodeMirror = (function() {
redo: function(cm) {cm.redo();},
goDocStart: function(cm) {cm.setCursor(0, 0, true);},
goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);},
- goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);},
+ goLineStart: function(cm) {
+ var line = cm.getCursor().line;
+ cm.setCursor(line, lineStart(cm.getLineHandle(line)), true);
+ },
goLineStartSmart: function(cm) {
- var cur = cm.getCursor();
- var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/));
- cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true);
+ var cur = cm.getCursor(), line = cm.getLineHandle(cur.line), order = getOrder(line);
+ if (!order || order[0].level == 0) {
+ var firstNonWS = Math.max(0, line.text.search(/\S/));
+ cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true);
+ } else cm.setCursor(cur.line, lineStart(line), true);
+ },
+ goLineEnd: function(cm) {
+ var line = cm.getCursor().line;
+ cm.setCursor(line, lineEnd(cm.getLineHandle(line)), true);
},
- goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);},
goLineUp: function(cm) {cm.moveV(-1, "line");},
goLineDown: function(cm) {cm.moveV(1, "line");},
goPageUp: function(cm) {cm.moveV(-1, "page");},
@@ -2504,6 +2600,7 @@ window.CodeMirror = (function() {
copyStyles(to, this.text.length, this.styles, st);
this.styles = st;
this.text = this.text.slice(0, from) + text + this.text.slice(to);
+ this.order = null;
this.stateAfter = null;
if (mk) {
var diff = text.length - (to - from);
@@ -2535,6 +2632,7 @@ window.CodeMirror = (function() {
append: function(line) {
var mylen = this.text.length, mk = line.marked, mymk = this.marked;
this.text += line.text;
+ this.order = this.order || line.order ? null : false;
copyStyles(0, line.text.length, line.styles, this.styles);
if (mymk) {
for (var i = 0; i < mymk.length; ++i)
@@ -3244,5 +3342,248 @@ window.CodeMirror = (function() {
for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
})();
+ function iterateBidiSections(order, from, to, f) {
+ if (!order) return f(from, to, "ltr");
+ for (var i = 0; i < order.length; ++i) {
+ var part = order[i];
+ if (part.from < to && part.to > from)
+ f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr");
+ }
+ }
+
+ function bidiLeft(part) { return part.level % 2 ? part.to : part.from; }
+ function bidiRight(part) { return part.level % 2 ? part.from : part.to; }
+
+ function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; }
+ function lineRight(line) {
+ var order = getOrder(line);
+ if (!order) return line.text.length;
+ var last = order[order.length-1];
+ return order[0].level != last.level ? line.text.length : bidiRight(last);
+ }
+ function lineStart(line) {
+ var order = getOrder(line);
+ if (!order) return 0;
+ return order[0].level % 2 ? lineRight(line) : lineLeft(line);
+ }
+ function lineEnd(line) {
+ var order = getOrder(line);
+ if (!order) return line.text.length;
+ return order[0].level % 2 ? lineLeft(line) : lineRight(line);
+ }
+
+ // This is somewhat involved. It is needed in order to move
+ // 'visually' through bi-directional text -- i.e., pressing left
+ // should make the cursor go left, even when in RTL text. The
+ // tricky part is the 'jumps', where RTL and LTR text touch each
+ // other. This often requires the cursor offset to move more than
+ // one unit, in order to visually move one unit.
+ function moveVisually(line, start, dir) {
+ var bidi = getOrder(line);
+ if (!bidi) return moveLogically(line, start, dir);
+ var linedir = bidi[0].level, last = bidi[bidi.length-1];
+ if (linedir == last.level % 2) last = null;
+ for (var i = 0; i < bidi.length; ++i) {
+ var part = bidi[i], sticky = part.level % 2 == linedir;
+ if ((part.from < start && part.to > start) ||
+ (sticky && (part.from == start || part.to == start))) break;
+ }
+ var target = start + (part.level % 2 ? -dir : dir);
+ if (i == bidi.length) {
+ if (dir > 0) return null; // Moving right from EOL
+ target = last.level % 1 ? last.from + 1 : last.to - 1;
+ if (last.to - last.from == 1) return bidiRight(bidi[i-2]);
+ else return target;
+ }
+
+ while (target != null) {
+ if (part.level % 2 == linedir) {
+ if (target < part.from || target > part.to) {
+ part = bidi[i += dir];
+ target = part && (dir > 0 == part.level % 2 ? part.to - 1 : part.from + 1);
+ } else break;
+ } else {
+ if (target == bidiLeft(part)) {
+ part = bidi[--i];
+ target = part && bidiRight(part);
+ } else if (target == bidiRight(part)) {
+ if (part == last) return line.text.length;
+ part = bidi[++i];
+ target = part && bidiLeft(part);
+ } else break;
+ }
+ }
+
+ return target < 0 || target > line.text.length ? null : target;
+ }
+
+ function moveLogically(line, start, dir) {
+ var target = start + dir;
+ return target < 0 || target > line.text.length ? null : target;
+ }
+
+ function getOrder(line) {
+ var order = line.order;
+ if (order == null) order = line.order = bidiOrdering(line.text);
+ return order;
+ }
+
+ // Bidirectional ordering algorithm
+ // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
+ // that this (partially) implements.
+
+ // One-char codes used for character types:
+ // L (L): Left-to-Right
+ // R (R): Right-to-Left
+ // r (AL): Right-to-Left Arabic
+ // 1 (EN): European Number
+ // + (ES): European Number Separator
+ // % (ET): European Number Terminator
+ // n (AN): Arabic Number
+ // , (CS): Common Number Separator
+ // m (NSM): Non-Spacing Mark
+ // b (BN): Boundary Neutral
+ // s (B): Paragraph Separator
+ // t (S): Segment Separator
+ // w (WS): Whitespace
+ // N (ON): Other Neutrals
+
+ // Returns null if characters are ordered as they appear
+ // (left-to-right), or an array of sections ({from, to, level}
+ // objects) in the order in which they occur visually.
+ var bidiOrdering = (function() {
+ // Character types for codepoints 0 to 0xff
+ var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL";
+ // Character types for codepoints 0x600 to 0x6ff
+ var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr";
+ function charType(code) {
+ var type = "L";
+ if (code <= 0xff) return lowTypes.charAt(code);
+ else if (0x590 <= code && code <= 0x5f4) return "R";
+ else if (0x600 <= code && code <= 0x6ff) return arabicTypes.charAt(code - 0x600);
+ else if (0x700 <= code && code <= 0x8ac) return "r";
+ else return "L";
+ }
+
+ var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
+ var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;
+
+ return function charOrdering(str) {
+ if (!bidiRE.test(str)) return false;
+ var len = str.length, types = new Array(len), startType = null;
+ for (var i = 0; i < len; ++i) {
+ var type = charType(str.charCodeAt(i));
+ types[i] = type;
+ if (startType == null) {
+ if (type == "L") startType = "L";
+ else if (type == "R" || type == "r") startType = "R";
+ }
+ }
+ if (startType == null) startType = "L";
+
+ // W1. Examine each non-spacing mark (NSM) in the level run, and
+ // change the type of the NSM to the type of the previous
+ // character. If the NSM is at the start of the level run, it will
+ // get the type of sor.
+ for (var i = 0, prev = startType; i < len; ++i) {
+ var type = types[i];
+ if (type == "m") types[i] = lastType;
+ else prev = type;
+ }
+
+ // W2. Search backwards from each instance of a European number
+ // until the first strong type (R, L, AL, or sor) is found. If an
+ // AL is found, change the type of the European number to Arabic
+ // number.
+ // W3. Change all ALs to R.
+ for (var i = 0, cur = startType; i < len; ++i) {
+ var type = types[i];
+ if (type == "1" && cur == "r") types[i] = "n";
+ else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; }
+ }
+
+ // W4. A single European separator between two European numbers
+ // changes to a European number. A single common separator between
+ // two numbers of the same type changes to that type.
+ for (var i = 1, prev = types[0]; i < len - 1; ++i) {
+ var type = types[i];
+ if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1";
+ else if (type == "," && prev == types[i+1] &&
+ (prev == "1" || prev == "n")) types[i] = prev;
+ prev = type;
+ }
+
+ // W5. A sequence of European terminators adjacent to European
+ // numbers changes to all European numbers.
+ // W6. Otherwise, separators and terminators change to Other
+ // Neutral.
+ for (var i = 0; i < len; ++i) {
+ var type = types[i];
+ if (type == ",") types[i] = "N";
+ else if (type == "%") {
+ for (var end = i + 1; end < len && types[end] == "%"; ++end) {}
+ var replace = (i && types[i-1] == "!") || (end < len - 1 && types[end] == "1") ? "1" : "N";
+ for (var j = i; j < end; ++j) types[j] = replace;
+ i = end - 1;
+ }
+ }
+
+ // W7. Search backwards from each instance of a European number
+ // until the first strong type (R, L, or sor) is found. If an L is
+ // found, then change the type of the European number to L.
+ for (var i = 0, cur = startType; i < len; ++i) {
+ var type = types[i];
+ if (cur == "L" && type == "1") types[i] = "L";
+ else if (isStrong.test(type)) cur = type;
+ }
+
+ // N1. A sequence of neutrals takes the direction of the
+ // surrounding strong text if the text on both sides has the same
+ // direction. European and Arabic numbers act as if they were R in
+ // terms of their influence on neutrals. Start-of-level-run (sor)
+ // and end-of-level-run (eor) are used at level run boundaries.
+ // N2. Any remaining neutrals take the embedding direction.
+ for (var i = 0; i < len; ++i) {
+ if (isNeutral.test(types[i])) {
+ for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {}
+ var before = (i ? types[i-1] : startType) == "L";
+ var after = (end < len - 1 ? types[end] : startType) == "L";
+ var replace = before == after ? (before ? "L" : "R") : startType;
+ for (var j = i; j < end; ++j) types[j] = replace;
+ i = end - 1;
+ }
+ }
+
+ // Here we depart from the documented algorithm, in order to avoid
+ // building up an actual levels array. Since there are only three
+ // levels (0, 1, 2) in an implementation that doesn't take
+ // explicit embedding into account, we can build up the order on
+ // the fly, without following the level-based algorithm.
+ var order = [];
+ for (var i = 0; i < len;) {
+ if (countsAsLeft.test(types[i])) {
+ var start = i;
+ for (++i; i < len && countsAsLeft.test(types[i]); ++i) {}
+ order.push({from: start, to: i, level: 0});
+ } else {
+ var pos = i, at = order.length;
+ for (++i; i < len && types[i] != "L"; ++i) {}
+ for (var j = pos; j < i;) {
+ if (countsAsNum.test(types[j])) {
+ if (pos < j) order.splice(at, 0, {from: pos, to: j, level: 1});
+ var nstart = j;
+ for (++j; j < i && countsAsNum.test(types[j]); ++j) {}
+ order.splice(at, 0, {from: nstart, to: j, level: 2});
+ pos = j;
+ } else ++j;
+ }
+ if (pos < i) order.splice(at, 0, {from: pos, to: i, level: 1});
+ }
+ }
+
+ return order;
+ };
+ })();
+
return CodeMirror;
})();
diff --git a/test/driver.js b/test/driver.js
index 975c24f9fc..12bdc1e29f 100644
--- a/test/driver.js
+++ b/test/driver.js
@@ -1,6 +1,7 @@
var tests = [], debug = null;
function Failure(why) {this.message = why;}
+Failure.prototype.toString = function() { return this.message; };
function test(name, run, expectedFail) {
tests.push({name: name, func: run, expectedFail: expectedFail});
@@ -19,7 +20,10 @@ function runTests(callback) {
function step(i) {
if (i == tests.length) return callback("done");
var test = tests[i], expFail = test.expectedFail;
- if (debug != null && debug != test.name) return step(i + 1);
+ if (debug != null) {
+ if (debug == test.name) return test.func();
+ else return step(i + 1);
+ }
try {
test.func();
if (expFail) callback("fail", test.name, "was expected to fail, but succeeded");
diff --git a/test/test.js b/test/test.js
index 19a227578a..5f26fdd986 100644
--- a/test/test.js
+++ b/test/test.js
@@ -199,7 +199,7 @@ testCM("posFromIndex", function(cm) {
eq(pos.ch, example.ch);
if (example.index >= 0 && example.index < 64)
eq(cm.indexFromPos(pos), example.index);
- }
+ }
});
testCM("undo", function(cm) {
@@ -397,7 +397,7 @@ testCM("doubleScrollbar", function(cm) {
cm.setSize(null, 100);
addDoc(cm, 1, 300);
var wrap = cm.getWrapperElement();
- is(wrap.offsetWidth - byClassName(wrap, "CodeMirror-lines")[0].offsetWidth <= scrollbarWidth);
+ is(wrap.offsetWidth - byClassName(wrap, "CodeMirror-lines")[0].offsetWidth <= scrollbarWidth + 1);
});
testCM("weirdLinebreaks", function(cm) {
@@ -658,3 +658,25 @@ testCM("verticalMovementCommandsWrapping", function(cm) {
}
}, {value: "a very long line that wraps around somehow so that we can test cursor movement\nshortone\nk",
lineWrapping: true});
+
+testCM("rtlMovement", function(cm) {
+ forEach(["خحج", "خحabcخحج", "abخحخحجcd", "abخde", "abخح2342خ1حج", "خ1ح2خح3حxج", "خحcd", "1خحcd", "abcdeح1ج"], function(line) {
+ var inv = line.charAt(0) == "خ";
+ cm.setValue(line + "\n"); cm.execCommand(inv ? "goLineEnd" : "goLineStart");
+ var cursor = byClassName(cm.getWrapperElement(), "CodeMirror-cursor")[0];
+ var prevX = cursor.offsetLeft, prevY = cursor.offsetTop;
+ for (var i = 0; i <= line.length; ++i) {
+ cm.execCommand("goCharRight");
+ if (i == line.length) is(cursor.offsetTop > prevY, "next line");
+ else is(cursor.offsetLeft > prevX, "moved right");
+ prevX = cursor.offsetLeft; prevY = cursor.offsetTop;
+ }
+ cm.setCursor(0, 0); cm.execCommand(inv ? "goLineStart" : "goLineEnd");
+ prevX = cursor.offsetLeft;
+ for (var i = 0; i < line.length; ++i) {
+ cm.execCommand("goCharLeft");
+ is(cursor.offsetLeft < prevX, "moved left");
+ prevX = cursor.offsetLeft;
+ }
+ });
+});
From 6e9c3de2b65f205451b11201400e6abaf5790e8b Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke Styling the current cursor line.
diff --git a/demo/changemode.html b/demo/changemode.html index a3d42c04bf..17c82d277c 100644 --- a/demo/changemode.html +++ b/demo/changemode.html @@ -33,11 +33,11 @@onChange (function){from, to, text, next}
- object containing information about the changes
- that occurred as second argument. from
- and to are the positions (in the pre-change
- coordinate system) where the change started and
- ended (for example, it might be {ch:0, line:18} if the
- position is at the beginning of line #19). text
- is an array of strings representing the text that replaced the changed
- range (split by line). If multiple changes happened during a single
- operation, the object will have a next property pointing to
- another change object (which may point to another, etc).onCursorActivity (function)onViewportChange (function)onGutterClick (function)mousedown event
- object as third argument, and the CSS class of the gutter that
- was clicked as fourth argument.onFocus, onBlur (function)onScroll (function)onHighlightComplete (function)onUpdate (function)matchBrackets (boolean)A CodeMirror instance emits a number of events, which allow
+ client code to react to various situations. These are registered
+ with the connect method (and
+ removed with the disconnect
+ method). These are the events that fire on the instance object.
+ The name of the event is followed by the arguments that will be
+ passed to the handler. The instance argument always
+ refers to the editor instance.
"change" (instance, changeObj)changeObj is a {from, to, text,
+ next} object containing information about the changes
+ that occurred as second argument. from
+ and to are the positions (in the pre-change
+ coordinate system) where the change started and ended (for
+ example, it might be {ch:0, line:18} if the
+ position is at the beginning of line #19). text is
+ an array of strings representing the text that replaced the
+ changed range (split by line). If multiple changes happened
+ during a single operation, the object will have
+ a next property pointing to another change object
+ (which may point to another, etc)."cursorActivity" (instance)"viewportChange" (instance, from, to)from and to arguments
+ give the new start and end of the viewport."gutterClick" (instance, line, gutter, clickEvent)mousedown event object as
+ fourth argument."focus", "blur" (instance)"scroll" (instance)"highlightComplete" (instance)"update" (instance)It is also possible to register events
+ on other objects. Line handles (as returned by, for
+ example, getLineHandle)
+ can be listened on with CodeMirror.connect(handle, "delete",
+ myFunc). They support the following events:
"delete" ()Keymaps are ways to associate keys with functionality. A keymap @@ -540,6 +563,16 @@
connect(type, func)CodeMirror.connect(object, type, func) version
+ that allows registering of events on any object.disconnect(type, func)CodeMirror.disconnect(object, type,
+ func) also exists.cursorCoords(start, mode) → object{left, top, bottom} object
containing the coordinates of the cursor position.
@@ -655,10 +688,6 @@ hideLine—re-shows a previously
hidden line, by number or by handle.onDeleteLine(line, func)lineInfo(line) → objectonViewportChange
- option.viewportChange
+ event.
addWidget(pos, node, scrollIntoView)node, which should be an absolutely
@@ -930,7 +959,7 @@ match-highlighter.jsmatchHighlight method to CodeMirror
instances that can be called (typically from
- a onCursorActivity
+ a cursorActivity
handler) to highlight all instances of a currently selected word
with the a classname given as a first argument to the method.
Depends on
@@ -1174,6 +1203,7 @@ Folded range handles, as returned
+ by foldLines, emit the
+ following event:
"unfold" ()unfoldLines. Will
+ only be fired once per handle.Keymaps are ways to associate keys with functionality. A keymap @@ -678,15 +690,23 @@
backgroundClassName to style its
background (which lies behind the selection).
Pass null to clear the classes for a line.
- hideLine(line) → lineHandleshowLine(line) → lineHandlehideLine—re-shows a previously
- hidden line, by number or by handle.foldLines(from, to, unfoldOnEnter) → foldHandleto is
+ non-inclusive). Hidden lines don't show up in the editor.
+ Deleting a region around them does delete them, and copying a
+ region around will include them in the copied text. Vertical
+ cursor movement will skip hidden lines.
+ When unfoldOnEnter is true, horizontal movement
+ into the range will un-hide the range. When it is false,
+ horizonal cursor movement will skip it as if it isn't there. The
+ handle this function returns can be used to unfold the range,
+ and emits an unfold
+ event when unfolded. Folded ranges may overlap and nest.unfoldLines(foldHandle)foldLines, this will
+ unfold that range of lines.lineInfo(line) → object"change" ()Folded range handles, as returned
diff --git a/lib/codemirror.js b/lib/codemirror.js
index 71e02e43d5..87bf6bc779 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -765,9 +765,10 @@ window.CodeMirror = (function() {
});
var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
- var th = textHeight();
+ var th = textHeight(), wholeLines = from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "";
+ if (!wholeLines) signalLater(firstLine, "change", delayedCallbacks);
// First adjust the line structure, taking some care to leave highlighting intact.
- if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") {
+ if (wholeLines) {
// This is a whole-line replace. Treated specially to make
// sure line objects move the way they are supposed to.
var added = [], prevLine = null;
@@ -800,6 +801,7 @@ window.CodeMirror = (function() {
} else {
var added = [];
firstLine.replace(from.ch, null, newText[0]);
+ signalLater(lastLine, "change", delayedCallbacks);
lastLine.replace(null, to.ch, newText[newText.length-1]);
firstLine.fixMarkEnds(lastLine);
for (var i = 1, e = newText.length - 1; i < e; ++i)
diff --git a/lib/util/foldcode.js b/lib/util/foldcode.js
index bf4ba1d785..46c25bf46c 100644
--- a/lib/util/foldcode.js
+++ b/lib/util/foldcode.js
@@ -190,6 +190,7 @@ CodeMirror.newFoldFunction = function(rangeFinder, markText, hideEnd) {
folded.splice(known.pos, 1);
}
CodeMirror.connect(first, "delete", clear);
+ CodeMirror.connect(first, "change", clear);
CodeMirror.connect(cm.getLineHandle(line + 1), "delete", clear);
folded.push({start: first, handle: handle});
}
diff --git a/test/test.js b/test/test.js
index 2f01e0e285..545ca6766e 100644
--- a/test/test.js
+++ b/test/test.js
@@ -712,3 +712,23 @@ testCM("movebyTextUnit", function(cm) {
cm.execCommand("goCharRight");
eqPos(cm.getCursor(), {line: 1, ch: 6});
});
+
+testCM("lineChangeEvents", function(cm) {
+ addDoc(cm, 3, 5);
+ var log = [], want = ["ch 0", "ch 1", "del 2", "ch 0", "ch 0", "del 1", "del 3", "del 4"];
+ for (var i = 0; i < 5; ++i) {
+ CodeMirror.connect(cm.getLineHandle(i), "delete", function(i) {
+ return function() {log.push("del " + i);};
+ }(i));
+ CodeMirror.connect(cm.getLineHandle(i), "change", function(i) {
+ return function() {log.push("ch " + i);};
+ }(i));
+ }
+ cm.replaceRange("x", {line: 0, ch: 1});
+ cm.replaceRange("xy", {line: 1, ch: 1}, {line: 2});
+ cm.replaceRange("foo\nbar", {line: 0, ch: 1});
+ cm.replaceRange("", {line: 0, ch: 0}, {line: cm.lineCount()});
+ eq(log.length, want.length, "same length");
+ for (var i = 0; i < log.length; ++i)
+ eq(log[i], want[i]);
+});
From 6ef1983809e1d26bf2f73aa97812fde5feac2062 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke CodeMirror: Mode-Changing demo
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
mode: "scheme",
lineNumbers: true,
- matchBrackets: true,
tabMode: "indent"
});
editor.connect("change", function() {
diff --git a/doc/compress.html b/doc/compress.html
index 3e4abc1479..7e3438edb4 100644
--- a/doc/compress.html
+++ b/doc/compress.html
@@ -119,6 +119,7 @@ { } CodeMi
+
diff --git a/doc/manual.html b/doc/manual.html
index f94b867775..2b56b6ff07 100644
--- a/doc/manual.html
+++ b/doc/manual.html
@@ -211,10 +211,6 @@
Configuration
simply true), focusing of the editor is also
disallowed.
- matchBrackets (boolean)cursorBlinkRate (number)Programming API
widget again, simply use DOM methods (move it somewhere else, or
call removeChild on its parent).
- matchBrackets()lineCount() → numberProgramming API
will cause the given value (usually a method) to be added to all
CodeMirror instances created from then on.
Similarly, CodeMirror.defineOption(name,
+ default, updateFunc) can be used to define new options for
+ CodeMirror. The updateFunc will be called with the
+ editor instance and the new value when an editor is initialized,
+ and whenever the option is modified
+ through setOption.
The lib/util directory in the distribution
@@ -917,6 +917,14 @@
searchcursor.js, and will make use
of openDialog when
available to make prompting for search queries less ugly.
+ matchbrackets.jsmatchBrackets which, when set
+ to true, causes matching brackets to be highlighted whenever the
+ cursor is next to them. It also adds a
+ method matchBrackets that forces this to happen
+ once, and a method findMatchingBracket that can be
+ used to run the bracket-finding algorithm that this uses
+ internally.foldcode.js<!doctype
html> is recommended.)I am not actively testing against every new browser release, and
diff --git a/lib/codemirror.js b/lib/codemirror.js
index 0e18076afc..17546493b1 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -249,6 +249,8 @@ window.CodeMirror = (function() {
// The element in which the editor lives.
var wrapper = elt("div", [gutters, inputDiv, scrollbarH, scrollbarV, scrollbarFiller, scroller],
"CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : ""));
+ // Work around IE7 z-index and scrollable div bugs
+ if (ie_lt8) { gutters.style.zIndex = -1; scroller.style.position = "relative"; }
wrapper.CodeMirror = instance;
if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
@@ -1236,6 +1238,7 @@ window.CodeMirror = (function() {
inside.push(elt("div", "\u00a0", line.bgClassName + " CodeMirror-linebackground"));
inside.push(lineElement);
lineElement = elt("div", inside, null, "position: relative");
+ if (ie_lt8) lineElement.style.zIndex = 2;
}
}
lineDiv.insertBefore(lineElement, curNode);
From 42f23ed9dcddaef01f5ad16c5eb61801414a5e6a Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke CodeMirror: Variable Height Demo
+
+
+
+
+
+
diff --git a/index.html b/index.html
index 8e3797210b..87047ac71a 100644
--- a/index.html
+++ b/index.html
@@ -101,6 +101,7 @@ Usage demos:
CodeMirror: Autocomplete demo
diff --git a/demo/fullscreen.html b/demo/fullscreen.html
index 69541674d7..d2df04de14 100644
--- a/demo/fullscreen.html
+++ b/demo/fullscreen.html
@@ -111,14 +111,14 @@ CodeMirror: Full Screen Editing
return window.innerHeight || (document.documentElement || document.body).clientHeight;
}
function setFullScreen(cm, full) {
- var wrap = cm.getWrapperElement(), scroll = cm.getScrollerElement();
+ var wrap = cm.getWrapperElement();
if (full) {
wrap.className += " CodeMirror-fullscreen";
- scroll.style.height = winHeight() + "px";
+ wrap.style.height = winHeight() + "px";
document.documentElement.style.overflow = "hidden";
} else {
wrap.className = wrap.className.replace(" CodeMirror-fullscreen", "");
- scroll.style.height = "";
+ wrap.style.height = "";
document.documentElement.style.overflow = "";
}
cm.refresh();
diff --git a/demo/resize.html b/demo/resize.html
index f0c9750acf..ddd4e56695 100644
--- a/demo/resize.html
+++ b/demo/resize.html
@@ -11,9 +11,9 @@
+
CodeMirror: XML Autocomplete demo
diff --git a/doc/manual.html b/doc/manual.html
index 2b56b6ff07..1291253650 100644
--- a/doc/manual.html
+++ b/doc/manual.html
@@ -443,16 +443,18 @@ Customized Styling
CodeMirror
CodeMirror-scrolloverflow: auto + fixed height). By
- default, it does. Giving this height: auto; overflow:
- visible; will cause the editor to resize to fit its
- content.overflow: auto +
+ fixed height). By default, it does. Setting
+ the CodeMirror class to have height:
+ auto and giving this class overflow-x: auto;
+ overflow-y: hidden; will cause the editor
+ to resize to fit its
+ content.CodeMirror-focusedSo note carefully that, in order to resize the
- editor, you should set a width on
- the wrapper
- (class CodeMirror) element, and a height on
- the scroller
- (class CodeMirror-scroll) element.
- The setSize method is the best
- way to dynamically change size at runtime.
The actual lines, as well as the cursor, are represented
by pre elements. By default no text styling (such as
bold) that might change line height is applied. If you do want
diff --git a/lib/codemirror.css b/lib/codemirror.css
index 15272a0373..9b3fe40adc 100644
--- a/lib/codemirror.css
+++ b/lib/codemirror.css
@@ -1,12 +1,12 @@
/* BASICS */
.CodeMirror {
- /* Set width, borders, and global font properties here */
+ /* Set height, width, borders, and global font properties here */
font-family: monospace;
+ height: 300px;
}
.CodeMirror-scroll {
- /* Set height and scrolling behaviour here */
- height: 300px;
+ /* Set scrolling behaviour here */
overflow: auto;
}
@@ -113,10 +113,12 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror, and the paddings in .CodeMirror-sizer */
margin-bottom: -30px; margin-right: -30px;
+ border: 0px solid white;
+ border-right-width: 30px; border-bottom-width: 30px;
+ height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
}
.CodeMirror-sizer {
- padding: 0 30px 30px 0; /* Scrollbar-hiding hack */
position: relative;
}
diff --git a/lib/codemirror.js b/lib/codemirror.js
index a69e2558da..ddf889a1a2 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -195,7 +195,7 @@ window.CodeMirror = (function() {
return /^\d+$/.test(val) ? val + "px" : val;
}
if (width != null) wrapper.style.width = interpret(width);
- if (height != null) scroller.style.height = interpret(height);
+ if (height != null) wrapper.style.height = interpret(height);
instance.refresh();
},
connect: function(type, f) {connect(this, type, f);},
@@ -1032,10 +1032,10 @@ window.CodeMirror = (function() {
var pt = paddingTop();
y1 += pt; y2 += pt;
var screen = scroller.clientHeight - scrollerCutOff, screentop = scroller.scrollTop, result = {};
- var docBottom = scroller.scrollHeight;
+ var docBottom = scroller.scrollHeight - scrollerCutOff;
var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10;
if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1);
- else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen;
+ else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2 - screen);
var screenw = scroller.clientWidth - scrollerCutOff, screenleft = scroller.scrollLeft;
x1 += gutters.offsetWidth; x2 += gutters.offsetWidth;
@@ -1065,6 +1065,7 @@ window.CodeMirror = (function() {
showingFrom = showingTo = displayOffset = 0;
return;
}
+
// Compute the new visible window
// If scrollTop is specified, use that to determine which lines
// to render instead of the current scrollbar position.
diff --git a/mode/less/index.html b/mode/less/index.html
index dd2f588fd9..7f27cf30e9 100644
--- a/mode/less/index.html
+++ b/mode/less/index.html
@@ -7,7 +7,7 @@
-
+
diff --git a/mode/vb/index.html b/mode/vb/index.html
index 55dfabfa56..1670c7d05b 100644
--- a/mode/vb/index.html
+++ b/mode/vb/index.html
@@ -8,8 +8,8 @@
diff --git a/test/test.js b/test/test.js
index f66513f07b..e9f51e3f01 100644
--- a/test/test.js
+++ b/test/test.js
@@ -333,7 +333,7 @@ testCM("scrollSnap", function(cm) {
cm.setCursor({line: 100, ch: 180});
cm.setCursor({line: 199, ch: 0});
info = cm.getScrollInfo();
- is(info.left == 0 && info.top > info.height - 100, "scrolled clean to bottom");
+ is(info.left == 0 && info.top + 2 > info.height - cm.getScrollerElement().clientHeight, "scrolled clean to bottom");
});
testCM("selectionPos", function(cm) {
@@ -410,14 +410,15 @@ testCM("weirdLinebreaks", function(cm) {
testCM("setSize", function(cm) {
cm.setSize(100, 100);
- is(cm.getWrapperElement().offsetWidth, 100);
- is(cm.getWrapperElement().offsetHeight, 100);
+ var wrap = cm.getWrapperElement();
+ is(wrap.offsetWidth, 100);
+ is(wrap.offsetHeight, 100);
cm.setSize("100%", "3em");
- is(cm.getWrapperElement().style.width, "100%");
- is(cm.getScrollerElement().style.height, "3em");
+ is(wrap.style.width, "100%");
+ is(wrap.style.height, "3em");
cm.setSize(null, 40);
- is(cm.getWrapperElement().style.width, "100%");
- is(cm.getScrollerElement().style.height, "40px");
+ is(wrap.style.width, "100%");
+ is(wrap.style.height, "40px");
});
testCM("hiddenLines", function(cm) {
From 40f48a0d4ee02ea534a8535e1d265700153ef8c1 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke Events
"scroll" (instance)"highlightComplete" (instance)"update" (instance)Writing CodeMirror Modes
which is given a state and should return a safe copy of that
state.
By default, CodeMirror will stop re-parsing
- a document as soon as it encounters a few lines that were
- highlighted the same in the old parse as in the new one. It is
- possible to provide an explicit way to test whether a state is
- equivalent to another one, which CodeMirror will use (instead of
- the unchanged-lines heuristic) to decide when to stop
- highlighting. You do this by providing
- a compareStates method on your mode object, which
- takes two state arguments and returns a boolean indicating whether
- they are equivalent. See the XML mode, which uses this to provide
- reliable highlighting of bad closing tags, as an example.
If you want your mode to provide smart indentation
(though the indentLine
method and the indentAuto
diff --git a/lib/codemirror.js b/lib/codemirror.js
index 17f2f0fd48..0618d1f42d 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -273,9 +273,9 @@ window.CodeMirror = (function() {
var measureLineMemo = [], measureLineMemoPos = 0;
// mode holds a mode API object. doc is the tree of Line objects,
- // work an array of lines that should be parsed, and history the
- // undo history (instance of History constructor).
- var mode, doc = new BranchChunk([new LeafChunk([new Line("", null, textHeight())])]), work, focused;
+ // frontier is the point up to which the content has been parsed,
+ // and history the undo history (instance of History constructor).
+ var mode, doc = new BranchChunk([new LeafChunk([new Line("", textHeight())])]), frontier = 0, focused;
loadMode();
// The selection. These are always maintained to point at valid
// positions. Inverted is used to remember that the user is
@@ -365,6 +365,8 @@ window.CodeMirror = (function() {
}
function lineContent(line, wrapAt) {
+ if (!line.styles)
+ line.highlight(mode, line.stateAfter = getStateBefore(lineNo(line)), options.tabSize);
return line.getContent(options.tabSize, wrapAt, options.lineWrapping);
}
@@ -835,19 +837,11 @@ window.CodeMirror = (function() {
if (recomputeMaxLength) updateMaxLine = true;
}
- // Add these lines to the work array, so that they will be
- // highlighted. Adjust work lines if lines were added/removed.
- var newWork = [], lendiff = newText.length - nlines - 1;
- for (var i = 0, l = work.length; i < l; ++i) {
- var task = work[i];
- if (task < from.line) newWork.push(task);
- else if (task > to.line) newWork.push(task + lendiff);
- }
- var hlEnd = from.line + Math.min(newText.length, 500);
- highlightLines(from.line, hlEnd);
- newWork.push(hlEnd);
- work = newWork;
- startWorker(100);
+ // Adjust frontier, schedule worker
+ frontier = Math.min(frontier, from.line);
+ startWorker(400);
+
+ var lendiff = newText.length - nlines - 1;
// Remember that these lines changed, for updating the display
changes.push({from: from.line, to: to.line + 1, diff: lendiff});
var changeObj = {from: from, to: to, text: newText};
@@ -1120,6 +1114,7 @@ window.CodeMirror = (function() {
signalLater(instance, "viewportChange", delayedCallbacks, instance, from, to);
showingFrom = from; showingTo = to;
displayOffset = heightAtLine(doc, from);
+ startWorker(100);
// Since this is all rather error prone, it is honoured with the
// only assertion in the whole file.
@@ -1579,8 +1574,8 @@ window.CodeMirror = (function() {
function loadMode() {
mode = CodeMirror.getMode(options, options.mode);
doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
- work = [0];
- startWorker();
+ frontier = 0;
+ startWorker(100);
}
function wrappingChanged(from, to) {
if (options.lineWrapping) {
@@ -2050,69 +2045,39 @@ window.CodeMirror = (function() {
return minline;
}
function getStateBefore(n) {
- var start = findStartLine(n), state = start && getLine(start-1).stateAfter;
+ var pos = findStartLine(n), state = pos && getLine(pos-1).stateAfter;
if (!state) state = startState(mode);
else state = copyState(mode, state);
- doc.iter(start, n, function(line) {
- line.highlight(mode, state, options.tabSize);
- line.stateAfter = copyState(mode, state);
+ doc.iter(pos, n, function(line) {
+ line.process(mode, state, options.tabSize);
+ line.stateAfter = (pos == n - 1 || pos % 5 == 0) ? copyState(mode, state) : null;
});
- if (start < n) changes.push({from: start, to: n});
- if (n < doc.size && !getLine(n).stateAfter) work.push(n);
return state;
}
- function highlightLines(start, end) {
- var state = getStateBefore(start);
- doc.iter(start, end, function(line) {
- line.highlight(mode, state, options.tabSize);
- line.stateAfter = copyState(mode, state);
- });
- }
function highlightWorker() {
- var end = +new Date + options.workTime;
- var foundWork = work.length;
- while (work.length) {
- if (!getLine(showingFrom).stateAfter) var task = showingFrom;
- else var task = work.pop();
- if (task >= doc.size) continue;
- var start = findStartLine(task), state = start && getLine(start-1).stateAfter;
- if (state) state = copyState(mode, state);
- else state = startState(mode);
-
- var unchanged = 0, compare = mode.compareStates, realChange = false,
- i = start, bail = false;
- doc.iter(i, doc.size, function(line) {
- var hadState = line.stateAfter;
- if (+new Date > end) {
- work.push(i);
- startWorker(options.workDelay);
- if (realChange) changes.push({from: task, to: i + 1});
- return (bail = true);
- }
- var changed = line.highlight(mode, state, options.tabSize);
- if (changed) realChange = true;
+ if (frontier >= showingTo) return;
+ var end = +new Date + options.workTime, state = copyState(mode, getStateBefore(frontier));
+ var startFrontier = frontier;
+ doc.iter(frontier, showingTo, function(line) {
+ if (frontier >= showingFrom) { // Visible
+ line.highlight(mode, state, options.tabSize);
line.stateAfter = copyState(mode, state);
- var done = null;
- if (compare) {
- var same = hadState && compare(hadState, state);
- if (same != Pass) done = !!same;
- }
- if (done == null) {
- if (changed !== false || !hadState) unchanged = 0;
- else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, "")))
- done = true;
- }
- if (done) return true;
- ++i;
- });
- if (bail) return;
- if (realChange) changes.push({from: task, to: i + 1});
- }
- if (foundWork) signal(instance, "highlightComplete", instance);
+ } else {
+ line.process(mode, state, options.tabSize);
+ line.stateAfter = frontier % 5 == 0 ? copyState(mode, state) : null;
+ }
+ ++frontier;
+ if (+new Date > end) {
+ startWorker(options.workDelay);
+ return true;
+ }
+ });
+ if (showingTo > startFrontier && frontier >= showingFrom)
+ operation(function() {changes.push({from: startFrontier, to: frontier});})();
}
function startWorker(time) {
- if (!work.length) return;
- highlight.set(time, operation(highlightWorker));
+ if (frontier < showingTo)
+ highlight.set(time, highlightWorker);
}
// Operations are used to wrap changes in such a way that each
@@ -2599,8 +2564,7 @@ window.CodeMirror = (function() {
// Line objects. These hold state related to a line, including
// highlighting info (the styles array).
- function Line(text, styles, height) {
- this.styles = styles || [text, null];
+ function Line(text, height) {
this.text = text;
this.height = height;
}
@@ -2617,16 +2581,11 @@ window.CodeMirror = (function() {
return ln;
};
Line.prototype = {
- // Replace a piece of a line, keeping the styles around it intact.
+ // Replace a piece of a line, keeping the markers intact.
replace: function(from, to_, text) {
- var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;
- copyStyles(0, from, this.styles, st);
- if (text) st.push(text, null);
- copyStyles(to, this.text.length, this.styles, st);
- this.styles = st;
+ var mk = this.marked, to = to_ == null ? this.text.length : to_;
this.text = this.text.slice(0, from) + text + this.text.slice(to);
- this.order = null;
- this.stateAfter = null;
+ this.order = this.stateAfter = this.styles = null;
if (mk) {
var diff = text.length - (to - from);
for (var i = 0; i < mk.length; ++i) {
@@ -2636,11 +2595,10 @@ window.CodeMirror = (function() {
}
}
},
- // Split a part off a line, keeping styles and markers intact.
+ // Split a part off a line, keeping markers intact.
split: function(pos, textBefore) {
- var st = [textBefore, null], mk = this.marked;
- copyStyles(pos, this.text.length, this.styles, st);
- var taken = new Line(textBefore + this.text.slice(pos), st, this.height);
+ var mk = this.marked;
+ var taken = new Line(textBefore + this.text.slice(pos), this.height);
if (mk) {
for (var i = 0; i < mk.length; ++i) {
var mark = mk[i];
@@ -2658,7 +2616,7 @@ window.CodeMirror = (function() {
var mylen = this.text.length, mk = line.marked, mymk = this.marked;
this.text += line.text;
this.order = this.order || line.order ? null : false;
- copyStyles(0, line.text.length, line.styles, this.styles);
+ this.styles = this.stateAfter = null;
if (mymk) {
for (var i = 0; i < mymk.length; ++i)
if (mymk[i].to == null) mymk[i].to = mylen;
@@ -2724,19 +2682,16 @@ window.CodeMirror = (function() {
// array, which contains alternating fragments of text and CSS
// classes.
highlight: function(mode, state, tabSize) {
- var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0;
- var changed = false, curWord = st[0], prevWord;
+ var stream = new StringStream(this.text, tabSize), st = this.styles || (this.styles = []);
+ var pos = st.length = 0;
if (this.text == "" && mode.blankLine) mode.blankLine(state);
while (!stream.eol()) {
- var style = mode.token(stream, state);
- var substr = this.text.slice(stream.start, stream.pos);
+ var style = mode.token(stream, state), substr = stream.current();
stream.start = stream.pos;
- if (pos && st[pos-1] == style)
+ if (pos && st[pos-1] == style) {
st[pos-2] += substr;
- else if (substr) {
- if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true;
+ } else if (substr) {
st[pos++] = substr; st[pos++] = style;
- prevWord = curWord; curWord = st[pos];
}
// Give up when line is ridiculously long
if (stream.pos > 5000) {
@@ -2744,12 +2699,14 @@ window.CodeMirror = (function() {
break;
}
}
- if (st.length != pos) {st.length = pos; changed = true;}
- if (pos && st[pos-2] != prevWord) changed = true;
- // Short lines with simple highlights return null, and are
- // counted as changed by the driver because they are likely to
- // highlight the same way in various contexts.
- return changed || (st.length < 5 && this.text.length < 10 ? null : false);
+ },
+ process: function(mode, state, tabSize) {
+ var stream = new StringStream(this.text, tabSize);
+ if (this.text == "" && mode.blankLine) mode.blankLine(state);
+ while (!stream.eol() && stream.pos <= 5000) {
+ mode.token(stream, state);
+ stream.start = stream.pos;
+ }
},
// Fetch the parser token for a given character. Useful for hacks
// that want to inspect the mode state (say, for completion).
@@ -2897,20 +2854,6 @@ window.CodeMirror = (function() {
for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this);
}
};
- // Utility used by replace and split above
- function copyStyles(from, to, source, dest) {
- for (var i = 0, pos = 0, state = 0; pos < to; i+=2) {
- var part = source[i], end = pos + part.length;
- if (state == 0) {
- if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]);
- if (end >= from) state = 1;
- } else if (state == 1) {
- if (end > to) dest.push(part.slice(0, to - pos), source[i+1]);
- else dest.push(part, source[i+1]);
- }
- pos = end;
- }
- }
// Data structure that holds the sequence of lines.
function LeafChunk(lines) {
diff --git a/lib/util/multiplex.js b/lib/util/multiplex.js
index 755588b9c4..b7c1838f62 100644
--- a/lib/util/multiplex.js
+++ b/lib/util/multiplex.js
@@ -68,14 +68,6 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
return mode.indent(state.innerActive ? state.inner : state.outer, textAfter);
},
- compareStates: function(a, b) {
- if (a.innerActive != b.innerActive) return false;
- var mode = a.innerActive || outer;
- if (!mode.compareStates) return CodeMirror.Pass;
- return mode.compareStates(a.innerActive ? a.inner : a.outer,
- b.innerActive ? b.inner : b.outer);
- },
-
electricChars: outer.electricChars
};
};
diff --git a/mode/haxe/haxe.js b/mode/haxe/haxe.js
index ea8bd834e6..64f4eb3ff8 100644
--- a/mode/haxe/haxe.js
+++ b/mode/haxe/haxe.js
@@ -421,9 +421,6 @@ CodeMirror.defineMode("haxe", function(config, parserConfig) {
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
- compareStates: function(state1, state2) {
- return (state1.localVars == state2.localVars) && (state1.context == state2.context);
- },
electricChars: "{}"
};
diff --git a/mode/htmlmixed/htmlmixed.js b/mode/htmlmixed/htmlmixed.js
index 260a6d0dfb..5f2fc238c9 100644
--- a/mode/htmlmixed/htmlmixed.js
+++ b/mode/htmlmixed/htmlmixed.js
@@ -76,12 +76,6 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
return cssMode.indent(state.localState, textAfter);
},
- compareStates: function(a, b) {
- if (a.mode != b.mode) return false;
- if (a.localState) return CodeMirror.Pass;
- return htmlMode.compareStates(a.htmlState, b.htmlState);
- },
-
electricChars: "/{}:"
};
}, "xml", "javascript", "css");
diff --git a/mode/tiki/tiki.js b/mode/tiki/tiki.js
index 24bf0fbfe5..af83dc1b5b 100644
--- a/mode/tiki/tiki.js
+++ b/mode/tiki/tiki.js
@@ -301,13 +301,6 @@ CodeMirror.defineMode('tiki', function(config, parserConfig) {
if (context) return context.indent + indentUnit;
else return 0;
},
- compareStates: function(a, b) {
- if (a.indented != b.indented || a.pluginName != b.pluginName) return false;
- for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) {
- if (!ca || !cb) return ca == cb;
- if (ca.pluginName != cb.pluginName) return false;
- }
- },
electricChars: "/"
};
});
diff --git a/mode/xml/xml.js b/mode/xml/xml.js
index cd69f62fd5..860e368f5b 100644
--- a/mode/xml/xml.js
+++ b/mode/xml/xml.js
@@ -308,14 +308,6 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
else return 0;
},
- compareStates: function(a, b) {
- if (a.indented != b.indented || a.tokenize != b.tokenize) return false;
- for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) {
- if (!ca || !cb) return ca == cb;
- if (ca.tagName != cb.tagName || ca.indent != cb.indent) return false;
- }
- },
-
electricChars: "/"
};
});
From b914a9652e39a72e62a30acb02da09d68fae527f Mon Sep 17 00:00:00 2001
From: Brandon Frohs Tests for the Markdown Mode
+ Basics
+
+
+ Code
+
+
+ Headers
+
+
+ Blockquotes
+
+
+ Horizontal rules
+
+
+ Links
+
+
+ Emphasis
+
+
+ Escaping
+
+
+ Summary
+
+
+
+
+
From 92e0114a54843792d88c68c49a57662343d05c15 Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke CodeMirror: XQuery mode
From 7ee24811ca2d4ca6c7feac743d8c65b9a994584b Mon Sep 17 00:00:00 2001
From: Marijn Haverbeke CodeMirror: Variable Height Demo
-
gutterMarkers is an
+ object mapping gutter IDs to marker elements,
+ and widgets is an array
+ of line widgets attached to this
+ line.
getLineHandle(num) → lineHandleremoveChild on its parent).
+ addLineWidget(line, node, inline) → objectline should be either an integer or a
+ line handle, and node should be a DOM node, which
+ will be displayed below the given line. When inline
+ is true, the widget will not overlap with the gutter, and scroll
+ horizontally with the editor. Note that the widget node will
+ become a descendant of nodes with CodeMirror-specific CSS
+ classes, and those classes might in some cases affect it. This
+ method returns an object that represents the widget placement,
+ which can be passed to removeLineWidget.removeLineWidget(widget)lineCount() → numbergetLineHandle(num) → lineHandlegetLineNumber(handle) → integernull when it is no longer in the
+ document).getViewport() → object{from, to} object indicating the
start (inclusive) and end (exclusive) of the currently displayed
@@ -738,8 +743,10 @@ removeLineWidget.line property pointing at the line
+ handle that it is associated with, and it can be passed
+ to removeLineWidget to remove the widget.
removeLineWidget(widget)A CodeMirror instance emits a number of events, which allow
client code to react to various situations. These are registered
- with the connect method (and
- removed with the disconnect
+ with the on method (and
+ removed with the off
method). These are the events that fire on the instance object.
The name of the event is followed by the arguments that will be
passed to the handler. The instance argument always
@@ -334,10 +334,10 @@
It is also possible to register events +
It is also possible to register events
on other objects. Line handles (as returned by, for
example, getLineHandle)
- can be listened on with CodeMirror.connect(handle, "delete",
+ can be listened on with CodeMirror.on(handle, "delete",
myFunc). They support the following events:
connect(type, func)on(type, func)CodeMirror.connect(object, type, func) version
+ a CodeMirror.on(object, type, func) version
that allows registering of events on any object.disconnect(type, func)off(type, func)CodeMirror.disconnect(object, type,
+ equivalent CodeMirror.off(object, type,
func) also exists.cursorCoords(start, mode) → objectFinally, the CodeMirror object
- itself has a method fromTextArea. This takes a
+
The CodeMirror object itself provides
+ several useful properties. Firstly, its version
+ property contains a string that indicates the version of the
+ library. For releases, this simply
+ contains "major.minor" (for
+ example "2.33". For beta versions, " B"
+ (space, capital B) is added at the end of the string, for
+ development snapshots, " +" (space, plus) is
+ added.
The CodeMirror.fromTextArea
+ method provides another way to initialize an editor. It takes a
textarea DOM node as first argument and an optional configuration
object as second. It will replace the textarea with a CodeMirror
instance, and wire up the form of that textarea (if any) to make
sure the editor contents are put into the textarea when the form
- is submitted. A CodeMirror instance created this way has two
+ is submitted. A CodeMirror instance created this way has three
additional methods:
This demo runs JSHint over the code in the editor (which is the script used on this page), and -inserts line widgets to +inserts line widgets to display the warnings that JSHint comes up with.
From 021a0d31a92aa4f90b90c8f098915459277a270e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke