annotate .cms/lib/codemirror/src/line/spans.js @ 1:1d486627aa1e draft default tip

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