annotate .cms/lib/codemirror/src/line/line_data.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 { getOrder } from "../util/bidi.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
2 import { ie, ie_version, webkit } from "../util/browser.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
3 import { elt, eltP, joinClasses } from "../util/dom.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
4 import { eventMixin, signal } from "../util/event.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
5 import { hasBadBidiRects, zeroWidthElement } from "../util/feature_detection.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
6 import { lst, spaceStr } from "../util/misc.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
7
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
8 import { getLineStyles } from "./highlight.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
9 import { attachMarkedSpans, compareCollapsedMarkers, detachMarkedSpans, lineIsHidden, visualLineContinued } from "./spans.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
10 import { getLine, lineNo, updateLineHeight } from "./utils_line.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
11
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
12 // LINE DATA STRUCTURE
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
13
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
14 // Line objects. These hold state related to a line, including
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
15 // highlighting info (the styles array).
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
16 export class Line {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
17 constructor(text, markedSpans, estimateHeight) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
18 this.text = text
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
19 attachMarkedSpans(this, markedSpans)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
20 this.height = estimateHeight ? estimateHeight(this) : 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
21 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
22
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
23 lineNo() { return lineNo(this) }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
24 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
25 eventMixin(Line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
26
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
27 // Change the content (text, markers) of a line. Automatically
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
28 // invalidates cached information and tries to re-estimate the
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
29 // line's height.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
30 export function updateLine(line, text, markedSpans, estimateHeight) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
31 line.text = text
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
32 if (line.stateAfter) line.stateAfter = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
33 if (line.styles) line.styles = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
34 if (line.order != null) line.order = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
35 detachMarkedSpans(line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
36 attachMarkedSpans(line, markedSpans)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
37 let estHeight = estimateHeight ? estimateHeight(line) : 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
38 if (estHeight != line.height) updateLineHeight(line, estHeight)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
39 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
40
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
41 // Detach a line from the document tree and its markers.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
42 export function cleanUpLine(line) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
43 line.parent = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
44 detachMarkedSpans(line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
45 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
46
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
47 // Convert a style as returned by a mode (either null, or a string
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
48 // containing one or more styles) to a CSS style. This is cached,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
49 // and also looks for line-wide styles.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
50 let styleToClassCache = {}, styleToClassCacheWithMode = {}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
51 function interpretTokenStyle(style, options) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
52 if (!style || /^\s*$/.test(style)) return null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
53 let cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
54 return cache[style] ||
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
55 (cache[style] = style.replace(/\S+/g, "cm-$&"))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
56 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
57
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
58 // Render the DOM representation of the text of a line. Also builds
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
59 // up a 'line map', which points at the DOM nodes that represent
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
60 // specific stretches of text, and is used by the measuring code.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
61 // The returned object contains the DOM node, this map, and
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
62 // information about line-wide styles that were set by the mode.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
63 export function buildLineContent(cm, lineView) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
64 // The padding-right forces the element to have a 'border', which
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
65 // is needed on Webkit to be able to get line-level bounding
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
66 // rectangles for it (in measureChar).
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
67 let content = eltP("span", null, null, webkit ? "padding-right: .1px" : null)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
68 let builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: content,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
69 col: 0, pos: 0, cm: cm,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
70 trailingSpace: false,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
71 splitSpaces: cm.getOption("lineWrapping")}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
72 lineView.measure = {}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
73
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
74 // Iterate over the logical lines that make up this visual line.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
75 for (let i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
76 let line = i ? lineView.rest[i - 1] : lineView.line, order
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
77 builder.pos = 0
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
78 builder.addToken = buildToken
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
79 // Optionally wire in some hacks into the token-rendering
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
80 // algorithm, to deal with browser quirks.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
81 if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction)))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
82 builder.addToken = buildTokenBadBidi(builder.addToken, order)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
83 builder.map = []
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
84 let allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
85 insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
86 if (line.styleClasses) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
87 if (line.styleClasses.bgClass)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
88 builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || "")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
89 if (line.styleClasses.textClass)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
90 builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || "")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
91 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
92
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
93 // Ensure at least a single node is present, for measuring.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
94 if (builder.map.length == 0)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
95 builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure)))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
96
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
97 // Store the map and a cache object for the current logical line
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
98 if (i == 0) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
99 lineView.measure.map = builder.map
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
100 lineView.measure.cache = {}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
101 } else {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
102 ;(lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
103 ;(lineView.measure.caches || (lineView.measure.caches = [])).push({})
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
104 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
105 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
106
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
107 // See issue #2901
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
108 if (webkit) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
109 let last = builder.content.lastChild
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
110 if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab")))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
111 builder.content.className = "cm-tab-wrap-hack"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
112 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
113
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
114 signal(cm, "renderLine", cm, lineView.line, builder.pre)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
115 if (builder.pre.className)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
116 builder.textClass = joinClasses(builder.pre.className, builder.textClass || "")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
117
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
118 return builder
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
119 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
120
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
121 export function defaultSpecialCharPlaceholder(ch) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
122 let token = elt("span", "\u2022", "cm-invalidchar")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
123 token.title = "\\u" + ch.charCodeAt(0).toString(16)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
124 token.setAttribute("aria-label", token.title)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
125 return token
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
126 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
127
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
128 // Build up the DOM representation for a single token, and add it to
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
129 // the line map. Takes care to render special characters separately.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
130 function buildToken(builder, text, style, startStyle, endStyle, css, attributes) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
131 if (!text) return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
132 let displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
133 let special = builder.cm.state.specialChars, mustWrap = false
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
134 let content
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
135 if (!special.test(text)) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
136 builder.col += text.length
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
137 content = document.createTextNode(displayText)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
138 builder.map.push(builder.pos, builder.pos + text.length, content)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
139 if (ie && ie_version < 9) mustWrap = true
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
140 builder.pos += text.length
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
141 } else {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
142 content = document.createDocumentFragment()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
143 let pos = 0
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
144 while (true) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
145 special.lastIndex = pos
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
146 let m = special.exec(text)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
147 let skipped = m ? m.index - pos : text.length - pos
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
148 if (skipped) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
149 let txt = document.createTextNode(displayText.slice(pos, pos + skipped))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
150 if (ie && ie_version < 9) content.appendChild(elt("span", [txt]))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
151 else content.appendChild(txt)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
152 builder.map.push(builder.pos, builder.pos + skipped, txt)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
153 builder.col += skipped
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
154 builder.pos += skipped
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
155 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
156 if (!m) break
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
157 pos += skipped + 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
158 let txt
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
159 if (m[0] == "\t") {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
160 let tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
161 txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
162 txt.setAttribute("role", "presentation")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
163 txt.setAttribute("cm-text", "\t")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
164 builder.col += tabWidth
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
165 } else if (m[0] == "\r" || m[0] == "\n") {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
166 txt = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar"))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
167 txt.setAttribute("cm-text", m[0])
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
168 builder.col += 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
169 } else {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
170 txt = builder.cm.options.specialCharPlaceholder(m[0])
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
171 txt.setAttribute("cm-text", m[0])
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
172 if (ie && ie_version < 9) content.appendChild(elt("span", [txt]))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
173 else content.appendChild(txt)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
174 builder.col += 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
175 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
176 builder.map.push(builder.pos, builder.pos + 1, txt)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
177 builder.pos++
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 builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
181 if (style || startStyle || endStyle || mustWrap || css || attributes) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
182 let fullStyle = style || ""
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
183 if (startStyle) fullStyle += startStyle
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
184 if (endStyle) fullStyle += endStyle
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
185 let token = elt("span", [content], fullStyle, css)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
186 if (attributes) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
187 for (let attr in attributes) if (attributes.hasOwnProperty(attr) && attr != "style" && attr != "class")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
188 token.setAttribute(attr, attributes[attr])
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
189 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
190 return builder.content.appendChild(token)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
191 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
192 builder.content.appendChild(content)
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 // Change some spaces to NBSP to prevent the browser from collapsing
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
196 // trailing spaces at the end of a line when rendering text (issue #1362).
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
197 function splitSpaces(text, trailingBefore) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
198 if (text.length > 1 && !/ /.test(text)) return text
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
199 let spaceBefore = trailingBefore, result = ""
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
200 for (let i = 0; i < text.length; i++) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
201 let ch = text.charAt(i)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
202 if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
203 ch = "\u00a0"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
204 result += ch
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
205 spaceBefore = ch == " "
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
206 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
207 return result
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
208 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
209
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
210 // Work around nonsense dimensions being reported for stretches of
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
211 // right-to-left text.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
212 function buildTokenBadBidi(inner, order) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
213 return (builder, text, style, startStyle, endStyle, css, attributes) => {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
214 style = style ? style + " cm-force-border" : "cm-force-border"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
215 let start = builder.pos, end = start + text.length
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
216 for (;;) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
217 // Find the part that overlaps with the start of this text
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
218 let part
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
219 for (let i = 0; i < order.length; i++) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
220 part = order[i]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
221 if (part.to > start && part.from <= start) break
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
222 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
223 if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, css, attributes)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
224 inner(builder, text.slice(0, part.to - start), style, startStyle, null, css, attributes)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
225 startStyle = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
226 text = text.slice(part.to - start)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
227 start = part.to
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
228 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
229 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
230 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
231
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
232 function buildCollapsedSpan(builder, size, marker, ignoreWidget) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
233 let widget = !ignoreWidget && marker.widgetNode
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
234 if (widget) builder.map.push(builder.pos, builder.pos + size, widget)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
235 if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
236 if (!widget)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
237 widget = builder.content.appendChild(document.createElement("span"))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
238 widget.setAttribute("cm-marker", marker.id)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
239 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
240 if (widget) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
241 builder.cm.display.input.setUneditable(widget)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
242 builder.content.appendChild(widget)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
243 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
244 builder.pos += size
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
245 builder.trailingSpace = false
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
246 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
247
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
248 // Outputs a number of spans to make up a line, taking highlighting
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
249 // and marked text into account.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
250 function insertLineContent(line, builder, styles) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
251 let spans = line.markedSpans, allText = line.text, at = 0
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
252 if (!spans) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
253 for (let i = 1; i < styles.length; i+=2)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
254 builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
255 return
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 let len = allText.length, pos = 0, i = 1, text = "", style, css
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
259 let nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed, attributes
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
260 for (;;) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
261 if (nextChange == pos) { // Update current marker set
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
262 spanStyle = spanEndStyle = spanStartStyle = css = ""
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
263 attributes = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
264 collapsed = null; nextChange = Infinity
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
265 let foundBookmarks = [], endStyles
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
266 for (let j = 0; j < spans.length; ++j) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
267 let sp = spans[j], m = sp.marker
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
268 if (m.type == "bookmark" && sp.from == pos && m.widgetNode) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
269 foundBookmarks.push(m)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
270 } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
271 if (sp.to != null && sp.to != pos && nextChange > sp.to) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
272 nextChange = sp.to
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
273 spanEndStyle = ""
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
274 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
275 if (m.className) spanStyle += " " + m.className
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
276 if (m.css) css = (css ? css + ";" : "") + m.css
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
277 if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
278 if (m.endStyle && sp.to == nextChange) (endStyles || (endStyles = [])).push(m.endStyle, sp.to)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
279 // support for the old title property
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
280 // https://github.com/codemirror/CodeMirror/pull/5673
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
281 if (m.title) (attributes || (attributes = {})).title = m.title
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
282 if (m.attributes) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
283 for (let attr in m.attributes)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
284 (attributes || (attributes = {}))[attr] = m.attributes[attr]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
285 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
286 if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
287 collapsed = sp
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
288 } else if (sp.from > pos && nextChange > sp.from) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
289 nextChange = sp.from
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
290 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
291 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
292 if (endStyles) for (let j = 0; j < endStyles.length; j += 2)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
293 if (endStyles[j + 1] == nextChange) spanEndStyle += " " + endStyles[j]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
294
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
295 if (!collapsed || collapsed.from == pos) for (let j = 0; j < foundBookmarks.length; ++j)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
296 buildCollapsedSpan(builder, 0, foundBookmarks[j])
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
297 if (collapsed && (collapsed.from || 0) == pos) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
298 buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
299 collapsed.marker, collapsed.from == null)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
300 if (collapsed.to == null) return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
301 if (collapsed.to == pos) collapsed = false
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
302 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
303 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
304 if (pos >= len) break
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
305
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
306 let upto = Math.min(len, nextChange)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
307 while (true) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
308 if (text) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
309 let end = pos + text.length
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
310 if (!collapsed) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
311 let tokenText = end > upto ? text.slice(0, upto - pos) : text
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
312 builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
313 spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", css, attributes)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
314 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
315 if (end >= upto) {text = text.slice(upto - pos); pos = upto; break}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
316 pos = end
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
317 spanStartStyle = ""
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
318 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
319 text = allText.slice(at, at = styles[i++])
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
320 style = interpretTokenStyle(styles[i++], builder.cm.options)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
321 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
322 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
323 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
324
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
325
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
326 // These objects are used to represent the visible (currently drawn)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
327 // part of the document. A LineView may correspond to multiple
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
328 // logical lines, if those are connected by collapsed ranges.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
329 export function LineView(doc, line, lineN) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
330 // The starting line
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
331 this.line = line
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
332 // Continuing lines, if any
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
333 this.rest = visualLineContinued(line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
334 // Number of logical lines in this visual line
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
335 this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
336 this.node = this.text = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
337 this.hidden = lineIsHidden(doc, line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
338 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
339
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
340 // Create a range of LineView objects for the given lines.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
341 export function buildViewArray(cm, from, to) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
342 let array = [], nextPos
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
343 for (let pos = from; pos < to; pos = nextPos) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
344 let view = new LineView(cm.doc, getLine(cm.doc, pos), pos)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
345 nextPos = pos + view.size
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
346 array.push(view)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
347 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
348 return array
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
349 }