This commit is contained in:
436
node_modules/codemirror/src/model/Doc.js
generated
vendored
Normal file
436
node_modules/codemirror/src/model/Doc.js
generated
vendored
Normal file
@@ -0,0 +1,436 @@
|
||||
import CodeMirror from "../edit/CodeMirror.js"
|
||||
import { docMethodOp } from "../display/operations.js"
|
||||
import { Line } from "../line/line_data.js"
|
||||
import { clipPos, clipPosArray, Pos } from "../line/pos.js"
|
||||
import { visualLine } from "../line/spans.js"
|
||||
import { getBetween, getLine, getLines, isLine, lineNo } from "../line/utils_line.js"
|
||||
import { classTest } from "../util/dom.js"
|
||||
import { splitLinesAuto } from "../util/feature_detection.js"
|
||||
import { createObj, map, isEmpty, sel_dontScroll } from "../util/misc.js"
|
||||
import { ensureCursorVisible, scrollToCoords } from "../display/scrolling.js"
|
||||
|
||||
import { changeLine, makeChange, makeChangeFromHistory, replaceRange } from "./changes.js"
|
||||
import { computeReplacedSel } from "./change_measurement.js"
|
||||
import { BranchChunk, LeafChunk } from "./chunk.js"
|
||||
import { directionChanged, linkedDocs, updateDoc } from "./document_data.js"
|
||||
import { copyHistoryArray, History } from "./history.js"
|
||||
import { addLineWidget } from "./line_widget.js"
|
||||
import { copySharedMarkers, detachSharedMarkers, findSharedMarkers, markText } from "./mark_text.js"
|
||||
import { normalizeSelection, Range, simpleSelection } from "./selection.js"
|
||||
import { extendSelection, extendSelections, setSelection, setSelectionReplaceHistory, setSimpleSelection } from "./selection_updates.js"
|
||||
|
||||
let nextDocId = 0
|
||||
let Doc = function(text, mode, firstLine, lineSep, direction) {
|
||||
if (!(this instanceof Doc)) return new Doc(text, mode, firstLine, lineSep, direction)
|
||||
if (firstLine == null) firstLine = 0
|
||||
|
||||
BranchChunk.call(this, [new LeafChunk([new Line("", null)])])
|
||||
this.first = firstLine
|
||||
this.scrollTop = this.scrollLeft = 0
|
||||
this.cantEdit = false
|
||||
this.cleanGeneration = 1
|
||||
this.modeFrontier = this.highlightFrontier = firstLine
|
||||
let start = Pos(firstLine, 0)
|
||||
this.sel = simpleSelection(start)
|
||||
this.history = new History(null)
|
||||
this.id = ++nextDocId
|
||||
this.modeOption = mode
|
||||
this.lineSep = lineSep
|
||||
this.direction = (direction == "rtl") ? "rtl" : "ltr"
|
||||
this.extend = false
|
||||
|
||||
if (typeof text == "string") text = this.splitLines(text)
|
||||
updateDoc(this, {from: start, to: start, text: text})
|
||||
setSelection(this, simpleSelection(start), sel_dontScroll)
|
||||
}
|
||||
|
||||
Doc.prototype = createObj(BranchChunk.prototype, {
|
||||
constructor: Doc,
|
||||
// Iterate over the document. Supports two forms -- with only one
|
||||
// argument, it calls that for each line in the document. With
|
||||
// three, it iterates over the range given by the first two (with
|
||||
// the second being non-inclusive).
|
||||
iter: function(from, to, op) {
|
||||
if (op) this.iterN(from - this.first, to - from, op)
|
||||
else this.iterN(this.first, this.first + this.size, from)
|
||||
},
|
||||
|
||||
// Non-public interface for adding and removing lines.
|
||||
insert: function(at, lines) {
|
||||
let height = 0
|
||||
for (let i = 0; i < lines.length; ++i) height += lines[i].height
|
||||
this.insertInner(at - this.first, lines, height)
|
||||
},
|
||||
remove: function(at, n) { this.removeInner(at - this.first, n) },
|
||||
|
||||
// From here, the methods are part of the public interface. Most
|
||||
// are also available from CodeMirror (editor) instances.
|
||||
|
||||
getValue: function(lineSep) {
|
||||
let lines = getLines(this, this.first, this.first + this.size)
|
||||
if (lineSep === false) return lines
|
||||
return lines.join(lineSep || this.lineSeparator())
|
||||
},
|
||||
setValue: docMethodOp(function(code) {
|
||||
let top = Pos(this.first, 0), last = this.first + this.size - 1
|
||||
makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),
|
||||
text: this.splitLines(code), origin: "setValue", full: true}, true)
|
||||
if (this.cm) scrollToCoords(this.cm, 0, 0)
|
||||
setSelection(this, simpleSelection(top), sel_dontScroll)
|
||||
}),
|
||||
replaceRange: function(code, from, to, origin) {
|
||||
from = clipPos(this, from)
|
||||
to = to ? clipPos(this, to) : from
|
||||
replaceRange(this, code, from, to, origin)
|
||||
},
|
||||
getRange: function(from, to, lineSep) {
|
||||
let lines = getBetween(this, clipPos(this, from), clipPos(this, to))
|
||||
if (lineSep === false) return lines
|
||||
if (lineSep === '') return lines.join('')
|
||||
return lines.join(lineSep || this.lineSeparator())
|
||||
},
|
||||
|
||||
getLine: function(line) {let l = this.getLineHandle(line); return l && l.text},
|
||||
|
||||
getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line)},
|
||||
getLineNumber: function(line) {return lineNo(line)},
|
||||
|
||||
getLineHandleVisualStart: function(line) {
|
||||
if (typeof line == "number") line = getLine(this, line)
|
||||
return visualLine(line)
|
||||
},
|
||||
|
||||
lineCount: function() {return this.size},
|
||||
firstLine: function() {return this.first},
|
||||
lastLine: function() {return this.first + this.size - 1},
|
||||
|
||||
clipPos: function(pos) {return clipPos(this, pos)},
|
||||
|
||||
getCursor: function(start) {
|
||||
let range = this.sel.primary(), pos
|
||||
if (start == null || start == "head") pos = range.head
|
||||
else if (start == "anchor") pos = range.anchor
|
||||
else if (start == "end" || start == "to" || start === false) pos = range.to()
|
||||
else pos = range.from()
|
||||
return pos
|
||||
},
|
||||
listSelections: function() { return this.sel.ranges },
|
||||
somethingSelected: function() {return this.sel.somethingSelected()},
|
||||
|
||||
setCursor: docMethodOp(function(line, ch, options) {
|
||||
setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options)
|
||||
}),
|
||||
setSelection: docMethodOp(function(anchor, head, options) {
|
||||
setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options)
|
||||
}),
|
||||
extendSelection: docMethodOp(function(head, other, options) {
|
||||
extendSelection(this, clipPos(this, head), other && clipPos(this, other), options)
|
||||
}),
|
||||
extendSelections: docMethodOp(function(heads, options) {
|
||||
extendSelections(this, clipPosArray(this, heads), options)
|
||||
}),
|
||||
extendSelectionsBy: docMethodOp(function(f, options) {
|
||||
let heads = map(this.sel.ranges, f)
|
||||
extendSelections(this, clipPosArray(this, heads), options)
|
||||
}),
|
||||
setSelections: docMethodOp(function(ranges, primary, options) {
|
||||
if (!ranges.length) return
|
||||
let out = []
|
||||
for (let i = 0; i < ranges.length; i++)
|
||||
out[i] = new Range(clipPos(this, ranges[i].anchor),
|
||||
clipPos(this, ranges[i].head || ranges[i].anchor))
|
||||
if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex)
|
||||
setSelection(this, normalizeSelection(this.cm, out, primary), options)
|
||||
}),
|
||||
addSelection: docMethodOp(function(anchor, head, options) {
|
||||
let ranges = this.sel.ranges.slice(0)
|
||||
ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor)))
|
||||
setSelection(this, normalizeSelection(this.cm, ranges, ranges.length - 1), options)
|
||||
}),
|
||||
|
||||
getSelection: function(lineSep) {
|
||||
let ranges = this.sel.ranges, lines
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
let sel = getBetween(this, ranges[i].from(), ranges[i].to())
|
||||
lines = lines ? lines.concat(sel) : sel
|
||||
}
|
||||
if (lineSep === false) return lines
|
||||
else return lines.join(lineSep || this.lineSeparator())
|
||||
},
|
||||
getSelections: function(lineSep) {
|
||||
let parts = [], ranges = this.sel.ranges
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
let sel = getBetween(this, ranges[i].from(), ranges[i].to())
|
||||
if (lineSep !== false) sel = sel.join(lineSep || this.lineSeparator())
|
||||
parts[i] = sel
|
||||
}
|
||||
return parts
|
||||
},
|
||||
replaceSelection: function(code, collapse, origin) {
|
||||
let dup = []
|
||||
for (let i = 0; i < this.sel.ranges.length; i++)
|
||||
dup[i] = code
|
||||
this.replaceSelections(dup, collapse, origin || "+input")
|
||||
},
|
||||
replaceSelections: docMethodOp(function(code, collapse, origin) {
|
||||
let changes = [], sel = this.sel
|
||||
for (let i = 0; i < sel.ranges.length; i++) {
|
||||
let range = sel.ranges[i]
|
||||
changes[i] = {from: range.from(), to: range.to(), text: this.splitLines(code[i]), origin: origin}
|
||||
}
|
||||
let newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse)
|
||||
for (let i = changes.length - 1; i >= 0; i--)
|
||||
makeChange(this, changes[i])
|
||||
if (newSel) setSelectionReplaceHistory(this, newSel)
|
||||
else if (this.cm) ensureCursorVisible(this.cm)
|
||||
}),
|
||||
undo: docMethodOp(function() {makeChangeFromHistory(this, "undo")}),
|
||||
redo: docMethodOp(function() {makeChangeFromHistory(this, "redo")}),
|
||||
undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true)}),
|
||||
redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true)}),
|
||||
|
||||
setExtending: function(val) {this.extend = val},
|
||||
getExtending: function() {return this.extend},
|
||||
|
||||
historySize: function() {
|
||||
let hist = this.history, done = 0, undone = 0
|
||||
for (let i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done
|
||||
for (let i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone
|
||||
return {undo: done, redo: undone}
|
||||
},
|
||||
clearHistory: function() {
|
||||
this.history = new History(this.history)
|
||||
linkedDocs(this, doc => doc.history = this.history, true)
|
||||
},
|
||||
|
||||
markClean: function() {
|
||||
this.cleanGeneration = this.changeGeneration(true)
|
||||
},
|
||||
changeGeneration: function(forceSplit) {
|
||||
if (forceSplit)
|
||||
this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null
|
||||
return this.history.generation
|
||||
},
|
||||
isClean: function (gen) {
|
||||
return this.history.generation == (gen || this.cleanGeneration)
|
||||
},
|
||||
|
||||
getHistory: function() {
|
||||
return {done: copyHistoryArray(this.history.done),
|
||||
undone: copyHistoryArray(this.history.undone)}
|
||||
},
|
||||
setHistory: function(histData) {
|
||||
let hist = this.history = new History(this.history)
|
||||
hist.done = copyHistoryArray(histData.done.slice(0), null, true)
|
||||
hist.undone = copyHistoryArray(histData.undone.slice(0), null, true)
|
||||
},
|
||||
|
||||
setGutterMarker: docMethodOp(function(line, gutterID, value) {
|
||||
return changeLine(this, line, "gutter", line => {
|
||||
let markers = line.gutterMarkers || (line.gutterMarkers = {})
|
||||
markers[gutterID] = value
|
||||
if (!value && isEmpty(markers)) line.gutterMarkers = null
|
||||
return true
|
||||
})
|
||||
}),
|
||||
|
||||
clearGutter: docMethodOp(function(gutterID) {
|
||||
this.iter(line => {
|
||||
if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
|
||||
changeLine(this, line, "gutter", () => {
|
||||
line.gutterMarkers[gutterID] = null
|
||||
if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null
|
||||
return true
|
||||
})
|
||||
}
|
||||
})
|
||||
}),
|
||||
|
||||
lineInfo: function(line) {
|
||||
let n
|
||||
if (typeof line == "number") {
|
||||
if (!isLine(this, line)) return null
|
||||
n = line
|
||||
line = getLine(this, line)
|
||||
if (!line) return null
|
||||
} else {
|
||||
n = lineNo(line)
|
||||
if (n == null) return null
|
||||
}
|
||||
return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,
|
||||
textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,
|
||||
widgets: line.widgets}
|
||||
},
|
||||
|
||||
addLineClass: docMethodOp(function(handle, where, cls) {
|
||||
return changeLine(this, handle, where == "gutter" ? "gutter" : "class", line => {
|
||||
let prop = where == "text" ? "textClass"
|
||||
: where == "background" ? "bgClass"
|
||||
: where == "gutter" ? "gutterClass" : "wrapClass"
|
||||
if (!line[prop]) line[prop] = cls
|
||||
else if (classTest(cls).test(line[prop])) return false
|
||||
else line[prop] += " " + cls
|
||||
return true
|
||||
})
|
||||
}),
|
||||
removeLineClass: docMethodOp(function(handle, where, cls) {
|
||||
return changeLine(this, handle, where == "gutter" ? "gutter" : "class", line => {
|
||||
let prop = where == "text" ? "textClass"
|
||||
: where == "background" ? "bgClass"
|
||||
: where == "gutter" ? "gutterClass" : "wrapClass"
|
||||
let cur = line[prop]
|
||||
if (!cur) return false
|
||||
else if (cls == null) line[prop] = null
|
||||
else {
|
||||
let found = cur.match(classTest(cls))
|
||||
if (!found) return false
|
||||
let end = found.index + found[0].length
|
||||
line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null
|
||||
}
|
||||
return true
|
||||
})
|
||||
}),
|
||||
|
||||
addLineWidget: docMethodOp(function(handle, node, options) {
|
||||
return addLineWidget(this, handle, node, options)
|
||||
}),
|
||||
removeLineWidget: function(widget) { widget.clear() },
|
||||
|
||||
markText: function(from, to, options) {
|
||||
return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range")
|
||||
},
|
||||
setBookmark: function(pos, options) {
|
||||
let realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
|
||||
insertLeft: options && options.insertLeft,
|
||||
clearWhenEmpty: false, shared: options && options.shared,
|
||||
handleMouseEvents: options && options.handleMouseEvents}
|
||||
pos = clipPos(this, pos)
|
||||
return markText(this, pos, pos, realOpts, "bookmark")
|
||||
},
|
||||
findMarksAt: function(pos) {
|
||||
pos = clipPos(this, pos)
|
||||
let markers = [], spans = getLine(this, pos.line).markedSpans
|
||||
if (spans) for (let i = 0; i < spans.length; ++i) {
|
||||
let span = spans[i]
|
||||
if ((span.from == null || span.from <= pos.ch) &&
|
||||
(span.to == null || span.to >= pos.ch))
|
||||
markers.push(span.marker.parent || span.marker)
|
||||
}
|
||||
return markers
|
||||
},
|
||||
findMarks: function(from, to, filter) {
|
||||
from = clipPos(this, from); to = clipPos(this, to)
|
||||
let found = [], lineNo = from.line
|
||||
this.iter(from.line, to.line + 1, line => {
|
||||
let spans = line.markedSpans
|
||||
if (spans) for (let i = 0; i < spans.length; i++) {
|
||||
let span = spans[i]
|
||||
if (!(span.to != null && lineNo == from.line && from.ch >= span.to ||
|
||||
span.from == null && lineNo != from.line ||
|
||||
span.from != null && lineNo == to.line && span.from >= to.ch) &&
|
||||
(!filter || filter(span.marker)))
|
||||
found.push(span.marker.parent || span.marker)
|
||||
}
|
||||
++lineNo
|
||||
})
|
||||
return found
|
||||
},
|
||||
getAllMarks: function() {
|
||||
let markers = []
|
||||
this.iter(line => {
|
||||
let sps = line.markedSpans
|
||||
if (sps) for (let i = 0; i < sps.length; ++i)
|
||||
if (sps[i].from != null) markers.push(sps[i].marker)
|
||||
})
|
||||
return markers
|
||||
},
|
||||
|
||||
posFromIndex: function(off) {
|
||||
let ch, lineNo = this.first, sepSize = this.lineSeparator().length
|
||||
this.iter(line => {
|
||||
let sz = line.text.length + sepSize
|
||||
if (sz > off) { ch = off; return true }
|
||||
off -= sz
|
||||
++lineNo
|
||||
})
|
||||
return clipPos(this, Pos(lineNo, ch))
|
||||
},
|
||||
indexFromPos: function (coords) {
|
||||
coords = clipPos(this, coords)
|
||||
let index = coords.ch
|
||||
if (coords.line < this.first || coords.ch < 0) return 0
|
||||
let sepSize = this.lineSeparator().length
|
||||
this.iter(this.first, coords.line, line => { // iter aborts when callback returns a truthy value
|
||||
index += line.text.length + sepSize
|
||||
})
|
||||
return index
|
||||
},
|
||||
|
||||
copy: function(copyHistory) {
|
||||
let doc = new Doc(getLines(this, this.first, this.first + this.size),
|
||||
this.modeOption, this.first, this.lineSep, this.direction)
|
||||
doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft
|
||||
doc.sel = this.sel
|
||||
doc.extend = false
|
||||
if (copyHistory) {
|
||||
doc.history.undoDepth = this.history.undoDepth
|
||||
doc.setHistory(this.getHistory())
|
||||
}
|
||||
return doc
|
||||
},
|
||||
|
||||
linkedDoc: function(options) {
|
||||
if (!options) options = {}
|
||||
let from = this.first, to = this.first + this.size
|
||||
if (options.from != null && options.from > from) from = options.from
|
||||
if (options.to != null && options.to < to) to = options.to
|
||||
let copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction)
|
||||
if (options.sharedHist) copy.history = this.history
|
||||
;(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist})
|
||||
copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]
|
||||
copySharedMarkers(copy, findSharedMarkers(this))
|
||||
return copy
|
||||
},
|
||||
unlinkDoc: function(other) {
|
||||
if (other instanceof CodeMirror) other = other.doc
|
||||
if (this.linked) for (let i = 0; i < this.linked.length; ++i) {
|
||||
let link = this.linked[i]
|
||||
if (link.doc != other) continue
|
||||
this.linked.splice(i, 1)
|
||||
other.unlinkDoc(this)
|
||||
detachSharedMarkers(findSharedMarkers(this))
|
||||
break
|
||||
}
|
||||
// If the histories were shared, split them again
|
||||
if (other.history == this.history) {
|
||||
let splitIds = [other.id]
|
||||
linkedDocs(other, doc => splitIds.push(doc.id), true)
|
||||
other.history = new History(null)
|
||||
other.history.done = copyHistoryArray(this.history.done, splitIds)
|
||||
other.history.undone = copyHistoryArray(this.history.undone, splitIds)
|
||||
}
|
||||
},
|
||||
iterLinkedDocs: function(f) {linkedDocs(this, f)},
|
||||
|
||||
getMode: function() {return this.mode},
|
||||
getEditor: function() {return this.cm},
|
||||
|
||||
splitLines: function(str) {
|
||||
if (this.lineSep) return str.split(this.lineSep)
|
||||
return splitLinesAuto(str)
|
||||
},
|
||||
lineSeparator: function() { return this.lineSep || "\n" },
|
||||
|
||||
setDirection: docMethodOp(function (dir) {
|
||||
if (dir != "rtl") dir = "ltr"
|
||||
if (dir == this.direction) return
|
||||
this.direction = dir
|
||||
this.iter(line => line.order = null)
|
||||
if (this.cm) directionChanged(this.cm)
|
||||
})
|
||||
})
|
||||
|
||||
// Public alias.
|
||||
Doc.prototype.eachLine = Doc.prototype.iter
|
||||
|
||||
export default Doc
|
||||
61
node_modules/codemirror/src/model/change_measurement.js
generated
vendored
Normal file
61
node_modules/codemirror/src/model/change_measurement.js
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
import { cmp, Pos } from "../line/pos.js"
|
||||
import { lst } from "../util/misc.js"
|
||||
|
||||
import { normalizeSelection, Range, Selection } from "./selection.js"
|
||||
|
||||
// Compute the position of the end of a change (its 'to' property
|
||||
// refers to the pre-change end).
|
||||
export function changeEnd(change) {
|
||||
if (!change.text) return change.to
|
||||
return Pos(change.from.line + change.text.length - 1,
|
||||
lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0))
|
||||
}
|
||||
|
||||
// Adjust a position to refer to the post-change position of the
|
||||
// same text, or the end of the change if the change covers it.
|
||||
function adjustForChange(pos, change) {
|
||||
if (cmp(pos, change.from) < 0) return pos
|
||||
if (cmp(pos, change.to) <= 0) return changeEnd(change)
|
||||
|
||||
let line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch
|
||||
if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch
|
||||
return Pos(line, ch)
|
||||
}
|
||||
|
||||
export function computeSelAfterChange(doc, change) {
|
||||
let out = []
|
||||
for (let i = 0; i < doc.sel.ranges.length; i++) {
|
||||
let range = doc.sel.ranges[i]
|
||||
out.push(new Range(adjustForChange(range.anchor, change),
|
||||
adjustForChange(range.head, change)))
|
||||
}
|
||||
return normalizeSelection(doc.cm, out, doc.sel.primIndex)
|
||||
}
|
||||
|
||||
function offsetPos(pos, old, nw) {
|
||||
if (pos.line == old.line)
|
||||
return Pos(nw.line, pos.ch - old.ch + nw.ch)
|
||||
else
|
||||
return Pos(nw.line + (pos.line - old.line), pos.ch)
|
||||
}
|
||||
|
||||
// Used by replaceSelections to allow moving the selection to the
|
||||
// start or around the replaced test. Hint may be "start" or "around".
|
||||
export function computeReplacedSel(doc, changes, hint) {
|
||||
let out = []
|
||||
let oldPrev = Pos(doc.first, 0), newPrev = oldPrev
|
||||
for (let i = 0; i < changes.length; i++) {
|
||||
let change = changes[i]
|
||||
let from = offsetPos(change.from, oldPrev, newPrev)
|
||||
let to = offsetPos(changeEnd(change), oldPrev, newPrev)
|
||||
oldPrev = change.to
|
||||
newPrev = to
|
||||
if (hint == "around") {
|
||||
let range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0
|
||||
out[i] = new Range(inv ? to : from, inv ? from : to)
|
||||
} else {
|
||||
out[i] = new Range(from, from)
|
||||
}
|
||||
}
|
||||
return new Selection(out, doc.sel.primIndex)
|
||||
}
|
||||
339
node_modules/codemirror/src/model/changes.js
generated
vendored
Normal file
339
node_modules/codemirror/src/model/changes.js
generated
vendored
Normal file
@@ -0,0 +1,339 @@
|
||||
import { retreatFrontier } from "../line/highlight.js"
|
||||
import { startWorker } from "../display/highlight_worker.js"
|
||||
import { operation } from "../display/operations.js"
|
||||
import { regChange, regLineChange } from "../display/view_tracking.js"
|
||||
import { clipLine, clipPos, cmp, Pos } from "../line/pos.js"
|
||||
import { sawReadOnlySpans } from "../line/saw_special_spans.js"
|
||||
import { lineLength, removeReadOnlyRanges, stretchSpansOverChange, visualLine } from "../line/spans.js"
|
||||
import { getBetween, getLine, lineNo } from "../line/utils_line.js"
|
||||
import { estimateHeight } from "../measurement/position_measurement.js"
|
||||
import { hasHandler, signal, signalCursorActivity } from "../util/event.js"
|
||||
import { indexOf, lst, map, sel_dontScroll } from "../util/misc.js"
|
||||
import { signalLater } from "../util/operation_group.js"
|
||||
|
||||
import { changeEnd, computeSelAfterChange } from "./change_measurement.js"
|
||||
import { isWholeLineUpdate, linkedDocs, updateDoc } from "./document_data.js"
|
||||
import { addChangeToHistory, historyChangeFromChange, mergeOldSpans, pushSelectionToHistory } from "./history.js"
|
||||
import { Range, Selection } from "./selection.js"
|
||||
import { setSelection, setSelectionNoUndo, skipAtomic } from "./selection_updates.js"
|
||||
|
||||
// UPDATING
|
||||
|
||||
// Allow "beforeChange" event handlers to influence a change
|
||||
function filterChange(doc, change, update) {
|
||||
let obj = {
|
||||
canceled: false,
|
||||
from: change.from,
|
||||
to: change.to,
|
||||
text: change.text,
|
||||
origin: change.origin,
|
||||
cancel: () => obj.canceled = true
|
||||
}
|
||||
if (update) obj.update = (from, to, text, origin) => {
|
||||
if (from) obj.from = clipPos(doc, from)
|
||||
if (to) obj.to = clipPos(doc, to)
|
||||
if (text) obj.text = text
|
||||
if (origin !== undefined) obj.origin = origin
|
||||
}
|
||||
signal(doc, "beforeChange", doc, obj)
|
||||
if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj)
|
||||
|
||||
if (obj.canceled) {
|
||||
if (doc.cm) doc.cm.curOp.updateInput = 2
|
||||
return null
|
||||
}
|
||||
return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}
|
||||
}
|
||||
|
||||
// Apply a change to a document, and add it to the document's
|
||||
// history, and propagating it to all linked documents.
|
||||
export function makeChange(doc, change, ignoreReadOnly) {
|
||||
if (doc.cm) {
|
||||
if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly)
|
||||
if (doc.cm.state.suppressEdits) return
|
||||
}
|
||||
|
||||
if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) {
|
||||
change = filterChange(doc, change, true)
|
||||
if (!change) return
|
||||
}
|
||||
|
||||
// Possibly split or suppress the update based on the presence
|
||||
// of read-only spans in its range.
|
||||
let split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to)
|
||||
if (split) {
|
||||
for (let i = split.length - 1; i >= 0; --i)
|
||||
makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text, origin: change.origin})
|
||||
} else {
|
||||
makeChangeInner(doc, change)
|
||||
}
|
||||
}
|
||||
|
||||
function makeChangeInner(doc, change) {
|
||||
if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) return
|
||||
let selAfter = computeSelAfterChange(doc, change)
|
||||
addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN)
|
||||
|
||||
makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change))
|
||||
let rebased = []
|
||||
|
||||
linkedDocs(doc, (doc, sharedHist) => {
|
||||
if (!sharedHist && indexOf(rebased, doc.history) == -1) {
|
||||
rebaseHist(doc.history, change)
|
||||
rebased.push(doc.history)
|
||||
}
|
||||
makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change))
|
||||
})
|
||||
}
|
||||
|
||||
// Revert a change stored in a document's history.
|
||||
export function makeChangeFromHistory(doc, type, allowSelectionOnly) {
|
||||
let suppress = doc.cm && doc.cm.state.suppressEdits
|
||||
if (suppress && !allowSelectionOnly) return
|
||||
|
||||
let hist = doc.history, event, selAfter = doc.sel
|
||||
let source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done
|
||||
|
||||
// Verify that there is a useable event (so that ctrl-z won't
|
||||
// needlessly clear selection events)
|
||||
let i = 0
|
||||
for (; i < source.length; i++) {
|
||||
event = source[i]
|
||||
if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges)
|
||||
break
|
||||
}
|
||||
if (i == source.length) return
|
||||
hist.lastOrigin = hist.lastSelOrigin = null
|
||||
|
||||
for (;;) {
|
||||
event = source.pop()
|
||||
if (event.ranges) {
|
||||
pushSelectionToHistory(event, dest)
|
||||
if (allowSelectionOnly && !event.equals(doc.sel)) {
|
||||
setSelection(doc, event, {clearRedo: false})
|
||||
return
|
||||
}
|
||||
selAfter = event
|
||||
} else if (suppress) {
|
||||
source.push(event)
|
||||
return
|
||||
} else break
|
||||
}
|
||||
|
||||
// Build up a reverse change object to add to the opposite history
|
||||
// stack (redo when undoing, and vice versa).
|
||||
let antiChanges = []
|
||||
pushSelectionToHistory(selAfter, dest)
|
||||
dest.push({changes: antiChanges, generation: hist.generation})
|
||||
hist.generation = event.generation || ++hist.maxGeneration
|
||||
|
||||
let filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")
|
||||
|
||||
for (let i = event.changes.length - 1; i >= 0; --i) {
|
||||
let change = event.changes[i]
|
||||
change.origin = type
|
||||
if (filter && !filterChange(doc, change, false)) {
|
||||
source.length = 0
|
||||
return
|
||||
}
|
||||
|
||||
antiChanges.push(historyChangeFromChange(doc, change))
|
||||
|
||||
let after = i ? computeSelAfterChange(doc, change) : lst(source)
|
||||
makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change))
|
||||
if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)})
|
||||
let rebased = []
|
||||
|
||||
// Propagate to the linked documents
|
||||
linkedDocs(doc, (doc, sharedHist) => {
|
||||
if (!sharedHist && indexOf(rebased, doc.history) == -1) {
|
||||
rebaseHist(doc.history, change)
|
||||
rebased.push(doc.history)
|
||||
}
|
||||
makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Sub-views need their line numbers shifted when text is added
|
||||
// above or below them in the parent document.
|
||||
function shiftDoc(doc, distance) {
|
||||
if (distance == 0) return
|
||||
doc.first += distance
|
||||
doc.sel = new Selection(map(doc.sel.ranges, range => new Range(
|
||||
Pos(range.anchor.line + distance, range.anchor.ch),
|
||||
Pos(range.head.line + distance, range.head.ch)
|
||||
)), doc.sel.primIndex)
|
||||
if (doc.cm) {
|
||||
regChange(doc.cm, doc.first, doc.first - distance, distance)
|
||||
for (let d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++)
|
||||
regLineChange(doc.cm, l, "gutter")
|
||||
}
|
||||
}
|
||||
|
||||
// More lower-level change function, handling only a single document
|
||||
// (not linked ones).
|
||||
function makeChangeSingleDoc(doc, change, selAfter, spans) {
|
||||
if (doc.cm && !doc.cm.curOp)
|
||||
return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans)
|
||||
|
||||
if (change.to.line < doc.first) {
|
||||
shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line))
|
||||
return
|
||||
}
|
||||
if (change.from.line > doc.lastLine()) return
|
||||
|
||||
// Clip the change to the size of this doc
|
||||
if (change.from.line < doc.first) {
|
||||
let shift = change.text.length - 1 - (doc.first - change.from.line)
|
||||
shiftDoc(doc, shift)
|
||||
change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),
|
||||
text: [lst(change.text)], origin: change.origin}
|
||||
}
|
||||
let last = doc.lastLine()
|
||||
if (change.to.line > last) {
|
||||
change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),
|
||||
text: [change.text[0]], origin: change.origin}
|
||||
}
|
||||
|
||||
change.removed = getBetween(doc, change.from, change.to)
|
||||
|
||||
if (!selAfter) selAfter = computeSelAfterChange(doc, change)
|
||||
if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans)
|
||||
else updateDoc(doc, change, spans)
|
||||
setSelectionNoUndo(doc, selAfter, sel_dontScroll)
|
||||
|
||||
if (doc.cantEdit && skipAtomic(doc, Pos(doc.firstLine(), 0)))
|
||||
doc.cantEdit = false
|
||||
}
|
||||
|
||||
// Handle the interaction of a change to a document with the editor
|
||||
// that this document is part of.
|
||||
function makeChangeSingleDocInEditor(cm, change, spans) {
|
||||
let doc = cm.doc, display = cm.display, from = change.from, to = change.to
|
||||
|
||||
let recomputeMaxLength = false, checkWidthStart = from.line
|
||||
if (!cm.options.lineWrapping) {
|
||||
checkWidthStart = lineNo(visualLine(getLine(doc, from.line)))
|
||||
doc.iter(checkWidthStart, to.line + 1, line => {
|
||||
if (line == display.maxLine) {
|
||||
recomputeMaxLength = true
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (doc.sel.contains(change.from, change.to) > -1)
|
||||
signalCursorActivity(cm)
|
||||
|
||||
updateDoc(doc, change, spans, estimateHeight(cm))
|
||||
|
||||
if (!cm.options.lineWrapping) {
|
||||
doc.iter(checkWidthStart, from.line + change.text.length, line => {
|
||||
let len = lineLength(line)
|
||||
if (len > display.maxLineLength) {
|
||||
display.maxLine = line
|
||||
display.maxLineLength = len
|
||||
display.maxLineChanged = true
|
||||
recomputeMaxLength = false
|
||||
}
|
||||
})
|
||||
if (recomputeMaxLength) cm.curOp.updateMaxLine = true
|
||||
}
|
||||
|
||||
retreatFrontier(doc, from.line)
|
||||
startWorker(cm, 400)
|
||||
|
||||
let lendiff = change.text.length - (to.line - from.line) - 1
|
||||
// Remember that these lines changed, for updating the display
|
||||
if (change.full)
|
||||
regChange(cm)
|
||||
else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))
|
||||
regLineChange(cm, from.line, "text")
|
||||
else
|
||||
regChange(cm, from.line, to.line + 1, lendiff)
|
||||
|
||||
let changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change")
|
||||
if (changeHandler || changesHandler) {
|
||||
let obj = {
|
||||
from: from, to: to,
|
||||
text: change.text,
|
||||
removed: change.removed,
|
||||
origin: change.origin
|
||||
}
|
||||
if (changeHandler) signalLater(cm, "change", cm, obj)
|
||||
if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj)
|
||||
}
|
||||
cm.display.selForContextMenu = null
|
||||
}
|
||||
|
||||
export function replaceRange(doc, code, from, to, origin) {
|
||||
if (!to) to = from
|
||||
if (cmp(to, from) < 0) [from, to] = [to, from]
|
||||
if (typeof code == "string") code = doc.splitLines(code)
|
||||
makeChange(doc, {from, to, text: code, origin})
|
||||
}
|
||||
|
||||
// Rebasing/resetting history to deal with externally-sourced changes
|
||||
|
||||
function rebaseHistSelSingle(pos, from, to, diff) {
|
||||
if (to < pos.line) {
|
||||
pos.line += diff
|
||||
} else if (from < pos.line) {
|
||||
pos.line = from
|
||||
pos.ch = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Tries to rebase an array of history events given a change in the
|
||||
// document. If the change touches the same lines as the event, the
|
||||
// event, and everything 'behind' it, is discarded. If the change is
|
||||
// before the event, the event's positions are updated. Uses a
|
||||
// copy-on-write scheme for the positions, to avoid having to
|
||||
// reallocate them all on every rebase, but also avoid problems with
|
||||
// shared position objects being unsafely updated.
|
||||
function rebaseHistArray(array, from, to, diff) {
|
||||
for (let i = 0; i < array.length; ++i) {
|
||||
let sub = array[i], ok = true
|
||||
if (sub.ranges) {
|
||||
if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true }
|
||||
for (let j = 0; j < sub.ranges.length; j++) {
|
||||
rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff)
|
||||
rebaseHistSelSingle(sub.ranges[j].head, from, to, diff)
|
||||
}
|
||||
continue
|
||||
}
|
||||
for (let j = 0; j < sub.changes.length; ++j) {
|
||||
let cur = sub.changes[j]
|
||||
if (to < cur.from.line) {
|
||||
cur.from = Pos(cur.from.line + diff, cur.from.ch)
|
||||
cur.to = Pos(cur.to.line + diff, cur.to.ch)
|
||||
} else if (from <= cur.to.line) {
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
array.splice(0, i + 1)
|
||||
i = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function rebaseHist(hist, change) {
|
||||
let from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1
|
||||
rebaseHistArray(hist.done, from, to, diff)
|
||||
rebaseHistArray(hist.undone, from, to, diff)
|
||||
}
|
||||
|
||||
// Utility for applying a change to a line by handle or number,
|
||||
// returning the number and optionally registering the line as
|
||||
// changed.
|
||||
export function changeLine(doc, handle, changeType, op) {
|
||||
let no = handle, line = handle
|
||||
if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle))
|
||||
else no = lineNo(handle)
|
||||
if (no == null) return null
|
||||
if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType)
|
||||
return line
|
||||
}
|
||||
167
node_modules/codemirror/src/model/chunk.js
generated
vendored
Normal file
167
node_modules/codemirror/src/model/chunk.js
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
import { cleanUpLine } from "../line/line_data.js"
|
||||
import { indexOf } from "../util/misc.js"
|
||||
import { signalLater } from "../util/operation_group.js"
|
||||
|
||||
// The document is represented as a BTree consisting of leaves, with
|
||||
// chunk of lines in them, and branches, with up to ten leaves or
|
||||
// other branch nodes below them. The top node is always a branch
|
||||
// node, and is the document object itself (meaning it has
|
||||
// additional methods and properties).
|
||||
//
|
||||
// All nodes have parent links. The tree is used both to go from
|
||||
// line numbers to line objects, and to go from objects to numbers.
|
||||
// It also indexes by height, and is used to convert between height
|
||||
// and line object, and to find the total height of the document.
|
||||
//
|
||||
// See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html
|
||||
|
||||
export function LeafChunk(lines) {
|
||||
this.lines = lines
|
||||
this.parent = null
|
||||
let height = 0
|
||||
for (let i = 0; i < lines.length; ++i) {
|
||||
lines[i].parent = this
|
||||
height += lines[i].height
|
||||
}
|
||||
this.height = height
|
||||
}
|
||||
|
||||
LeafChunk.prototype = {
|
||||
chunkSize() { return this.lines.length },
|
||||
|
||||
// Remove the n lines at offset 'at'.
|
||||
removeInner(at, n) {
|
||||
for (let i = at, e = at + n; i < e; ++i) {
|
||||
let line = this.lines[i]
|
||||
this.height -= line.height
|
||||
cleanUpLine(line)
|
||||
signalLater(line, "delete")
|
||||
}
|
||||
this.lines.splice(at, n)
|
||||
},
|
||||
|
||||
// Helper used to collapse a small branch into a single leaf.
|
||||
collapse(lines) {
|
||||
lines.push.apply(lines, this.lines)
|
||||
},
|
||||
|
||||
// Insert the given array of lines at offset 'at', count them as
|
||||
// having the given height.
|
||||
insertInner(at, lines, height) {
|
||||
this.height += height
|
||||
this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at))
|
||||
for (let i = 0; i < lines.length; ++i) lines[i].parent = this
|
||||
},
|
||||
|
||||
// Used to iterate over a part of the tree.
|
||||
iterN(at, n, op) {
|
||||
for (let e = at + n; at < e; ++at)
|
||||
if (op(this.lines[at])) return true
|
||||
}
|
||||
}
|
||||
|
||||
export function BranchChunk(children) {
|
||||
this.children = children
|
||||
let size = 0, height = 0
|
||||
for (let i = 0; i < children.length; ++i) {
|
||||
let ch = children[i]
|
||||
size += ch.chunkSize(); height += ch.height
|
||||
ch.parent = this
|
||||
}
|
||||
this.size = size
|
||||
this.height = height
|
||||
this.parent = null
|
||||
}
|
||||
|
||||
BranchChunk.prototype = {
|
||||
chunkSize() { return this.size },
|
||||
|
||||
removeInner(at, n) {
|
||||
this.size -= n
|
||||
for (let i = 0; i < this.children.length; ++i) {
|
||||
let child = this.children[i], sz = child.chunkSize()
|
||||
if (at < sz) {
|
||||
let rm = Math.min(n, sz - at), oldHeight = child.height
|
||||
child.removeInner(at, rm)
|
||||
this.height -= oldHeight - child.height
|
||||
if (sz == rm) { this.children.splice(i--, 1); child.parent = null }
|
||||
if ((n -= rm) == 0) break
|
||||
at = 0
|
||||
} else at -= sz
|
||||
}
|
||||
// If the result is smaller than 25 lines, ensure that it is a
|
||||
// single leaf node.
|
||||
if (this.size - n < 25 &&
|
||||
(this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) {
|
||||
let lines = []
|
||||
this.collapse(lines)
|
||||
this.children = [new LeafChunk(lines)]
|
||||
this.children[0].parent = this
|
||||
}
|
||||
},
|
||||
|
||||
collapse(lines) {
|
||||
for (let i = 0; i < this.children.length; ++i) this.children[i].collapse(lines)
|
||||
},
|
||||
|
||||
insertInner(at, lines, height) {
|
||||
this.size += lines.length
|
||||
this.height += height
|
||||
for (let i = 0; i < this.children.length; ++i) {
|
||||
let child = this.children[i], sz = child.chunkSize()
|
||||
if (at <= sz) {
|
||||
child.insertInner(at, lines, height)
|
||||
if (child.lines && child.lines.length > 50) {
|
||||
// To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced.
|
||||
// Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest.
|
||||
let remaining = child.lines.length % 25 + 25
|
||||
for (let pos = remaining; pos < child.lines.length;) {
|
||||
let leaf = new LeafChunk(child.lines.slice(pos, pos += 25))
|
||||
child.height -= leaf.height
|
||||
this.children.splice(++i, 0, leaf)
|
||||
leaf.parent = this
|
||||
}
|
||||
child.lines = child.lines.slice(0, remaining)
|
||||
this.maybeSpill()
|
||||
}
|
||||
break
|
||||
}
|
||||
at -= sz
|
||||
}
|
||||
},
|
||||
|
||||
// When a node has grown, check whether it should be split.
|
||||
maybeSpill() {
|
||||
if (this.children.length <= 10) return
|
||||
let me = this
|
||||
do {
|
||||
let spilled = me.children.splice(me.children.length - 5, 5)
|
||||
let sibling = new BranchChunk(spilled)
|
||||
if (!me.parent) { // Become the parent node
|
||||
let copy = new BranchChunk(me.children)
|
||||
copy.parent = me
|
||||
me.children = [copy, sibling]
|
||||
me = copy
|
||||
} else {
|
||||
me.size -= sibling.size
|
||||
me.height -= sibling.height
|
||||
let myIndex = indexOf(me.parent.children, me)
|
||||
me.parent.children.splice(myIndex + 1, 0, sibling)
|
||||
}
|
||||
sibling.parent = me.parent
|
||||
} while (me.children.length > 10)
|
||||
me.parent.maybeSpill()
|
||||
},
|
||||
|
||||
iterN(at, n, op) {
|
||||
for (let i = 0; i < this.children.length; ++i) {
|
||||
let child = this.children[i], sz = child.chunkSize()
|
||||
if (at < sz) {
|
||||
let used = Math.min(n, sz - at)
|
||||
if (child.iterN(at, used, op)) return true
|
||||
if ((n -= used) == 0) break
|
||||
at = 0
|
||||
} else at -= sz
|
||||
}
|
||||
}
|
||||
}
|
||||
112
node_modules/codemirror/src/model/document_data.js
generated
vendored
Normal file
112
node_modules/codemirror/src/model/document_data.js
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
import { loadMode } from "../display/mode_state.js"
|
||||
import { runInOp } from "../display/operations.js"
|
||||
import { regChange } from "../display/view_tracking.js"
|
||||
import { Line, updateLine } from "../line/line_data.js"
|
||||
import { findMaxLine } from "../line/spans.js"
|
||||
import { getLine } from "../line/utils_line.js"
|
||||
import { estimateLineHeights } from "../measurement/position_measurement.js"
|
||||
import { addClass, rmClass } from "../util/dom.js"
|
||||
import { lst } from "../util/misc.js"
|
||||
import { signalLater } from "../util/operation_group.js"
|
||||
|
||||
// DOCUMENT DATA STRUCTURE
|
||||
|
||||
// By default, updates that start and end at the beginning of a line
|
||||
// are treated specially, in order to make the association of line
|
||||
// widgets and marker elements with the text behave more intuitive.
|
||||
export function isWholeLineUpdate(doc, change) {
|
||||
return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" &&
|
||||
(!doc.cm || doc.cm.options.wholeLineUpdateBefore)
|
||||
}
|
||||
|
||||
// Perform a change on the document data structure.
|
||||
export function updateDoc(doc, change, markedSpans, estimateHeight) {
|
||||
function spansFor(n) {return markedSpans ? markedSpans[n] : null}
|
||||
function update(line, text, spans) {
|
||||
updateLine(line, text, spans, estimateHeight)
|
||||
signalLater(line, "change", line, change)
|
||||
}
|
||||
function linesFor(start, end) {
|
||||
let result = []
|
||||
for (let i = start; i < end; ++i)
|
||||
result.push(new Line(text[i], spansFor(i), estimateHeight))
|
||||
return result
|
||||
}
|
||||
|
||||
let from = change.from, to = change.to, text = change.text
|
||||
let firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line)
|
||||
let lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line
|
||||
|
||||
// Adjust the line structure
|
||||
if (change.full) {
|
||||
doc.insert(0, linesFor(0, text.length))
|
||||
doc.remove(text.length, doc.size - text.length)
|
||||
} else if (isWholeLineUpdate(doc, change)) {
|
||||
// This is a whole-line replace. Treated specially to make
|
||||
// sure line objects move the way they are supposed to.
|
||||
let added = linesFor(0, text.length - 1)
|
||||
update(lastLine, lastLine.text, lastSpans)
|
||||
if (nlines) doc.remove(from.line, nlines)
|
||||
if (added.length) doc.insert(from.line, added)
|
||||
} else if (firstLine == lastLine) {
|
||||
if (text.length == 1) {
|
||||
update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans)
|
||||
} else {
|
||||
let added = linesFor(1, text.length - 1)
|
||||
added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight))
|
||||
update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0))
|
||||
doc.insert(from.line + 1, added)
|
||||
}
|
||||
} else if (text.length == 1) {
|
||||
update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0))
|
||||
doc.remove(from.line + 1, nlines)
|
||||
} else {
|
||||
update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0))
|
||||
update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans)
|
||||
let added = linesFor(1, text.length - 1)
|
||||
if (nlines > 1) doc.remove(from.line + 1, nlines - 1)
|
||||
doc.insert(from.line + 1, added)
|
||||
}
|
||||
|
||||
signalLater(doc, "change", doc, change)
|
||||
}
|
||||
|
||||
// Call f for all linked documents.
|
||||
export function linkedDocs(doc, f, sharedHistOnly) {
|
||||
function propagate(doc, skip, sharedHist) {
|
||||
if (doc.linked) for (let i = 0; i < doc.linked.length; ++i) {
|
||||
let rel = doc.linked[i]
|
||||
if (rel.doc == skip) continue
|
||||
let shared = sharedHist && rel.sharedHist
|
||||
if (sharedHistOnly && !shared) continue
|
||||
f(rel.doc, shared)
|
||||
propagate(rel.doc, doc, shared)
|
||||
}
|
||||
}
|
||||
propagate(doc, null, true)
|
||||
}
|
||||
|
||||
// Attach a document to an editor.
|
||||
export function attachDoc(cm, doc) {
|
||||
if (doc.cm) throw new Error("This document is already in use.")
|
||||
cm.doc = doc
|
||||
doc.cm = cm
|
||||
estimateLineHeights(cm)
|
||||
loadMode(cm)
|
||||
setDirectionClass(cm)
|
||||
cm.options.direction = doc.direction
|
||||
if (!cm.options.lineWrapping) findMaxLine(cm)
|
||||
cm.options.mode = doc.modeOption
|
||||
regChange(cm)
|
||||
}
|
||||
|
||||
function setDirectionClass(cm) {
|
||||
;(cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl")
|
||||
}
|
||||
|
||||
export function directionChanged(cm) {
|
||||
runInOp(cm, () => {
|
||||
setDirectionClass(cm)
|
||||
regChange(cm)
|
||||
})
|
||||
}
|
||||
228
node_modules/codemirror/src/model/history.js
generated
vendored
Normal file
228
node_modules/codemirror/src/model/history.js
generated
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
import { cmp, copyPos } from "../line/pos.js"
|
||||
import { stretchSpansOverChange } from "../line/spans.js"
|
||||
import { getBetween } from "../line/utils_line.js"
|
||||
import { signal } from "../util/event.js"
|
||||
import { indexOf, lst } from "../util/misc.js"
|
||||
|
||||
import { changeEnd } from "./change_measurement.js"
|
||||
import { linkedDocs } from "./document_data.js"
|
||||
import { Selection } from "./selection.js"
|
||||
|
||||
export function History(prev) {
|
||||
// Arrays of change events and selections. Doing something adds an
|
||||
// event to done and clears undo. Undoing moves events from done
|
||||
// to undone, redoing moves them in the other direction.
|
||||
this.done = []; this.undone = []
|
||||
this.undoDepth = prev ? prev.undoDepth : Infinity
|
||||
// Used to track when changes can be merged into a single undo
|
||||
// event
|
||||
this.lastModTime = this.lastSelTime = 0
|
||||
this.lastOp = this.lastSelOp = null
|
||||
this.lastOrigin = this.lastSelOrigin = null
|
||||
// Used by the isClean() method
|
||||
this.generation = this.maxGeneration = prev ? prev.maxGeneration : 1
|
||||
}
|
||||
|
||||
// Create a history change event from an updateDoc-style change
|
||||
// object.
|
||||
export function historyChangeFromChange(doc, change) {
|
||||
let histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)}
|
||||
attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1)
|
||||
linkedDocs(doc, doc => attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1), true)
|
||||
return histChange
|
||||
}
|
||||
|
||||
// Pop all selection events off the end of a history array. Stop at
|
||||
// a change event.
|
||||
function clearSelectionEvents(array) {
|
||||
while (array.length) {
|
||||
let last = lst(array)
|
||||
if (last.ranges) array.pop()
|
||||
else break
|
||||
}
|
||||
}
|
||||
|
||||
// Find the top change event in the history. Pop off selection
|
||||
// events that are in the way.
|
||||
function lastChangeEvent(hist, force) {
|
||||
if (force) {
|
||||
clearSelectionEvents(hist.done)
|
||||
return lst(hist.done)
|
||||
} else if (hist.done.length && !lst(hist.done).ranges) {
|
||||
return lst(hist.done)
|
||||
} else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) {
|
||||
hist.done.pop()
|
||||
return lst(hist.done)
|
||||
}
|
||||
}
|
||||
|
||||
// Register a change in the history. Merges changes that are within
|
||||
// a single operation, or are close together with an origin that
|
||||
// allows merging (starting with "+") into a single event.
|
||||
export function addChangeToHistory(doc, change, selAfter, opId) {
|
||||
let hist = doc.history
|
||||
hist.undone.length = 0
|
||||
let time = +new Date, cur
|
||||
let last
|
||||
|
||||
if ((hist.lastOp == opId ||
|
||||
hist.lastOrigin == change.origin && change.origin &&
|
||||
((change.origin.charAt(0) == "+" && hist.lastModTime > time - (doc.cm ? doc.cm.options.historyEventDelay : 500)) ||
|
||||
change.origin.charAt(0) == "*")) &&
|
||||
(cur = lastChangeEvent(hist, hist.lastOp == opId))) {
|
||||
// Merge this change into the last event
|
||||
last = lst(cur.changes)
|
||||
if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) {
|
||||
// Optimized case for simple insertion -- don't want to add
|
||||
// new changesets for every character typed
|
||||
last.to = changeEnd(change)
|
||||
} else {
|
||||
// Add new sub-event
|
||||
cur.changes.push(historyChangeFromChange(doc, change))
|
||||
}
|
||||
} else {
|
||||
// Can not be merged, start a new event.
|
||||
let before = lst(hist.done)
|
||||
if (!before || !before.ranges)
|
||||
pushSelectionToHistory(doc.sel, hist.done)
|
||||
cur = {changes: [historyChangeFromChange(doc, change)],
|
||||
generation: hist.generation}
|
||||
hist.done.push(cur)
|
||||
while (hist.done.length > hist.undoDepth) {
|
||||
hist.done.shift()
|
||||
if (!hist.done[0].ranges) hist.done.shift()
|
||||
}
|
||||
}
|
||||
hist.done.push(selAfter)
|
||||
hist.generation = ++hist.maxGeneration
|
||||
hist.lastModTime = hist.lastSelTime = time
|
||||
hist.lastOp = hist.lastSelOp = opId
|
||||
hist.lastOrigin = hist.lastSelOrigin = change.origin
|
||||
|
||||
if (!last) signal(doc, "historyAdded")
|
||||
}
|
||||
|
||||
function selectionEventCanBeMerged(doc, origin, prev, sel) {
|
||||
let ch = origin.charAt(0)
|
||||
return ch == "*" ||
|
||||
ch == "+" &&
|
||||
prev.ranges.length == sel.ranges.length &&
|
||||
prev.somethingSelected() == sel.somethingSelected() &&
|
||||
new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500)
|
||||
}
|
||||
|
||||
// Called whenever the selection changes, sets the new selection as
|
||||
// the pending selection in the history, and pushes the old pending
|
||||
// selection into the 'done' array when it was significantly
|
||||
// different (in number of selected ranges, emptiness, or time).
|
||||
export function addSelectionToHistory(doc, sel, opId, options) {
|
||||
let hist = doc.history, origin = options && options.origin
|
||||
|
||||
// A new event is started when the previous origin does not match
|
||||
// the current, or the origins don't allow matching. Origins
|
||||
// starting with * are always merged, those starting with + are
|
||||
// merged when similar and close together in time.
|
||||
if (opId == hist.lastSelOp ||
|
||||
(origin && hist.lastSelOrigin == origin &&
|
||||
(hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin ||
|
||||
selectionEventCanBeMerged(doc, origin, lst(hist.done), sel))))
|
||||
hist.done[hist.done.length - 1] = sel
|
||||
else
|
||||
pushSelectionToHistory(sel, hist.done)
|
||||
|
||||
hist.lastSelTime = +new Date
|
||||
hist.lastSelOrigin = origin
|
||||
hist.lastSelOp = opId
|
||||
if (options && options.clearRedo !== false)
|
||||
clearSelectionEvents(hist.undone)
|
||||
}
|
||||
|
||||
export function pushSelectionToHistory(sel, dest) {
|
||||
let top = lst(dest)
|
||||
if (!(top && top.ranges && top.equals(sel)))
|
||||
dest.push(sel)
|
||||
}
|
||||
|
||||
// Used to store marked span information in the history.
|
||||
function attachLocalSpans(doc, change, from, to) {
|
||||
let existing = change["spans_" + doc.id], n = 0
|
||||
doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), line => {
|
||||
if (line.markedSpans)
|
||||
(existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans
|
||||
++n
|
||||
})
|
||||
}
|
||||
|
||||
// When un/re-doing restores text containing marked spans, those
|
||||
// that have been explicitly cleared should not be restored.
|
||||
function removeClearedSpans(spans) {
|
||||
if (!spans) return null
|
||||
let out
|
||||
for (let i = 0; i < spans.length; ++i) {
|
||||
if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i) }
|
||||
else if (out) out.push(spans[i])
|
||||
}
|
||||
return !out ? spans : out.length ? out : null
|
||||
}
|
||||
|
||||
// Retrieve and filter the old marked spans stored in a change event.
|
||||
function getOldSpans(doc, change) {
|
||||
let found = change["spans_" + doc.id]
|
||||
if (!found) return null
|
||||
let nw = []
|
||||
for (let i = 0; i < change.text.length; ++i)
|
||||
nw.push(removeClearedSpans(found[i]))
|
||||
return nw
|
||||
}
|
||||
|
||||
// Used for un/re-doing changes from the history. Combines the
|
||||
// result of computing the existing spans with the set of spans that
|
||||
// existed in the history (so that deleting around a span and then
|
||||
// undoing brings back the span).
|
||||
export function mergeOldSpans(doc, change) {
|
||||
let old = getOldSpans(doc, change)
|
||||
let stretched = stretchSpansOverChange(doc, change)
|
||||
if (!old) return stretched
|
||||
if (!stretched) return old
|
||||
|
||||
for (let i = 0; i < old.length; ++i) {
|
||||
let oldCur = old[i], stretchCur = stretched[i]
|
||||
if (oldCur && stretchCur) {
|
||||
spans: for (let j = 0; j < stretchCur.length; ++j) {
|
||||
let span = stretchCur[j]
|
||||
for (let k = 0; k < oldCur.length; ++k)
|
||||
if (oldCur[k].marker == span.marker) continue spans
|
||||
oldCur.push(span)
|
||||
}
|
||||
} else if (stretchCur) {
|
||||
old[i] = stretchCur
|
||||
}
|
||||
}
|
||||
return old
|
||||
}
|
||||
|
||||
// Used both to provide a JSON-safe object in .getHistory, and, when
|
||||
// detaching a document, to split the history in two
|
||||
export function copyHistoryArray(events, newGroup, instantiateSel) {
|
||||
let copy = []
|
||||
for (let i = 0; i < events.length; ++i) {
|
||||
let event = events[i]
|
||||
if (event.ranges) {
|
||||
copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event)
|
||||
continue
|
||||
}
|
||||
let changes = event.changes, newChanges = []
|
||||
copy.push({changes: newChanges})
|
||||
for (let j = 0; j < changes.length; ++j) {
|
||||
let change = changes[j], m
|
||||
newChanges.push({from: change.from, to: change.to, text: change.text})
|
||||
if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) {
|
||||
if (indexOf(newGroup, Number(m[1])) > -1) {
|
||||
lst(newChanges)[prop] = change[prop]
|
||||
delete change[prop]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return copy
|
||||
}
|
||||
78
node_modules/codemirror/src/model/line_widget.js
generated
vendored
Normal file
78
node_modules/codemirror/src/model/line_widget.js
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
import { runInOp } from "../display/operations.js"
|
||||
import { addToScrollTop } from "../display/scrolling.js"
|
||||
import { regLineChange } from "../display/view_tracking.js"
|
||||
import { heightAtLine, lineIsHidden } from "../line/spans.js"
|
||||
import { lineNo, updateLineHeight } from "../line/utils_line.js"
|
||||
import { widgetHeight } from "../measurement/widgets.js"
|
||||
import { changeLine } from "./changes.js"
|
||||
import { eventMixin } from "../util/event.js"
|
||||
import { signalLater } from "../util/operation_group.js"
|
||||
|
||||
// Line widgets are block elements displayed above or below a line.
|
||||
|
||||
export class LineWidget {
|
||||
constructor(doc, node, options) {
|
||||
if (options) for (let opt in options) if (options.hasOwnProperty(opt))
|
||||
this[opt] = options[opt]
|
||||
this.doc = doc
|
||||
this.node = node
|
||||
}
|
||||
|
||||
clear() {
|
||||
let cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line)
|
||||
if (no == null || !ws) return
|
||||
for (let i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1)
|
||||
if (!ws.length) line.widgets = null
|
||||
let height = widgetHeight(this)
|
||||
updateLineHeight(line, Math.max(0, line.height - height))
|
||||
if (cm) {
|
||||
runInOp(cm, () => {
|
||||
adjustScrollWhenAboveVisible(cm, line, -height)
|
||||
regLineChange(cm, no, "widget")
|
||||
})
|
||||
signalLater(cm, "lineWidgetCleared", cm, this, no)
|
||||
}
|
||||
}
|
||||
|
||||
changed() {
|
||||
let oldH = this.height, cm = this.doc.cm, line = this.line
|
||||
this.height = null
|
||||
let diff = widgetHeight(this) - oldH
|
||||
if (!diff) return
|
||||
if (!lineIsHidden(this.doc, line)) updateLineHeight(line, line.height + diff)
|
||||
if (cm) {
|
||||
runInOp(cm, () => {
|
||||
cm.curOp.forceUpdate = true
|
||||
adjustScrollWhenAboveVisible(cm, line, diff)
|
||||
signalLater(cm, "lineWidgetChanged", cm, this, lineNo(line))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
eventMixin(LineWidget)
|
||||
|
||||
function adjustScrollWhenAboveVisible(cm, line, diff) {
|
||||
if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop))
|
||||
addToScrollTop(cm, diff)
|
||||
}
|
||||
|
||||
export function addLineWidget(doc, handle, node, options) {
|
||||
let widget = new LineWidget(doc, node, options)
|
||||
let cm = doc.cm
|
||||
if (cm && widget.noHScroll) cm.display.alignWidgets = true
|
||||
changeLine(doc, handle, "widget", line => {
|
||||
let widgets = line.widgets || (line.widgets = [])
|
||||
if (widget.insertAt == null) widgets.push(widget)
|
||||
else widgets.splice(Math.min(widgets.length, Math.max(0, widget.insertAt)), 0, widget)
|
||||
widget.line = line
|
||||
if (cm && !lineIsHidden(doc, line)) {
|
||||
let aboveVisible = heightAtLine(line) < doc.scrollTop
|
||||
updateLineHeight(line, line.height + widgetHeight(widget))
|
||||
if (aboveVisible) addToScrollTop(cm, widget.height)
|
||||
cm.curOp.forceUpdate = true
|
||||
}
|
||||
return true
|
||||
})
|
||||
if (cm) signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle))
|
||||
return widget
|
||||
}
|
||||
293
node_modules/codemirror/src/model/mark_text.js
generated
vendored
Normal file
293
node_modules/codemirror/src/model/mark_text.js
generated
vendored
Normal file
@@ -0,0 +1,293 @@
|
||||
import { eltP } from "../util/dom.js"
|
||||
import { eventMixin, hasHandler, on } from "../util/event.js"
|
||||
import { endOperation, operation, runInOp, startOperation } from "../display/operations.js"
|
||||
import { clipPos, cmp, Pos } from "../line/pos.js"
|
||||
import { lineNo, updateLineHeight } from "../line/utils_line.js"
|
||||
import { clearLineMeasurementCacheFor, findViewForLine, textHeight } from "../measurement/position_measurement.js"
|
||||
import { seeReadOnlySpans, seeCollapsedSpans } from "../line/saw_special_spans.js"
|
||||
import { addMarkedSpan, conflictingCollapsedRange, getMarkedSpanFor, lineIsHidden, lineLength, MarkedSpan, removeMarkedSpan, visualLine } from "../line/spans.js"
|
||||
import { copyObj, indexOf, lst } from "../util/misc.js"
|
||||
import { signalLater } from "../util/operation_group.js"
|
||||
import { widgetHeight } from "../measurement/widgets.js"
|
||||
import { regChange, regLineChange } from "../display/view_tracking.js"
|
||||
|
||||
import { linkedDocs } from "./document_data.js"
|
||||
import { addChangeToHistory } from "./history.js"
|
||||
import { reCheckSelection } from "./selection_updates.js"
|
||||
|
||||
// TEXTMARKERS
|
||||
|
||||
// Created with markText and setBookmark methods. A TextMarker is a
|
||||
// handle that can be used to clear or find a marked position in the
|
||||
// document. Line objects hold arrays (markedSpans) containing
|
||||
// {from, to, marker} object pointing to such marker objects, and
|
||||
// indicating that such a marker is present on that line. Multiple
|
||||
// lines may point to the same marker when it spans across lines.
|
||||
// The spans will have null for their from/to properties when the
|
||||
// marker continues beyond the start/end of the line. Markers have
|
||||
// links back to the lines they currently touch.
|
||||
|
||||
// Collapsed markers have unique ids, in order to be able to order
|
||||
// them, which is needed for uniquely determining an outer marker
|
||||
// when they overlap (they may nest, but not partially overlap).
|
||||
let nextMarkerId = 0
|
||||
|
||||
export class TextMarker {
|
||||
constructor(doc, type) {
|
||||
this.lines = []
|
||||
this.type = type
|
||||
this.doc = doc
|
||||
this.id = ++nextMarkerId
|
||||
}
|
||||
|
||||
// Clear the marker.
|
||||
clear() {
|
||||
if (this.explicitlyCleared) return
|
||||
let cm = this.doc.cm, withOp = cm && !cm.curOp
|
||||
if (withOp) startOperation(cm)
|
||||
if (hasHandler(this, "clear")) {
|
||||
let found = this.find()
|
||||
if (found) signalLater(this, "clear", found.from, found.to)
|
||||
}
|
||||
let min = null, max = null
|
||||
for (let i = 0; i < this.lines.length; ++i) {
|
||||
let line = this.lines[i]
|
||||
let span = getMarkedSpanFor(line.markedSpans, this)
|
||||
if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text")
|
||||
else if (cm) {
|
||||
if (span.to != null) max = lineNo(line)
|
||||
if (span.from != null) min = lineNo(line)
|
||||
}
|
||||
line.markedSpans = removeMarkedSpan(line.markedSpans, span)
|
||||
if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm)
|
||||
updateLineHeight(line, textHeight(cm.display))
|
||||
}
|
||||
if (cm && this.collapsed && !cm.options.lineWrapping) for (let i = 0; i < this.lines.length; ++i) {
|
||||
let visual = visualLine(this.lines[i]), len = lineLength(visual)
|
||||
if (len > cm.display.maxLineLength) {
|
||||
cm.display.maxLine = visual
|
||||
cm.display.maxLineLength = len
|
||||
cm.display.maxLineChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
if (min != null && cm && this.collapsed) regChange(cm, min, max + 1)
|
||||
this.lines.length = 0
|
||||
this.explicitlyCleared = true
|
||||
if (this.atomic && this.doc.cantEdit) {
|
||||
this.doc.cantEdit = false
|
||||
if (cm) reCheckSelection(cm.doc)
|
||||
}
|
||||
if (cm) signalLater(cm, "markerCleared", cm, this, min, max)
|
||||
if (withOp) endOperation(cm)
|
||||
if (this.parent) this.parent.clear()
|
||||
}
|
||||
|
||||
// Find the position of the marker in the document. Returns a {from,
|
||||
// to} object by default. Side can be passed to get a specific side
|
||||
// -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the
|
||||
// Pos objects returned contain a line object, rather than a line
|
||||
// number (used to prevent looking up the same line twice).
|
||||
find(side, lineObj) {
|
||||
if (side == null && this.type == "bookmark") side = 1
|
||||
let from, to
|
||||
for (let i = 0; i < this.lines.length; ++i) {
|
||||
let line = this.lines[i]
|
||||
let span = getMarkedSpanFor(line.markedSpans, this)
|
||||
if (span.from != null) {
|
||||
from = Pos(lineObj ? line : lineNo(line), span.from)
|
||||
if (side == -1) return from
|
||||
}
|
||||
if (span.to != null) {
|
||||
to = Pos(lineObj ? line : lineNo(line), span.to)
|
||||
if (side == 1) return to
|
||||
}
|
||||
}
|
||||
return from && {from: from, to: to}
|
||||
}
|
||||
|
||||
// Signals that the marker's widget changed, and surrounding layout
|
||||
// should be recomputed.
|
||||
changed() {
|
||||
let pos = this.find(-1, true), widget = this, cm = this.doc.cm
|
||||
if (!pos || !cm) return
|
||||
runInOp(cm, () => {
|
||||
let line = pos.line, lineN = lineNo(pos.line)
|
||||
let view = findViewForLine(cm, lineN)
|
||||
if (view) {
|
||||
clearLineMeasurementCacheFor(view)
|
||||
cm.curOp.selectionChanged = cm.curOp.forceUpdate = true
|
||||
}
|
||||
cm.curOp.updateMaxLine = true
|
||||
if (!lineIsHidden(widget.doc, line) && widget.height != null) {
|
||||
let oldHeight = widget.height
|
||||
widget.height = null
|
||||
let dHeight = widgetHeight(widget) - oldHeight
|
||||
if (dHeight)
|
||||
updateLineHeight(line, line.height + dHeight)
|
||||
}
|
||||
signalLater(cm, "markerChanged", cm, this)
|
||||
})
|
||||
}
|
||||
|
||||
attachLine(line) {
|
||||
if (!this.lines.length && this.doc.cm) {
|
||||
let op = this.doc.cm.curOp
|
||||
if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)
|
||||
(op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this)
|
||||
}
|
||||
this.lines.push(line)
|
||||
}
|
||||
|
||||
detachLine(line) {
|
||||
this.lines.splice(indexOf(this.lines, line), 1)
|
||||
if (!this.lines.length && this.doc.cm) {
|
||||
let op = this.doc.cm.curOp
|
||||
;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
eventMixin(TextMarker)
|
||||
|
||||
// Create a marker, wire it up to the right lines, and
|
||||
export function markText(doc, from, to, options, type) {
|
||||
// Shared markers (across linked documents) are handled separately
|
||||
// (markTextShared will call out to this again, once per
|
||||
// document).
|
||||
if (options && options.shared) return markTextShared(doc, from, to, options, type)
|
||||
// Ensure we are in an operation.
|
||||
if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type)
|
||||
|
||||
let marker = new TextMarker(doc, type), diff = cmp(from, to)
|
||||
if (options) copyObj(options, marker, false)
|
||||
// Don't connect empty markers unless clearWhenEmpty is false
|
||||
if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false)
|
||||
return marker
|
||||
if (marker.replacedWith) {
|
||||
// Showing up as a widget implies collapsed (widget replaces text)
|
||||
marker.collapsed = true
|
||||
marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget")
|
||||
if (!options.handleMouseEvents) marker.widgetNode.setAttribute("cm-ignore-events", "true")
|
||||
if (options.insertLeft) marker.widgetNode.insertLeft = true
|
||||
}
|
||||
if (marker.collapsed) {
|
||||
if (conflictingCollapsedRange(doc, from.line, from, to, marker) ||
|
||||
from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker))
|
||||
throw new Error("Inserting collapsed marker partially overlapping an existing one")
|
||||
seeCollapsedSpans()
|
||||
}
|
||||
|
||||
if (marker.addToHistory)
|
||||
addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN)
|
||||
|
||||
let curLine = from.line, cm = doc.cm, updateMaxLine
|
||||
doc.iter(curLine, to.line + 1, line => {
|
||||
if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine)
|
||||
updateMaxLine = true
|
||||
if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0)
|
||||
addMarkedSpan(line, new MarkedSpan(marker,
|
||||
curLine == from.line ? from.ch : null,
|
||||
curLine == to.line ? to.ch : null), doc.cm && doc.cm.curOp)
|
||||
++curLine
|
||||
})
|
||||
// lineIsHidden depends on the presence of the spans, so needs a second pass
|
||||
if (marker.collapsed) doc.iter(from.line, to.line + 1, line => {
|
||||
if (lineIsHidden(doc, line)) updateLineHeight(line, 0)
|
||||
})
|
||||
|
||||
if (marker.clearOnEnter) on(marker, "beforeCursorEnter", () => marker.clear())
|
||||
|
||||
if (marker.readOnly) {
|
||||
seeReadOnlySpans()
|
||||
if (doc.history.done.length || doc.history.undone.length)
|
||||
doc.clearHistory()
|
||||
}
|
||||
if (marker.collapsed) {
|
||||
marker.id = ++nextMarkerId
|
||||
marker.atomic = true
|
||||
}
|
||||
if (cm) {
|
||||
// Sync editor state
|
||||
if (updateMaxLine) cm.curOp.updateMaxLine = true
|
||||
if (marker.collapsed)
|
||||
regChange(cm, from.line, to.line + 1)
|
||||
else if (marker.className || marker.startStyle || marker.endStyle || marker.css ||
|
||||
marker.attributes || marker.title)
|
||||
for (let i = from.line; i <= to.line; i++) regLineChange(cm, i, "text")
|
||||
if (marker.atomic) reCheckSelection(cm.doc)
|
||||
signalLater(cm, "markerAdded", cm, marker)
|
||||
}
|
||||
return marker
|
||||
}
|
||||
|
||||
// SHARED TEXTMARKERS
|
||||
|
||||
// A shared marker spans multiple linked documents. It is
|
||||
// implemented as a meta-marker-object controlling multiple normal
|
||||
// markers.
|
||||
export class SharedTextMarker {
|
||||
constructor(markers, primary) {
|
||||
this.markers = markers
|
||||
this.primary = primary
|
||||
for (let i = 0; i < markers.length; ++i)
|
||||
markers[i].parent = this
|
||||
}
|
||||
|
||||
clear() {
|
||||
if (this.explicitlyCleared) return
|
||||
this.explicitlyCleared = true
|
||||
for (let i = 0; i < this.markers.length; ++i)
|
||||
this.markers[i].clear()
|
||||
signalLater(this, "clear")
|
||||
}
|
||||
|
||||
find(side, lineObj) {
|
||||
return this.primary.find(side, lineObj)
|
||||
}
|
||||
}
|
||||
eventMixin(SharedTextMarker)
|
||||
|
||||
function markTextShared(doc, from, to, options, type) {
|
||||
options = copyObj(options)
|
||||
options.shared = false
|
||||
let markers = [markText(doc, from, to, options, type)], primary = markers[0]
|
||||
let widget = options.widgetNode
|
||||
linkedDocs(doc, doc => {
|
||||
if (widget) options.widgetNode = widget.cloneNode(true)
|
||||
markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type))
|
||||
for (let i = 0; i < doc.linked.length; ++i)
|
||||
if (doc.linked[i].isParent) return
|
||||
primary = lst(markers)
|
||||
})
|
||||
return new SharedTextMarker(markers, primary)
|
||||
}
|
||||
|
||||
export function findSharedMarkers(doc) {
|
||||
return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), m => m.parent)
|
||||
}
|
||||
|
||||
export function copySharedMarkers(doc, markers) {
|
||||
for (let i = 0; i < markers.length; i++) {
|
||||
let marker = markers[i], pos = marker.find()
|
||||
let mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to)
|
||||
if (cmp(mFrom, mTo)) {
|
||||
let subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type)
|
||||
marker.markers.push(subMark)
|
||||
subMark.parent = marker
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function detachSharedMarkers(markers) {
|
||||
for (let i = 0; i < markers.length; i++) {
|
||||
let marker = markers[i], linked = [marker.primary.doc]
|
||||
linkedDocs(marker.primary.doc, d => linked.push(d))
|
||||
for (let j = 0; j < marker.markers.length; j++) {
|
||||
let subMarker = marker.markers[j]
|
||||
if (indexOf(linked, subMarker.doc) == -1) {
|
||||
subMarker.parent = null
|
||||
marker.markers.splice(j--, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
84
node_modules/codemirror/src/model/selection.js
generated
vendored
Normal file
84
node_modules/codemirror/src/model/selection.js
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
import { cmp, copyPos, equalCursorPos, maxPos, minPos } from "../line/pos.js"
|
||||
import { indexOf } from "../util/misc.js"
|
||||
|
||||
// Selection objects are immutable. A new one is created every time
|
||||
// the selection changes. A selection is one or more non-overlapping
|
||||
// (and non-touching) ranges, sorted, and an integer that indicates
|
||||
// which one is the primary selection (the one that's scrolled into
|
||||
// view, that getCursor returns, etc).
|
||||
export class Selection {
|
||||
constructor(ranges, primIndex) {
|
||||
this.ranges = ranges
|
||||
this.primIndex = primIndex
|
||||
}
|
||||
|
||||
primary() { return this.ranges[this.primIndex] }
|
||||
|
||||
equals(other) {
|
||||
if (other == this) return true
|
||||
if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false
|
||||
for (let i = 0; i < this.ranges.length; i++) {
|
||||
let here = this.ranges[i], there = other.ranges[i]
|
||||
if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
deepCopy() {
|
||||
let out = []
|
||||
for (let i = 0; i < this.ranges.length; i++)
|
||||
out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head))
|
||||
return new Selection(out, this.primIndex)
|
||||
}
|
||||
|
||||
somethingSelected() {
|
||||
for (let i = 0; i < this.ranges.length; i++)
|
||||
if (!this.ranges[i].empty()) return true
|
||||
return false
|
||||
}
|
||||
|
||||
contains(pos, end) {
|
||||
if (!end) end = pos
|
||||
for (let i = 0; i < this.ranges.length; i++) {
|
||||
let range = this.ranges[i]
|
||||
if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0)
|
||||
return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
export class Range {
|
||||
constructor(anchor, head) {
|
||||
this.anchor = anchor; this.head = head
|
||||
}
|
||||
|
||||
from() { return minPos(this.anchor, this.head) }
|
||||
to() { return maxPos(this.anchor, this.head) }
|
||||
empty() { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch }
|
||||
}
|
||||
|
||||
// Take an unsorted, potentially overlapping set of ranges, and
|
||||
// build a selection out of it. 'Consumes' ranges array (modifying
|
||||
// it).
|
||||
export function normalizeSelection(cm, ranges, primIndex) {
|
||||
let mayTouch = cm && cm.options.selectionsMayTouch
|
||||
let prim = ranges[primIndex]
|
||||
ranges.sort((a, b) => cmp(a.from(), b.from()))
|
||||
primIndex = indexOf(ranges, prim)
|
||||
for (let i = 1; i < ranges.length; i++) {
|
||||
let cur = ranges[i], prev = ranges[i - 1]
|
||||
let diff = cmp(prev.to(), cur.from())
|
||||
if (mayTouch && !cur.empty() ? diff > 0 : diff >= 0) {
|
||||
let from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to())
|
||||
let inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head
|
||||
if (i <= primIndex) --primIndex
|
||||
ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to))
|
||||
}
|
||||
}
|
||||
return new Selection(ranges, primIndex)
|
||||
}
|
||||
|
||||
export function simpleSelection(anchor, head) {
|
||||
return new Selection([new Range(anchor, head || anchor)], 0)
|
||||
}
|
||||
216
node_modules/codemirror/src/model/selection_updates.js
generated
vendored
Normal file
216
node_modules/codemirror/src/model/selection_updates.js
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
import { signalLater } from "../util/operation_group.js"
|
||||
import { ensureCursorVisible } from "../display/scrolling.js"
|
||||
import { clipPos, cmp, Pos } from "../line/pos.js"
|
||||
import { getLine } from "../line/utils_line.js"
|
||||
import { hasHandler, signal, signalCursorActivity } from "../util/event.js"
|
||||
import { lst, sel_dontScroll } from "../util/misc.js"
|
||||
|
||||
import { addSelectionToHistory } from "./history.js"
|
||||
import { normalizeSelection, Range, Selection, simpleSelection } from "./selection.js"
|
||||
|
||||
// The 'scroll' parameter given to many of these indicated whether
|
||||
// the new cursor position should be scrolled into view after
|
||||
// modifying the selection.
|
||||
|
||||
// If shift is held or the extend flag is set, extends a range to
|
||||
// include a given position (and optionally a second position).
|
||||
// Otherwise, simply returns the range between the given positions.
|
||||
// Used for cursor motion and such.
|
||||
export function extendRange(range, head, other, extend) {
|
||||
if (extend) {
|
||||
let anchor = range.anchor
|
||||
if (other) {
|
||||
let posBefore = cmp(head, anchor) < 0
|
||||
if (posBefore != (cmp(other, anchor) < 0)) {
|
||||
anchor = head
|
||||
head = other
|
||||
} else if (posBefore != (cmp(head, other) < 0)) {
|
||||
head = other
|
||||
}
|
||||
}
|
||||
return new Range(anchor, head)
|
||||
} else {
|
||||
return new Range(other || head, head)
|
||||
}
|
||||
}
|
||||
|
||||
// Extend the primary selection range, discard the rest.
|
||||
export function extendSelection(doc, head, other, options, extend) {
|
||||
if (extend == null) extend = doc.cm && (doc.cm.display.shift || doc.extend)
|
||||
setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options)
|
||||
}
|
||||
|
||||
// Extend all selections (pos is an array of selections with length
|
||||
// equal the number of selections)
|
||||
export function extendSelections(doc, heads, options) {
|
||||
let out = []
|
||||
let extend = doc.cm && (doc.cm.display.shift || doc.extend)
|
||||
for (let i = 0; i < doc.sel.ranges.length; i++)
|
||||
out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend)
|
||||
let newSel = normalizeSelection(doc.cm, out, doc.sel.primIndex)
|
||||
setSelection(doc, newSel, options)
|
||||
}
|
||||
|
||||
// Updates a single range in the selection.
|
||||
export function replaceOneSelection(doc, i, range, options) {
|
||||
let ranges = doc.sel.ranges.slice(0)
|
||||
ranges[i] = range
|
||||
setSelection(doc, normalizeSelection(doc.cm, ranges, doc.sel.primIndex), options)
|
||||
}
|
||||
|
||||
// Reset the selection to a single range.
|
||||
export function setSimpleSelection(doc, anchor, head, options) {
|
||||
setSelection(doc, simpleSelection(anchor, head), options)
|
||||
}
|
||||
|
||||
// Give beforeSelectionChange handlers a change to influence a
|
||||
// selection update.
|
||||
function filterSelectionChange(doc, sel, options) {
|
||||
let obj = {
|
||||
ranges: sel.ranges,
|
||||
update: function(ranges) {
|
||||
this.ranges = []
|
||||
for (let i = 0; i < ranges.length; i++)
|
||||
this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
|
||||
clipPos(doc, ranges[i].head))
|
||||
},
|
||||
origin: options && options.origin
|
||||
}
|
||||
signal(doc, "beforeSelectionChange", doc, obj)
|
||||
if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj)
|
||||
if (obj.ranges != sel.ranges) return normalizeSelection(doc.cm, obj.ranges, obj.ranges.length - 1)
|
||||
else return sel
|
||||
}
|
||||
|
||||
export function setSelectionReplaceHistory(doc, sel, options) {
|
||||
let done = doc.history.done, last = lst(done)
|
||||
if (last && last.ranges) {
|
||||
done[done.length - 1] = sel
|
||||
setSelectionNoUndo(doc, sel, options)
|
||||
} else {
|
||||
setSelection(doc, sel, options)
|
||||
}
|
||||
}
|
||||
|
||||
// Set a new selection.
|
||||
export function setSelection(doc, sel, options) {
|
||||
setSelectionNoUndo(doc, sel, options)
|
||||
addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options)
|
||||
}
|
||||
|
||||
export function setSelectionNoUndo(doc, sel, options) {
|
||||
if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
|
||||
sel = filterSelectionChange(doc, sel, options)
|
||||
|
||||
let bias = options && options.bias ||
|
||||
(cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1)
|
||||
setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true))
|
||||
|
||||
if (!(options && options.scroll === false) && doc.cm && doc.cm.getOption("readOnly") != "nocursor")
|
||||
ensureCursorVisible(doc.cm)
|
||||
}
|
||||
|
||||
function setSelectionInner(doc, sel) {
|
||||
if (sel.equals(doc.sel)) return
|
||||
|
||||
doc.sel = sel
|
||||
|
||||
if (doc.cm) {
|
||||
doc.cm.curOp.updateInput = 1
|
||||
doc.cm.curOp.selectionChanged = true
|
||||
signalCursorActivity(doc.cm)
|
||||
}
|
||||
signalLater(doc, "cursorActivity", doc)
|
||||
}
|
||||
|
||||
// Verify that the selection does not partially select any atomic
|
||||
// marked ranges.
|
||||
export function reCheckSelection(doc) {
|
||||
setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false))
|
||||
}
|
||||
|
||||
// Return a selection that does not partially select any atomic
|
||||
// ranges.
|
||||
function skipAtomicInSelection(doc, sel, bias, mayClear) {
|
||||
let out
|
||||
for (let i = 0; i < sel.ranges.length; i++) {
|
||||
let range = sel.ranges[i]
|
||||
let old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i]
|
||||
let newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear)
|
||||
let newHead = range.head == range.anchor ? newAnchor : skipAtomic(doc, range.head, old && old.head, bias, mayClear)
|
||||
if (out || newAnchor != range.anchor || newHead != range.head) {
|
||||
if (!out) out = sel.ranges.slice(0, i)
|
||||
out[i] = new Range(newAnchor, newHead)
|
||||
}
|
||||
}
|
||||
return out ? normalizeSelection(doc.cm, out, sel.primIndex) : sel
|
||||
}
|
||||
|
||||
function skipAtomicInner(doc, pos, oldPos, dir, mayClear) {
|
||||
let line = getLine(doc, pos.line)
|
||||
if (line.markedSpans) for (let i = 0; i < line.markedSpans.length; ++i) {
|
||||
let sp = line.markedSpans[i], m = sp.marker
|
||||
|
||||
// Determine if we should prevent the cursor being placed to the left/right of an atomic marker
|
||||
// Historically this was determined using the inclusiveLeft/Right option, but the new way to control it
|
||||
// is with selectLeft/Right
|
||||
let preventCursorLeft = ("selectLeft" in m) ? !m.selectLeft : m.inclusiveLeft
|
||||
let preventCursorRight = ("selectRight" in m) ? !m.selectRight : m.inclusiveRight
|
||||
|
||||
if ((sp.from == null || (preventCursorLeft ? sp.from <= pos.ch : sp.from < pos.ch)) &&
|
||||
(sp.to == null || (preventCursorRight ? sp.to >= pos.ch : sp.to > pos.ch))) {
|
||||
if (mayClear) {
|
||||
signal(m, "beforeCursorEnter")
|
||||
if (m.explicitlyCleared) {
|
||||
if (!line.markedSpans) break
|
||||
else {--i; continue}
|
||||
}
|
||||
}
|
||||
if (!m.atomic) continue
|
||||
|
||||
if (oldPos) {
|
||||
let near = m.find(dir < 0 ? 1 : -1), diff
|
||||
if (dir < 0 ? preventCursorRight : preventCursorLeft)
|
||||
near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null)
|
||||
if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0))
|
||||
return skipAtomicInner(doc, near, pos, dir, mayClear)
|
||||
}
|
||||
|
||||
let far = m.find(dir < 0 ? -1 : 1)
|
||||
if (dir < 0 ? preventCursorLeft : preventCursorRight)
|
||||
far = movePos(doc, far, dir, far.line == pos.line ? line : null)
|
||||
return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null
|
||||
}
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
// Ensure a given position is not inside an atomic range.
|
||||
export function skipAtomic(doc, pos, oldPos, bias, mayClear) {
|
||||
let dir = bias || 1
|
||||
let found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) ||
|
||||
(!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) ||
|
||||
skipAtomicInner(doc, pos, oldPos, -dir, mayClear) ||
|
||||
(!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true))
|
||||
if (!found) {
|
||||
doc.cantEdit = true
|
||||
return Pos(doc.first, 0)
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
function movePos(doc, pos, dir, line) {
|
||||
if (dir < 0 && pos.ch == 0) {
|
||||
if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1))
|
||||
else return null
|
||||
} else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) {
|
||||
if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0)
|
||||
else return null
|
||||
} else {
|
||||
return new Pos(pos.line, pos.ch + dir)
|
||||
}
|
||||
}
|
||||
|
||||
export function selectAll(cm) {
|
||||
cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll)
|
||||
}
|
||||
Reference in New Issue
Block a user