Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Make undo/redo preserve text markers and bookmarks

Cleans up the implementation of marked ranges, makes the data
structure for markers-within-a-line persistant, and attaches them to
the lines stored in the undo history when necessary.

Closes #675
  • Loading branch information...
commit 0d3df7ef16add9a11f3632bcbd048a4a243187bf 1 parent 328780d
@marijnh marijnh authored
Showing with 241 additions and 264 deletions.
  1. +9 −3 doc/manual.html
  2. +210 −257 lib/codemirror.js
  3. +22 −4 test/test.js
View
12 doc/manual.html
@@ -590,14 +590,20 @@ <h2 id="api">Programming API</h2>
<dt><code>state</code></dt><dd>The mode's state at the end of this token.</dd>
</dl></dd>
- <dt id="markText"><code>markText(from, to, className) → object</code></dt>
+ <dt id="markText"><code>markText(from, to, className, options) → object</code></dt>
<dd>Can be used to mark a range of text with a specific CSS
class name. <code>from</code> and <code>to</code> should
- be <code>{line, ch}</code> objects. The method will return an
+ be <code>{line, ch}</code> objects. The <code>options</code>
+ parameter is optional, and can be an object
+ with <code>inclusiveLeft</code> and <code>inclusiveRight</code>
+ properties, which determine whether typing at the left or right
+ of the marker will cause the new text to become part of the
+ marker (the default is false for both). The method will return an
object with two methods, <code>clear()</code>, which removes the
mark, and <code>find()</code>, which returns a <code>{from,
to}</code> (both document positions), indicating the current
- position of the marked range.</dd>
+ position of the marked range, or <code>undefined</code> if the
+ marker is no longer in the document.</dd>
<dt id="setBookmark"><code>setBookmark(pos) → object</code></dt>
<dd>Inserts a bookmark, a handle that follows the text around it
View
467 lib/codemirror.js
@@ -702,13 +702,16 @@ window.CodeMirror = (function() {
// Afterwards, set the selection to selFrom, selTo.
function updateLines(from, to, newText, selFrom, selTo) {
if (suppressEdits) return;
+ var old = [];
+ doc.iter(from.line, to.line + 1, function(line) {
+ old.push(newHL(line.text, line.markedSpans));
+ });
if (history) {
- var old = [];
- doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); });
history.addChange(from.line, newText.length, old);
while (history.done.length > options.undoDepth) history.done.shift();
}
- updateLinesNoUndo(from, to, newText, selFrom, selTo);
+ var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(old[old.length-1]), from.ch, to.ch, newText);
+ updateLinesNoUndo(from, to, lines, selFrom, selTo);
}
function unredoHelper(from, to) {
if (!from.length) return;
@@ -716,11 +719,12 @@ window.CodeMirror = (function() {
for (var i = set.length - 1; i >= 0; i -= 1) {
var change = set[i];
var replaced = [], end = change.start + change.added;
- doc.iter(change.start, end, function(line) { replaced.push(line.text); });
+ doc.iter(change.start, end, function(line) { replaced.push(newHL(line.text, line.markedSpans)); });
out.push({start: change.start, added: change.old.length, old: replaced});
var pos = {line: change.start + change.old.length - 1,
- ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])};
- updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos);
+ ch: editEnd(hlText(replaced[replaced.length-1]), hlText(change.old[change.old.length-1]))};
+ updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length},
+ change.old, pos, pos);
}
updateInput = true;
to.push(out);
@@ -728,66 +732,59 @@ window.CodeMirror = (function() {
function undo() {unredoHelper(history.done, history.undone);}
function redo() {unredoHelper(history.undone, history.done);}
- function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
+ function updateLinesNoUndo(from, to, lines, selFrom, selTo) {
if (suppressEdits) return;
var recomputeMaxLength = false, maxLineLength = maxLine.text.length;
if (!options.lineWrapping)
doc.iter(from.line, to.line + 1, function(line) {
if (!line.hidden && line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
});
- if (from.line != to.line || newText.length > 1) gutterDirty = true;
+ if (from.line != to.line || lines.length > 1) gutterDirty = true;
var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
- // First adjust the line structure, taking some care to leave highlighting intact.
- if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") {
+ var lastHL = lines[lines.length-1];
+
+ // First adjust the line structure
+ if (from.ch == 0 && to.ch == 0 && hlText(lastHL) == "") {
// This is a whole-line replace. Treated specially to make
// sure line objects move the way they are supposed to.
var added = [], prevLine = null;
- if (from.line) {
- prevLine = getLine(from.line - 1);
- prevLine.fixMarkEnds(lastLine);
- } else lastLine.fixMarkStarts();
- for (var i = 0, e = newText.length - 1; i < e; ++i)
- added.push(Line.inheritMarks(newText[i], prevLine));
+ for (var i = 0, e = lines.length - 1; i < e; ++i)
+ added.push(new Line(hlText(lines[i]), hlSpans(lines[i])));
+ lastLine.update(lastLine.text, hlSpans(lastHL));
if (nlines) doc.remove(from.line, nlines, callbacks);
if (added.length) doc.insert(from.line, added);
} else if (firstLine == lastLine) {
- if (newText.length == 1)
- firstLine.replace(from.ch, to.ch, newText[0]);
- else {
- lastLine = firstLine.split(to.ch, newText[newText.length-1]);
- firstLine.replace(from.ch, null, newText[0]);
- firstLine.fixMarkEnds(lastLine);
- var added = [];
- for (var i = 1, e = newText.length - 1; i < e; ++i)
- added.push(Line.inheritMarks(newText[i], firstLine));
- added.push(lastLine);
+ if (lines.length == 1) {
+ firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + firstLine.text.slice(to.ch), hlSpans(lines[0]));
+ } else {
+ for (var added = [], i = 1, e = lines.length - 1; i < e; ++i)
+ added.push(new Line(hlText(lines[i]), hlSpans(lines[i])));
+ added.push(new Line(hlText(lastHL) + firstLine.text.slice(to.ch), hlSpans(lastHL)));
+ firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0]));
doc.insert(from.line + 1, added);
}
- } else if (newText.length == 1) {
- firstLine.replace(from.ch, null, newText[0]);
- lastLine.replace(null, to.ch, "");
- firstLine.append(lastLine);
+ } else if (lines.length == 1) {
+ firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + lastLine.text.slice(to.ch), hlSpans(lines[0]));
doc.remove(from.line + 1, nlines, callbacks);
} else {
var added = [];
- firstLine.replace(from.ch, null, newText[0]);
- lastLine.replace(null, to.ch, newText[newText.length-1]);
- firstLine.fixMarkEnds(lastLine);
- for (var i = 1, e = newText.length - 1; i < e; ++i)
- added.push(Line.inheritMarks(newText[i], firstLine));
+ firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0]));
+ lastLine.update(hlText(lastHL) + lastLine.text.slice(to.ch), hlSpans(lastHL));
+ for (var i = 1, e = lines.length - 1; i < e; ++i)
+ added.push(new Line(hlText(lines[i]), hlSpans(lines[i])));
if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks);
doc.insert(from.line + 1, added);
}
if (options.lineWrapping) {
var perLine = Math.max(5, scroller.clientWidth / charWidth() - 3);
- doc.iter(from.line, from.line + newText.length, function(line) {
+ doc.iter(from.line, from.line + lines.length, function(line) {
if (line.hidden) return;
var guess = Math.ceil(line.text.length / perLine) || 1;
if (guess != line.height) updateLineHeight(line, guess);
});
} else {
- doc.iter(from.line, from.line + newText.length, function(line) {
+ doc.iter(from.line, from.line + lines.length, function(line) {
var l = line.text;
if (!line.hidden && l.length > maxLineLength) {
maxLine = line; maxLineLength = l.length; maxLineChanged = true;
@@ -801,14 +798,20 @@ window.CodeMirror = (function() {
frontier = Math.min(frontier, from.line);
startWorker(400);
- var lendiff = newText.length - nlines - 1;
+ var lendiff = lines.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};
- if (textChanged) {
- for (var cur = textChanged; cur.next; cur = cur.next) {}
- cur.next = changeObj;
- } else textChanged = changeObj;
+ if (options.onChange) {
+ // Normalize lines to contain only strings, since that's what
+ // the change event handler expects
+ for (var i = 0; i < lines.length; ++i)
+ if (typeof lines[i] != "string") lines[i] = lines[i].text;
+ var changeObj = {from: from, to: to, text: lines};
+ if (textChanged) {
+ for (var cur = textChanged; cur.next; cur = cur.next) {}
+ cur.next = changeObj;
+ } else textChanged = changeObj;
+ }
// Update the selection
function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}
@@ -1488,74 +1491,71 @@ window.CodeMirror = (function() {
(style ? " cm-keymap-" + style : "");
}
- function TextMarker() { this.set = []; }
+ function TextMarker(type, style) { this.lines = []; this.type = type; if (style) this.style = style; }
TextMarker.prototype.clear = operation(function() {
var min = Infinity, max = -Infinity;
- for (var i = 0, e = this.set.length; i < e; ++i) {
- var line = this.set[i], mk = line.marked;
- if (!mk || !line.parent) continue;
- var lineN = lineNo(line);
- min = Math.min(min, lineN); max = Math.max(max, lineN);
- for (var j = 0; j < mk.length; ++j)
- if (mk[j].marker == this) mk.splice(j--, 1);
+ for (var i = 0; i < this.lines.length; ++i) {
+ var line = this.lines[i];
+ var span = getMarkedSpanFor(line.markedSpans, this, true);
+ if (span.from != null || span.to != null) {
+ var lineN = lineNo(line);
+ min = Math.min(min, lineN); max = Math.max(max, lineN);
+ }
}
if (min != Infinity)
changes.push({from: min, to: max + 1});
+ this.lines.length = 0;
});
TextMarker.prototype.find = function() {
var from, to;
- for (var i = 0, e = this.set.length; i < e; ++i) {
- var line = this.set[i], mk = line.marked;
- for (var j = 0; j < mk.length; ++j) {
- var mark = mk[j];
- if (mark.marker == this) {
- if (mark.from != null || mark.to != null) {
- var found = lineNo(line);
- if (found != null) {
- if (mark.from != null) from = {line: found, ch: mark.from};
- if (mark.to != null) to = {line: found, ch: mark.to};
- }
- }
- }
+ for (var i = 0; i < this.lines.length; ++i) {
+ var line = this.lines[i];
+ var span = getMarkedSpanFor(line.markedSpans, this);
+ if (span.from != null || span.to != null) {
+ var found = lineNo(line);
+ if (span.from != null) from = {line: found, ch: span.from};
+ if (span.to != null) to = {line: found, ch: span.to};
}
}
- return {from: from, to: to};
+ if (this.type == "bookmark") return from;
+ return from && {from: from, to: to};
};
- function markText(from, to, className) {
+ function markText(from, to, className, options) {
from = clipPos(from); to = clipPos(to);
- var tm = new TextMarker();
- if (!posLess(from, to)) return tm;
- function add(line, from, to, className) {
- getLine(line).addMark(new MarkedText(from, to, className, tm));
- }
- if (from.line == to.line) add(from.line, from.ch, to.ch, className);
- else {
- add(from.line, from.ch, null, className);
- for (var i = from.line + 1, e = to.line; i < e; ++i)
- add(i, null, null, className);
- add(to.line, null, to.ch, className);
- }
+ var marker = new TextMarker("range", className);
+ if (options && options.inclusiveLeft) marker.inclusiveLeft = true;
+ if (options && options.inclusiveRight) marker.inclusiveRight = true;
+ var curLine = from.line;
+ doc.iter(curLine, to.line + 1, function(line) {
+ var span = {from: curLine == from.line ? from.ch : null,
+ to: curLine == to.line ? to.ch : null,
+ marker: marker};
+ (line.markedSpans || (line.markedSpans = [])).push(span);
+ marker.lines.push(line);
+ ++curLine;
+ });
changes.push({from: from.line, to: to.line + 1});
- return tm;
+ return marker;
}
function setBookmark(pos) {
pos = clipPos(pos);
- var bm = new Bookmark(pos.ch);
- getLine(pos.line).addMark(bm);
- return bm;
+ var marker = new TextMarker("bookmark"), line = getLine(pos.line);
+ var span = {from: pos.ch, to: pos.ch, marker: marker};
+ (line.markedSpans || (line.markedSpans = [])).push(span);
+ marker.lines.push(line);
+ return marker;
}
function findMarksAt(pos) {
pos = clipPos(pos);
- var markers = [], marked = getLine(pos.line).marked;
- if (!marked) return markers;
- for (var i = 0, e = marked.length; i < e; ++i) {
- var m = marked[i];
- if ((m.from == null || m.from <= pos.ch) &&
- (m.to == null || m.to >= pos.ch))
- markers.push(m.marker || m);
+ var markers = [], spans = getLine(pos.line).markedSpans;
+ if (spans) for (var i = 0; i < spans.length; ++i) {
+ var span = spans[i];
+ if ((span.from == null || span.from <= pos.ch) &&
+ (span.to == null || span.to >= pos.ch))
+ markers.push(span.marker);
}
return markers;
}
@@ -2316,69 +2316,123 @@ window.CodeMirror = (function() {
};
CodeMirror.StringStream = StringStream;
- function MarkedText(from, to, className, marker) {
- this.from = from; this.to = to; this.style = className; this.marker = marker;
+ function MarkedSpan(from, to, marker) {
+ this.from = from; this.to = to; this.marker = marker;
}
- MarkedText.prototype = {
- attach: function(line) { this.marker.set.push(line); },
- detach: function(line) {
- var ix = indexOf(this.marker.set, line);
- if (ix > -1) this.marker.set.splice(ix, 1);
- },
- split: function(pos, lenBefore) {
- if (this.to <= pos && this.to != null) return null;
- var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore;
- var to = this.to == null ? null : this.to - pos + lenBefore;
- return new MarkedText(from, to, this.style, this.marker);
- },
- dup: function() { return new MarkedText(null, null, this.style, this.marker); },
- clipTo: function(fromOpen, from, toOpen, to, diff) {
- if (fromOpen && to > this.from && (to < this.to || this.to == null))
- this.from = null;
- else if (this.from != null && this.from >= from)
- this.from = Math.max(to, this.from) + diff;
- if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null))
- this.to = null;
- else if (this.to != null && this.to > from)
- this.to = to < this.to ? this.to + diff : from;
- },
- isDead: function() { return this.from != null && this.to != null && this.from >= this.to; },
- sameSet: function(x) { return this.marker == x.marker; }
- };
- function Bookmark(pos) {
- this.from = pos; this.to = pos; this.line = null;
+ function getMarkedSpanFor(spans, marker, del) {
+ if (spans) for (var i = 0; i < spans.length; ++i) {
+ var span = spans[i];
+ if (span.marker == marker) {
+ if (del) spans.splice(i, 1);
+ return span;
+ }
+ }
}
- Bookmark.prototype = {
- attach: function(line) { this.line = line; },
- detach: function(line) { if (this.line == line) this.line = null; },
- split: function(pos, lenBefore) {
- if (pos < this.from) {
- this.from = this.to = (this.from - pos) + lenBefore;
- return this;
+
+ function markedSpansBefore(old, startCh, endCh) {
+ if (old) for (var i = 0, nw; i < old.length; ++i) {
+ var span = old[i], marker = span.marker;
+ var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);
+ if (startsBefore || marker.type == "bookmark" && span.from == startCh && span.from != endCh) {
+ var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);
+ (nw || (nw = [])).push({from: span.from,
+ to: endsAfter ? null : span.to,
+ marker: marker});
}
- },
- isDead: function() { return this.from > this.to; },
- clipTo: function(fromOpen, from, toOpen, to, diff) {
- if ((fromOpen || from < this.from) && (toOpen || to > this.to)) {
- this.from = 0; this.to = -1;
- } else if (this.from > from) {
- this.from = this.to = Math.max(to, this.from) + diff;
+ }
+ return nw;
+ }
+
+ function markedSpansAfter(old, endCh) {
+ if (old) for (var i = 0, nw; i < old.length; ++i) {
+ var span = old[i], marker = span.marker;
+ var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);
+ if (endsAfter || marker.type == "bookmark" && span.from == endCh) {
+ var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh);
+ (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh,
+ to: span.to == null ? null : span.to - endCh,
+ marker: marker});
}
- },
- sameSet: function(x) { return false; },
- find: function() {
- if (!this.line || !this.line.parent) return null;
- return {line: lineNo(this.line), ch: this.from};
- },
- clear: function() {
- if (this.line) {
- var found = indexOf(this.line.marked, this);
- if (found != -1) this.line.marked.splice(found, 1);
- this.line = null;
+ }
+ return nw;
+ }
+
+ function updateMarkedSpans(oldFirst, oldLast, startCh, endCh, newText) {
+ if (!oldFirst && !oldLast) return newText;
+ // Get the spans that 'stick out' on both sides
+ var first = markedSpansBefore(oldFirst, startCh);
+ var last = markedSpansAfter(oldLast, endCh);
+
+ // Next, merge those two ends
+ var sameLine = newText.length == 1, offset = newText[newText.length-1].length + (sameLine ? startCh : 0);
+ if (first) {
+ // Fix up .to properties of first
+ for (var i = 0; i < first.length; ++i) {
+ var span = first[i];
+ if (span.to == null) {
+ var found = getMarkedSpanFor(last, span.marker);
+ if (!found) span.to = startCh;
+ else if (sameLine) span.to = found.to == null ? null : found.to + offset;
+ }
+ }
+ }
+ if (last) {
+ // Fix up .from in last (or move them into first in case of sameLine)
+ for (var i = 0; i < last.length; ++i) {
+ var span = last[i];
+ if (span.to != null) span.to += offset;
+ if (span.from == null) {
+ var found = getMarkedSpanFor(first, span.marker);
+ if (!found) {
+ span.from = offset;
+ if (sameLine) (first || (first = [])).push(span);
+ }
+ } else {
+ span.from += offset;
+ if (sameLine) (first || (first = [])).push(span);
+ }
}
}
- };
+
+ var newMarkers = [newHL(newText[0], first)];
+ if (!sameLine) {
+ // Fill gap with whole-line-spans
+ var gap = newText.length - 2, gapMarkers;
+ if (gap > 0 && first)
+ for (var i = 0; i < first.length; ++i)
+ if (first[i].to == null)
+ (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker});
+ for (var i = 0; i < gap; ++i)
+ newMarkers.push(newHL(newText[i+1], gapMarkers));
+ newMarkers.push(newHL(newText[newText.length-1], last));
+ }
+ return newMarkers;
+ }
+
+ // hl stands for history-line, a data structure that can be either a
+ // string (line without markers) or a {text, markedSpans} object.
+ function hlText(val) { return typeof val == "string" ? val : val.text; }
+ function hlSpans(val) { return typeof val == "string" ? null : val.markedSpans; }
+ function newHL(text, spans) { return spans ? {text: text, markedSpans: spans} : text; }
+
+ function detachMarkedSpans(line) {
+ var spans = line.markedSpans;
+ if (!spans) return;
+ for (var i = 0; i < spans.length; ++i) {
+ var lines = spans[i].marker.lines;
+ var ix = indexOf(lines, line);
+ lines.splice(ix, 1);
+ }
+ line.markedSpans = null;
+ }
+
+ function attachMarkedSpans(line, spans) {
+ if (!spans) return;
+ for (var i = 0; i < spans.length; ++i)
+ var marker = spans[i].marker.lines.push(line);
+ line.markedSpans = spans;
+ }
// When measuring the position of the end of a line, different
// browsers require different approaches. If an empty span is added,
@@ -2392,118 +2446,17 @@ window.CodeMirror = (function() {
// Line objects. These hold state related to a line, including
// highlighting info (the styles array).
- function Line(text) {
+ function Line(text, markedSpans) {
this.text = text;
this.height = 1;
+ attachMarkedSpans(this, markedSpans);
}
- Line.inheritMarks = function(text, orig) {
- var ln = new Line(text), mk = orig && orig.marked;
- if (mk) {
- for (var i = 0; i < mk.length; ++i) {
- if (mk[i].to == null && mk[i].style) {
- var newmk = ln.marked || (ln.marked = []), mark = mk[i];
- var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln);
- }
- }
- }
- return ln;
- };
Line.prototype = {
- // Replace a piece of a line, keeping the markers intact.
- replace: function(from, to_, text) {
- var mk = this.marked, to = to_ == null ? this.text.length : to_;
- this.text = this.text.slice(0, from) + text + this.text.slice(to);
+ update: function(text, markedSpans) {
+ this.text = text;
this.stateAfter = this.styles = null;
- if (mk) {
- var diff = text.length - (to - from);
- for (var i = 0; i < mk.length; ++i) {
- var mark = mk[i];
- mark.clipTo(from == null, from || 0, to_ == null, to, diff);
- if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);}
- }
- }
- },
- // Split a part off a line, keeping markers intact.
- split: function(pos, textBefore) {
- var mk = this.marked;
- var taken = new Line(textBefore + this.text.slice(pos));
- if (mk) {
- for (var i = 0; i < mk.length; ++i) {
- var mark = mk[i];
- var newmark = mark.split(pos, textBefore.length);
- if (newmark) {
- if (!taken.marked) taken.marked = [];
- taken.marked.push(newmark); newmark.attach(taken);
- if (newmark == mark) mk.splice(i--, 1);
- }
- }
- }
- return taken;
- },
- append: function(line) {
- var mylen = this.text.length, mk = line.marked, mymk = this.marked;
- this.text += line.text;
- this.styles = this.stateAfter = null;
- if (mymk) {
- for (var i = 0; i < mymk.length; ++i)
- if (mymk[i].to == null) mymk[i].to = mylen;
- }
- if (mk && mk.length) {
- if (!mymk) this.marked = mymk = [];
- outer: for (var i = 0; i < mk.length; ++i) {
- var mark = mk[i];
- if (!mark.from) {
- for (var j = 0; j < mymk.length; ++j) {
- var mymark = mymk[j];
- if (mymark.to == mylen && mymark.sameSet(mark)) {
- mymark.to = mark.to == null ? null : mark.to + mylen;
- if (mymark.isDead()) {
- mymark.detach(this);
- mk.splice(i--, 1);
- }
- continue outer;
- }
- }
- }
- mymk.push(mark);
- mark.attach(this);
- mark.from += mylen;
- if (mark.to != null) mark.to += mylen;
- }
- }
- },
- fixMarkEnds: function(other) {
- var mk = this.marked, omk = other.marked;
- if (!mk) return;
- outer: for (var i = 0; i < mk.length; ++i) {
- var mark = mk[i], close = mark.to == null;
- if (close && omk) {
- for (var j = 0; j < omk.length; ++j) {
- var om = omk[j];
- if (!om.sameSet(mark) || om.from != null) continue;
- if (mark.from == this.text.length && om.to == 0) {
- omk.splice(j, 1);
- mk.splice(i--, 1);
- continue outer;
- } else {
- close = false; break;
- }
- }
- }
- if (close) mark.to = this.text.length;
- }
- },
- fixMarkStarts: function() {
- var mk = this.marked;
- if (!mk) return;
- for (var i = 0; i < mk.length; ++i)
- if (mk[i].from == null) mk[i].from = 0;
- },
- addMark: function(mark) {
- mark.attach(this);
- if (this.marked == null) this.marked = [];
- this.marked.push(mark);
- this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);});
+ detachMarkedSpans(this);
+ attachMarkedSpans(this, markedSpans);
},
// Run the given mode's parser over a line, update the styles
// array, which contains alternating fragments of text and CSS
@@ -2620,7 +2573,7 @@ window.CodeMirror = (function() {
};
}
- var st = this.styles, allText = this.text, marked = this.marked;
+ var st = this.styles, allText = this.text, marked = this.markedSpans;
var len = allText.length;
function styleToClass(style) {
if (!style) return null;
@@ -2636,13 +2589,14 @@ window.CodeMirror = (function() {
span(pre, str, styleToClass(style));
}
} else {
+ marked.sort(function(a, b) { return a.from - b.from; });
var pos = 0, i = 0, text = "", style, sg = 0;
var nextChange = marked[0].from || 0, marks = [], markpos = 0;
var advanceMarks = function() {
var m;
while (markpos < marked.length &&
((m = marked[markpos]).from == pos || m.from == null)) {
- if (m.style != null) marks.push(m);
+ if (m.marker.style != null) marks.push(m);
++markpos;
}
nextChange = markpos < marked.length ? marked[markpos].from : Infinity;
@@ -2662,7 +2616,7 @@ window.CodeMirror = (function() {
var end = pos + text.length;
var appliedStyle = style;
for (var j = 0; j < marks.length; ++j)
- appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].style;
+ appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].marker.style;
span(pre, end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
pos = end;
@@ -2675,8 +2629,7 @@ window.CodeMirror = (function() {
},
cleanUp: function() {
this.parent = null;
- if (this.marked)
- for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this);
+ detachMarkedSpans(this);
}
};
View
26 test/test.js
@@ -257,13 +257,14 @@ testCM("markTextSingleLine", function(cm) {
var r = cm.markText({line: 0, ch: 3}, {line: 0, ch: 6}, "foo");
cm.replaceRange(test.c, {line: 0, ch: test.a}, {line: 0, ch: test.b});
var f = r.find();
- eq(f.from && f.from.ch, test.f); eq(f.to && f.to.ch, test.t);
+ eq(f && f.from.ch, test.f); eq(f && f.to.ch, test.t);
});
});
testCM("markTextMultiLine", function(cm) {
function p(v) { return v && {line: v[0], ch: v[1]}; }
forEach([{a: [0, 0], b: [0, 5], c: "", f: [0, 0], t: [2, 5]},
+ {a: [0, 0], b: [0, 5], c: "foo\n", f: [1, 0], t: [3, 5]},
{a: [0, 1], b: [0, 10], c: "", f: [0, 1], t: [2, 5]},
{a: [0, 5], b: [0, 6], c: "x", f: [0, 6], t: [2, 5]},
{a: [0, 0], b: [1, 0], c: "", f: [0, 0], t: [1, 5]},
@@ -275,16 +276,33 @@ testCM("markTextMultiLine", function(cm) {
{a: [1, 5], b: [2, 5], c: "", f: [0, 5], t: [1, 5]},
{a: [2, 0], b: [2, 3], c: "", f: [0, 5], t: [2, 2]},
{a: [2, 5], b: [3, 0], c: "a\nb", f: [0, 5], t: [2, 5]},
- {a: [2, 3], b: [3, 0], c: "x", f: [0, 5], t: [2, 4]},
+ {a: [2, 3], b: [3, 0], c: "x", f: [0, 5], t: [2, 3]},
{a: [1, 1], b: [1, 9], c: "1\n2\n3", f: [0, 5], t: [4, 5]}], function(test) {
cm.setValue("aaaaaaaaaa\nbbbbbbbbbb\ncccccccccc\ndddddddd\n");
- var r = cm.markText({line: 0, ch: 5}, {line: 2, ch: 5}, "foo");
+ var r = cm.markText({line: 0, ch: 5}, {line: 2, ch: 5}, "CodeMirror-matchingbracket");
cm.replaceRange(test.c, p(test.a), p(test.b));
var f = r.find();
- eqPos(f.from, p(test.f)); eqPos(f.to, p(test.t));
+ eqPos(f && f.from, p(test.f)); eqPos(f && f.to, p(test.t));
});
});
+testCM("markTextUndo", function(cm) {
+ var marker1 = cm.markText({line: 0, ch: 1}, {line: 0, ch: 3}, "CodeMirror-matchingbracket");
+ var marker2 = cm.markText({line: 0, ch: 0}, {line: 2, ch: 1}, "CodeMirror-matchingbracket");
+ var bookmark = cm.setBookmark({line: 1, ch: 5});
+ cm.replaceRange("foo", {line: 0, ch: 2});
+ cm.replaceRange("bar\baz\bug\n", {line: 2, ch: 0}, {line: 3, ch: 0});
+ cm.setValue("");
+ eq(marker1.find(), null); eq(marker2.find(), null); eq(bookmark.find(), null);
+ cm.undo();
+ eqPos(bookmark.find(), {line: 1, ch: 5});
+ cm.undo(); cm.undo();
+ var m1Pos = marker1.find(), m2Pos = marker2.find();
+ eqPos(m1Pos.from, {line: 0, ch: 1}); eqPos(m1Pos.to, {line: 0, ch: 3});
+ eqPos(m2Pos.from, {line: 0, ch: 0}); eqPos(m2Pos.to, {line: 2, ch: 1});
+ eqPos(bookmark.find(), {line: 1, ch: 5});
+}, {value: "1234\n56789\n00\n"});
+
testCM("markClearBetween", function(cm) {
cm.setValue("aaa\nbbb\nccc\nddd\n");
cm.markText({line: 0, ch: 0}, {line: 2}, "foo");
Please sign in to comment.
Something went wrong with that request. Please try again.