view_tracking.js (5385B)
1 import { buildViewArray } from "../line/line_data.js" 2 import { sawCollapsedSpans } from "../line/saw_special_spans.js" 3 import { visualLineEndNo, visualLineNo } from "../line/spans.js" 4 import { findViewIndex } from "../measurement/position_measurement.js" 5 import { indexOf } from "../util/misc.js" 6 7 // Updates the display.view data structure for a given change to the 8 // document. From and to are in pre-change coordinates. Lendiff is 9 // the amount of lines added or subtracted by the change. This is 10 // used for changes that span multiple lines, or change the way 11 // lines are divided into visual lines. regLineChange (below) 12 // registers single-line changes. 13 export function regChange(cm, from, to, lendiff) { 14 if (from == null) from = cm.doc.first 15 if (to == null) to = cm.doc.first + cm.doc.size 16 if (!lendiff) lendiff = 0 17 18 let display = cm.display 19 if (lendiff && to < display.viewTo && 20 (display.updateLineNumbers == null || display.updateLineNumbers > from)) 21 display.updateLineNumbers = from 22 23 cm.curOp.viewChanged = true 24 25 if (from >= display.viewTo) { // Change after 26 if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) 27 resetView(cm) 28 } else if (to <= display.viewFrom) { // Change before 29 if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { 30 resetView(cm) 31 } else { 32 display.viewFrom += lendiff 33 display.viewTo += lendiff 34 } 35 } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap 36 resetView(cm) 37 } else if (from <= display.viewFrom) { // Top overlap 38 let cut = viewCuttingPoint(cm, to, to + lendiff, 1) 39 if (cut) { 40 display.view = display.view.slice(cut.index) 41 display.viewFrom = cut.lineN 42 display.viewTo += lendiff 43 } else { 44 resetView(cm) 45 } 46 } else if (to >= display.viewTo) { // Bottom overlap 47 let cut = viewCuttingPoint(cm, from, from, -1) 48 if (cut) { 49 display.view = display.view.slice(0, cut.index) 50 display.viewTo = cut.lineN 51 } else { 52 resetView(cm) 53 } 54 } else { // Gap in the middle 55 let cutTop = viewCuttingPoint(cm, from, from, -1) 56 let cutBot = viewCuttingPoint(cm, to, to + lendiff, 1) 57 if (cutTop && cutBot) { 58 display.view = display.view.slice(0, cutTop.index) 59 .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) 60 .concat(display.view.slice(cutBot.index)) 61 display.viewTo += lendiff 62 } else { 63 resetView(cm) 64 } 65 } 66 67 let ext = display.externalMeasured 68 if (ext) { 69 if (to < ext.lineN) 70 ext.lineN += lendiff 71 else if (from < ext.lineN + ext.size) 72 display.externalMeasured = null 73 } 74 } 75 76 // Register a change to a single line. Type must be one of "text", 77 // "gutter", "class", "widget" 78 export function regLineChange(cm, line, type) { 79 cm.curOp.viewChanged = true 80 let display = cm.display, ext = cm.display.externalMeasured 81 if (ext && line >= ext.lineN && line < ext.lineN + ext.size) 82 display.externalMeasured = null 83 84 if (line < display.viewFrom || line >= display.viewTo) return 85 let lineView = display.view[findViewIndex(cm, line)] 86 if (lineView.node == null) return 87 let arr = lineView.changes || (lineView.changes = []) 88 if (indexOf(arr, type) == -1) arr.push(type) 89 } 90 91 // Clear the view. 92 export function resetView(cm) { 93 cm.display.viewFrom = cm.display.viewTo = cm.doc.first 94 cm.display.view = [] 95 cm.display.viewOffset = 0 96 } 97 98 function viewCuttingPoint(cm, oldN, newN, dir) { 99 let index = findViewIndex(cm, oldN), diff, view = cm.display.view 100 if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) 101 return {index: index, lineN: newN} 102 let n = cm.display.viewFrom 103 for (let i = 0; i < index; i++) 104 n += view[i].size 105 if (n != oldN) { 106 if (dir > 0) { 107 if (index == view.length - 1) return null 108 diff = (n + view[index].size) - oldN 109 index++ 110 } else { 111 diff = n - oldN 112 } 113 oldN += diff; newN += diff 114 } 115 while (visualLineNo(cm.doc, newN) != newN) { 116 if (index == (dir < 0 ? 0 : view.length - 1)) return null 117 newN += dir * view[index - (dir < 0 ? 1 : 0)].size 118 index += dir 119 } 120 return {index: index, lineN: newN} 121 } 122 123 // Force the view to cover a given range, adding empty view element 124 // or clipping off existing ones as needed. 125 export function adjustView(cm, from, to) { 126 let display = cm.display, view = display.view 127 if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { 128 display.view = buildViewArray(cm, from, to) 129 display.viewFrom = from 130 } else { 131 if (display.viewFrom > from) 132 display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view) 133 else if (display.viewFrom < from) 134 display.view = display.view.slice(findViewIndex(cm, from)) 135 display.viewFrom = from 136 if (display.viewTo < to) 137 display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)) 138 else if (display.viewTo > to) 139 display.view = display.view.slice(0, findViewIndex(cm, to)) 140 } 141 display.viewTo = to 142 } 143 144 // Count the number of lines in the view whose DOM representation is 145 // out of date (or nonexistent). 146 export function countDirtyView(cm) { 147 let view = cm.display.view, dirty = 0 148 for (let i = 0; i < view.length; i++) { 149 let lineView = view[i] 150 if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty 151 } 152 return dirty 153 }