File modules/editor/codemirror/src/line/spans.min.js

Last commit: Tue May 22 22:39:53 2018 +0200	Jan Dankert	Fix für PHP 7.2: 'Object' darf nun nicht mehr als Klassennamen verwendet werden. AUCH NICHT IN EINEM NAMESPACE! WTF, wozu habe ich das in einen verfickten Namespace gepackt? Wozu soll der sonst da sein??? Amateure. Daher nun notgedrungen unbenannt in 'BaseObject'.
1 import { indexOf, lst } from "../util/misc.js" 2 3 import { cmp } from "./pos.js" 4 import { sawCollapsedSpans } from "./saw_special_spans.js" 5 import { getLine, isLine, lineNo } from "./utils_line.js" 6 7 // TEXTMARKER SPANS 8 9 export function MarkedSpan(marker, from, to) { 10 this.marker = marker 11 this.from = from; this.to = to 12 } 13 14 // Search an array of spans for a span matching the given marker. 15 export function getMarkedSpanFor(spans, marker) { 16 if (spans) for (let i = 0; i < spans.length; ++i) { 17 let span = spans[i] 18 if (span.marker == marker) return span 19 } 20 } 21 // Remove a span from an array, returning undefined if no spans are 22 // left (we don't store arrays for lines without spans). 23 export function removeMarkedSpan(spans, span) { 24 let r 25 for (let i = 0; i < spans.length; ++i) 26 if (spans[i] != span) (r || (r = [])).push(spans[i]) 27 return r 28 } 29 // Add a span to a line. 30 export function addMarkedSpan(line, span) { 31 line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span] 32 span.marker.attachLine(line) 33 } 34 35 // Used for the algorithm that adjusts markers for a change in the 36 // document. These functions cut an array of spans at a given 37 // character position, returning an array of remaining chunks (or 38 // undefined if nothing remains). 39 function markedSpansBefore(old, startCh, isInsert) { 40 let nw 41 if (old) for (let i = 0; i < old.length; ++i) { 42 let span = old[i], marker = span.marker 43 let startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh) 44 if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { 45 let endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh) 46 ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)) 47 } 48 } 49 return nw 50 } 51 function markedSpansAfter(old, endCh, isInsert) { 52 let nw 53 if (old) for (let i = 0; i < old.length; ++i) { 54 let span = old[i], marker = span.marker 55 let endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh) 56 if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { 57 let startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh) 58 ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, 59 span.to == null ? null : span.to - endCh)) 60 } 61 } 62 return nw 63 } 64 65 // Given a change object, compute the new set of marker spans that 66 // cover the line in which the change took place. Removes spans 67 // entirely within the change, reconnects spans belonging to the 68 // same marker that appear on both sides of the change, and cuts off 69 // spans partially within the change. Returns an array of span 70 // arrays with one element for each line in (after) the change. 71 export function stretchSpansOverChange(doc, change) { 72 if (change.full) return null 73 let oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans 74 let oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans 75 if (!oldFirst && !oldLast) return null 76 77 let startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0 78 // Get the spans that 'stick out' on both sides 79 let first = markedSpansBefore(oldFirst, startCh, isInsert) 80 let last = markedSpansAfter(oldLast, endCh, isInsert) 81 82 // Next, merge those two ends 83 let sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0) 84 if (first) { 85 // Fix up .to properties of first 86 for (let i = 0; i < first.length; ++i) { 87 let span = first[i] 88 if (span.to == null) { 89 let found = getMarkedSpanFor(last, span.marker) 90 if (!found) span.to = startCh 91 else if (sameLine) span.to = found.to == null ? null : found.to + offset 92 } 93 } 94 } 95 if (last) { 96 // Fix up .from in last (or move them into first in case of sameLine) 97 for (let i = 0; i < last.length; ++i) { 98 let span = last[i] 99 if (span.to != null) span.to += offset 100 if (span.from == null) { 101 let found = getMarkedSpanFor(first, span.marker) 102 if (!found) { 103 span.from = offset 104 if (sameLine) (first || (first = [])).push(span) 105 } 106 } else { 107 span.from += offset 108 if (sameLine) (first || (first = [])).push(span) 109 } 110 } 111 } 112 // Make sure we didn't create any zero-length spans 113 if (first) first = clearEmptySpans(first) 114 if (last && last != first) last = clearEmptySpans(last) 115 116 let newMarkers = [first] 117 if (!sameLine) { 118 // Fill gap with whole-line-spans 119 let gap = change.text.length - 2, gapMarkers 120 if (gap > 0 && first) 121 for (let i = 0; i < first.length; ++i) 122 if (first[i].to == null) 123 (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null)) 124 for (let i = 0; i < gap; ++i) 125 newMarkers.push(gapMarkers) 126 newMarkers.push(last) 127 } 128 return newMarkers 129 } 130 131 // Remove spans that are empty and don't have a clearWhenEmpty 132 // option of false. 133 function clearEmptySpans(spans) { 134 for (let i = 0; i < spans.length; ++i) { 135 let span = spans[i] 136 if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) 137 spans.splice(i--, 1) 138 } 139 if (!spans.length) return null 140 return spans 141 } 142 143 // Used to 'clip' out readOnly ranges when making a change. 144 export function removeReadOnlyRanges(doc, from, to) { 145 let markers = null 146 doc.iter(from.line, to.line + 1, line => { 147 if (line.markedSpans) for (let i = 0; i < line.markedSpans.length; ++i) { 148 let mark = line.markedSpans[i].marker 149 if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) 150 (markers || (markers = [])).push(mark) 151 } 152 }) 153 if (!markers) return null 154 let parts = [{from: from, to: to}] 155 for (let i = 0; i < markers.length; ++i) { 156 let mk = markers[i], m = mk.find(0) 157 for (let j = 0; j < parts.length; ++j) { 158 let p = parts[j] 159 if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue 160 let newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to) 161 if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) 162 newParts.push({from: p.from, to: m.from}) 163 if (dto > 0 || !mk.inclusiveRight && !dto) 164 newParts.push({from: m.to, to: p.to}) 165 parts.splice.apply(parts, newParts) 166 j += newParts.length - 3 167 } 168 } 169 return parts 170 } 171 172 // Connect or disconnect spans from a line. 173 export function detachMarkedSpans(line) { 174 let spans = line.markedSpans 175 if (!spans) return 176 for (let i = 0; i < spans.length; ++i) 177 spans[i].marker.detachLine(line) 178 line.markedSpans = null 179 } 180 export function attachMarkedSpans(line, spans) { 181 if (!spans) return 182 for (let i = 0; i < spans.length; ++i) 183 spans[i].marker.attachLine(line) 184 line.markedSpans = spans 185 } 186 187 // Helpers used when computing which overlapping collapsed span 188 // counts as the larger one. 189 function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 } 190 function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 } 191 192 // Returns a number indicating which of two overlapping collapsed 193 // spans is larger (and thus includes the other). Falls back to 194 // comparing ids when the spans cover exactly the same range. 195 export function compareCollapsedMarkers(a, b) { 196 let lenDiff = a.lines.length - b.lines.length 197 if (lenDiff != 0) return lenDiff 198 let aPos = a.find(), bPos = b.find() 199 let fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b) 200 if (fromCmp) return -fromCmp 201 let toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b) 202 if (toCmp) return toCmp 203 return b.id - a.id 204 } 205 206 // Find out whether a line ends or starts in a collapsed span. If 207 // so, return the marker for that span. 208 function collapsedSpanAtSide(line, start) { 209 let sps = sawCollapsedSpans && line.markedSpans, found 210 if (sps) for (let sp, i = 0; i < sps.length; ++i) { 211 sp = sps[i] 212 if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && 213 (!found || compareCollapsedMarkers(found, sp.marker) < 0)) 214 found = sp.marker 215 } 216 return found 217 } 218 export function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) } 219 export function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) } 220 221 // Test whether there exists a collapsed span that partially 222 // overlaps (covers the start or end, but not both) of a new span. 223 // Such overlap is not allowed. 224 export function conflictingCollapsedRange(doc, lineNo, from, to, marker) { 225 let line = getLine(doc, lineNo) 226 let sps = sawCollapsedSpans && line.markedSpans 227 if (sps) for (let i = 0; i < sps.length; ++i) { 228 let sp = sps[i] 229 if (!sp.marker.collapsed) continue 230 let found = sp.marker.find(0) 231 let fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker) 232 let toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker) 233 if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue 234 if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) || 235 fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0)) 236 return true 237 } 238 } 239 240 // A visual line is a line as drawn on the screen. Folding, for 241 // example, can cause multiple logical lines to appear on the same 242 // visual line. This finds the start of the visual line that the 243 // given line is part of (usually that is the line itself). 244 export function visualLine(line) { 245 let merged 246 while (merged = collapsedSpanAtStart(line)) 247 line = merged.find(-1, true).line 248 return line 249 } 250 251 export function visualLineEnd(line) { 252 let merged 253 while (merged = collapsedSpanAtEnd(line)) 254 line = merged.find(1, true).line 255 return line 256 } 257 258 // Returns an array of logical lines that continue the visual line 259 // started by the argument, or undefined if there are no such lines. 260 export function visualLineContinued(line) { 261 let merged, lines 262 while (merged = collapsedSpanAtEnd(line)) { 263 line = merged.find(1, true).line 264 ;(lines || (lines = [])).push(line) 265 } 266 return lines 267 } 268 269 // Get the line number of the start of the visual line that the 270 // given line number is part of. 271 export function visualLineNo(doc, lineN) { 272 let line = getLine(doc, lineN), vis = visualLine(line) 273 if (line == vis) return lineN 274 return lineNo(vis) 275 } 276 277 // Get the line number of the start of the next visual line after 278 // the given line. 279 export function visualLineEndNo(doc, lineN) { 280 if (lineN > doc.lastLine()) return lineN 281 let line = getLine(doc, lineN), merged 282 if (!lineIsHidden(doc, line)) return lineN 283 while (merged = collapsedSpanAtEnd(line)) 284 line = merged.find(1, true).line 285 return lineNo(line) + 1 286 } 287 288 // Compute whether a line is hidden. Lines count as hidden when they 289 // are part of a visual line that starts with another line, or when 290 // they are entirely covered by collapsed, non-widget span. 291 export function lineIsHidden(doc, line) { 292 let sps = sawCollapsedSpans && line.markedSpans 293 if (sps) for (let sp, i = 0; i < sps.length; ++i) { 294 sp = sps[i] 295 if (!sp.marker.collapsed) continue 296 if (sp.from == null) return true 297 if (sp.marker.widgetNode) continue 298 if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) 299 return true 300 } 301 } 302 function lineIsHiddenInner(doc, line, span) { 303 if (span.to == null) { 304 let end = span.marker.find(1, true) 305 return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)) 306 } 307 if (span.marker.inclusiveRight && span.to == line.text.length) 308 return true 309 for (let sp, i = 0; i < line.markedSpans.length; ++i) { 310 sp = line.markedSpans[i] 311 if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && 312 (sp.to == null || sp.to != span.from) && 313 (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && 314 lineIsHiddenInner(doc, line, sp)) return true 315 } 316 } 317 318 // Find the height above the given line. 319 export function heightAtLine(lineObj) { 320 lineObj = visualLine(lineObj) 321 322 let h = 0, chunk = lineObj.parent 323 for (let i = 0; i < chunk.lines.length; ++i) { 324 let line = chunk.lines[i] 325 if (line == lineObj) break 326 else h += line.height 327 } 328 for (let p = chunk.parent; p; chunk = p, p = chunk.parent) { 329 for (let i = 0; i < p.children.length; ++i) { 330 let cur = p.children[i] 331 if (cur == chunk) break 332 else h += cur.height 333 } 334 } 335 return h 336 } 337 338 // Compute the character length of a line, taking into account 339 // collapsed ranges (see markText) that might hide parts, and join 340 // other lines onto it. 341 export function lineLength(line) { 342 if (line.height == 0) return 0 343 let len = line.text.length, merged, cur = line 344 while (merged = collapsedSpanAtStart(cur)) { 345 let found = merged.find(0, true) 346 cur = found.from.line 347 len += found.from.ch - found.to.ch 348 } 349 cur = line 350 while (merged = collapsedSpanAtEnd(cur)) { 351 let found = merged.find(0, true) 352 len -= cur.text.length - found.from.ch 353 cur = found.to.line 354 len += cur.text.length - found.to.ch 355 } 356 return len 357 } 358 359 // Find the longest line in the document. 360 export function findMaxLine(cm) { 361 let d = cm.display, doc = cm.doc 362 d.maxLine = getLine(doc, doc.first) 363 d.maxLineLength = lineLength(d.maxLine) 364 d.maxLineChanged = true 365 doc.iter(line => { 366 let len = lineLength(line) 367 if (len > d.maxLineLength) { 368 d.maxLineLength = len 369 d.maxLine = line 370 } 371 }) 372 }
Download modules/editor/codemirror/src/line/spans.min.js
History Tue, 22 May 2018 22:39:53 +0200 Jan Dankert Fix für PHP 7.2: 'Object' darf nun nicht mehr als Klassennamen verwendet werden. AUCH NICHT IN EINEM NAMESPACE! WTF, wozu habe ich das in einen verfickten Namespace gepackt? Wozu soll der sonst da sein??? Amateure. Daher nun notgedrungen unbenannt in 'BaseObject'.