document_data.js (4226B)
1 import { loadMode } from "../display/mode_state.js" 2 import { runInOp } from "../display/operations.js" 3 import { regChange } from "../display/view_tracking.js" 4 import { Line, updateLine } from "../line/line_data.js" 5 import { findMaxLine } from "../line/spans.js" 6 import { getLine } from "../line/utils_line.js" 7 import { estimateLineHeights } from "../measurement/position_measurement.js" 8 import { addClass, rmClass } from "../util/dom.js" 9 import { lst } from "../util/misc.js" 10 import { signalLater } from "../util/operation_group.js" 11 12 // DOCUMENT DATA STRUCTURE 13 14 // By default, updates that start and end at the beginning of a line 15 // are treated specially, in order to make the association of line 16 // widgets and marker elements with the text behave more intuitive. 17 export function isWholeLineUpdate(doc, change) { 18 return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && 19 (!doc.cm || doc.cm.options.wholeLineUpdateBefore) 20 } 21 22 // Perform a change on the document data structure. 23 export function updateDoc(doc, change, markedSpans, estimateHeight) { 24 function spansFor(n) {return markedSpans ? markedSpans[n] : null} 25 function update(line, text, spans) { 26 updateLine(line, text, spans, estimateHeight) 27 signalLater(line, "change", line, change) 28 } 29 function linesFor(start, end) { 30 let result = [] 31 for (let i = start; i < end; ++i) 32 result.push(new Line(text[i], spansFor(i), estimateHeight)) 33 return result 34 } 35 36 let from = change.from, to = change.to, text = change.text 37 let firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line) 38 let lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line 39 40 // Adjust the line structure 41 if (change.full) { 42 doc.insert(0, linesFor(0, text.length)) 43 doc.remove(text.length, doc.size - text.length) 44 } else if (isWholeLineUpdate(doc, change)) { 45 // This is a whole-line replace. Treated specially to make 46 // sure line objects move the way they are supposed to. 47 let added = linesFor(0, text.length - 1) 48 update(lastLine, lastLine.text, lastSpans) 49 if (nlines) doc.remove(from.line, nlines) 50 if (added.length) doc.insert(from.line, added) 51 } else if (firstLine == lastLine) { 52 if (text.length == 1) { 53 update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans) 54 } else { 55 let added = linesFor(1, text.length - 1) 56 added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)) 57 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) 58 doc.insert(from.line + 1, added) 59 } 60 } else if (text.length == 1) { 61 update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)) 62 doc.remove(from.line + 1, nlines) 63 } else { 64 update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) 65 update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans) 66 let added = linesFor(1, text.length - 1) 67 if (nlines > 1) doc.remove(from.line + 1, nlines - 1) 68 doc.insert(from.line + 1, added) 69 } 70 71 signalLater(doc, "change", doc, change) 72 } 73 74 // Call f for all linked documents. 75 export function linkedDocs(doc, f, sharedHistOnly) { 76 function propagate(doc, skip, sharedHist) { 77 if (doc.linked) for (let i = 0; i < doc.linked.length; ++i) { 78 let rel = doc.linked[i] 79 if (rel.doc == skip) continue 80 let shared = sharedHist && rel.sharedHist 81 if (sharedHistOnly && !shared) continue 82 f(rel.doc, shared) 83 propagate(rel.doc, doc, shared) 84 } 85 } 86 propagate(doc, null, true) 87 } 88 89 // Attach a document to an editor. 90 export function attachDoc(cm, doc) { 91 if (doc.cm) throw new Error("This document is already in use.") 92 cm.doc = doc 93 doc.cm = cm 94 estimateLineHeights(cm) 95 loadMode(cm) 96 setDirectionClass(cm) 97 if (!cm.options.lineWrapping) findMaxLine(cm) 98 cm.options.mode = doc.modeOption 99 regChange(cm) 100 } 101 102 function setDirectionClass(cm) { 103 ;(cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl") 104 } 105 106 export function directionChanged(cm) { 107 runInOp(cm, () => { 108 setDirectionClass(cm) 109 regChange(cm) 110 }) 111 }