File modules/editor/codemirror/src/measurement/position_measurement.min.js
Last commit: Tue May 22 22:39:52 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 { buildLineContent, LineView } from "../line/line_data.js" 2 import { clipPos, Pos } from "../line/pos.js" 3 import { collapsedSpanAtEnd, heightAtLine, lineIsHidden, visualLine } from "../line/spans.js" 4 import { getLine, lineAtHeight, lineNo, updateLineHeight } from "../line/utils_line.js" 5 import { bidiOther, getBidiPartAt, getOrder } from "../util/bidi.js" 6 import { chrome, android, ie, ie_version } from "../util/browser.js" 7 import { elt, removeChildren, range, removeChildrenAndAdd } from "../util/dom.js" 8 import { e_target } from "../util/event.js" 9 import { hasBadZoomedRects } from "../util/feature_detection.js" 10 import { countColumn, findFirst, isExtendingChar, scrollerGap, skipExtendingChars } from "../util/misc.js" 11 import { updateLineForChanges } from "../display/update_line.js" 12 13 import { widgetHeight } from "./widgets.js" 14 15 // POSITION MEASUREMENT 16 17 export function paddingTop(display) {return display.lineSpace.offsetTop} 18 export function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight} 19 export function paddingH(display) { 20 if (display.cachedPaddingH) return display.cachedPaddingH 21 let e = removeChildrenAndAdd(display.measure, elt("pre", "x")) 22 let style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle 23 let data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)} 24 if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data 25 return data 26 } 27 28 export function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth } 29 export function displayWidth(cm) { 30 return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth 31 } 32 export function displayHeight(cm) { 33 return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight 34 } 35 36 // Ensure the lineView.wrapping.heights array is populated. This is 37 // an array of bottom offsets for the lines that make up a drawn 38 // line. When lineWrapping is on, there might be more than one 39 // height. 40 function ensureLineHeights(cm, lineView, rect) { 41 let wrapping = cm.options.lineWrapping 42 let curWidth = wrapping && displayWidth(cm) 43 if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { 44 let heights = lineView.measure.heights = [] 45 if (wrapping) { 46 lineView.measure.width = curWidth 47 let rects = lineView.text.firstChild.getClientRects() 48 for (let i = 0; i < rects.length - 1; i++) { 49 let cur = rects[i], next = rects[i + 1] 50 if (Math.abs(cur.bottom - next.bottom) > 2) 51 heights.push((cur.bottom + next.top) / 2 - rect.top) 52 } 53 } 54 heights.push(rect.bottom - rect.top) 55 } 56 } 57 58 // Find a line map (mapping character offsets to text nodes) and a 59 // measurement cache for the given line number. (A line view might 60 // contain multiple lines when collapsed ranges are present.) 61 export function mapFromLineView(lineView, line, lineN) { 62 if (lineView.line == line) 63 return {map: lineView.measure.map, cache: lineView.measure.cache} 64 for (let i = 0; i < lineView.rest.length; i++) 65 if (lineView.rest[i] == line) 66 return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} 67 for (let i = 0; i < lineView.rest.length; i++) 68 if (lineNo(lineView.rest[i]) > lineN) 69 return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true} 70 } 71 72 // Render a line into the hidden node display.externalMeasured. Used 73 // when measurement is needed for a line that's not in the viewport. 74 function updateExternalMeasurement(cm, line) { 75 line = visualLine(line) 76 let lineN = lineNo(line) 77 let view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN) 78 view.lineN = lineN 79 let built = view.built = buildLineContent(cm, view) 80 view.text = built.pre 81 removeChildrenAndAdd(cm.display.lineMeasure, built.pre) 82 return view 83 } 84 85 // Get a {top, bottom, left, right} box (in line-local coordinates) 86 // for a given character. 87 export function measureChar(cm, line, ch, bias) { 88 return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias) 89 } 90 91 // Find a line view that corresponds to the given line number. 92 export function findViewForLine(cm, lineN) { 93 if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) 94 return cm.display.view[findViewIndex(cm, lineN)] 95 let ext = cm.display.externalMeasured 96 if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) 97 return ext 98 } 99 100 // Measurement can be split in two steps, the set-up work that 101 // applies to the whole line, and the measurement of the actual 102 // character. Functions like coordsChar, that need to do a lot of 103 // measurements in a row, can thus ensure that the set-up work is 104 // only done once. 105 export function prepareMeasureForLine(cm, line) { 106 let lineN = lineNo(line) 107 let view = findViewForLine(cm, lineN) 108 if (view && !view.text) { 109 view = null 110 } else if (view && view.changes) { 111 updateLineForChanges(cm, view, lineN, getDimensions(cm)) 112 cm.curOp.forceUpdate = true 113 } 114 if (!view) 115 view = updateExternalMeasurement(cm, line) 116 117 let info = mapFromLineView(view, line, lineN) 118 return { 119 line: line, view: view, rect: null, 120 map: info.map, cache: info.cache, before: info.before, 121 hasHeights: false 122 } 123 } 124 125 // Given a prepared measurement object, measures the position of an 126 // actual character (or fetches it from the cache). 127 export function measureCharPrepared(cm, prepared, ch, bias, varHeight) { 128 if (prepared.before) ch = -1 129 let key = ch + (bias || ""), found 130 if (prepared.cache.hasOwnProperty(key)) { 131 found = prepared.cache[key] 132 } else { 133 if (!prepared.rect) 134 prepared.rect = prepared.view.text.getBoundingClientRect() 135 if (!prepared.hasHeights) { 136 ensureLineHeights(cm, prepared.view, prepared.rect) 137 prepared.hasHeights = true 138 } 139 found = measureCharInner(cm, prepared, ch, bias) 140 if (!found.bogus) prepared.cache[key] = found 141 } 142 return {left: found.left, right: found.right, 143 top: varHeight ? found.rtop : found.top, 144 bottom: varHeight ? found.rbottom : found.bottom} 145 } 146 147 let nullRect = {left: 0, right: 0, top: 0, bottom: 0} 148 149 export function nodeAndOffsetInLineMap(map, ch, bias) { 150 let node, start, end, collapse, mStart, mEnd 151 // First, search the line map for the text node corresponding to, 152 // or closest to, the target character. 153 for (let i = 0; i < map.length; i += 3) { 154 mStart = map[i] 155 mEnd = map[i + 1] 156 if (ch < mStart) { 157 start = 0; end = 1 158 collapse = "left" 159 } else if (ch < mEnd) { 160 start = ch - mStart 161 end = start + 1 162 } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { 163 end = mEnd - mStart 164 start = end - 1 165 if (ch >= mEnd) collapse = "right" 166 } 167 if (start != null) { 168 node = map[i + 2] 169 if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) 170 collapse = bias 171 if (bias == "left" && start == 0) 172 while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { 173 node = map[(i -= 3) + 2] 174 collapse = "left" 175 } 176 if (bias == "right" && start == mEnd - mStart) 177 while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { 178 node = map[(i += 3) + 2] 179 collapse = "right" 180 } 181 break 182 } 183 } 184 return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd} 185 } 186 187 function getUsefulRect(rects, bias) { 188 let rect = nullRect 189 if (bias == "left") for (let i = 0; i < rects.length; i++) { 190 if ((rect = rects[i]).left != rect.right) break 191 } else for (let i = rects.length - 1; i >= 0; i--) { 192 if ((rect = rects[i]).left != rect.right) break 193 } 194 return rect 195 } 196 197 function measureCharInner(cm, prepared, ch, bias) { 198 let place = nodeAndOffsetInLineMap(prepared.map, ch, bias) 199 let node = place.node, start = place.start, end = place.end, collapse = place.collapse 200 201 let rect 202 if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. 203 for (let i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned 204 while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) --start 205 while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) ++end 206 if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) 207 rect = node.parentNode.getBoundingClientRect() 208 else 209 rect = getUsefulRect(range(node, start, end).getClientRects(), bias) 210 if (rect.left || rect.right || start == 0) break 211 end = start 212 start = start - 1 213 collapse = "right" 214 } 215 if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect) 216 } else { // If it is a widget, simply get the box for the whole widget. 217 if (start > 0) collapse = bias = "right" 218 let rects 219 if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) 220 rect = rects[bias == "right" ? rects.length - 1 : 0] 221 else 222 rect = node.getBoundingClientRect() 223 } 224 if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { 225 let rSpan = node.parentNode.getClientRects()[0] 226 if (rSpan) 227 rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom} 228 else 229 rect = nullRect 230 } 231 232 let rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top 233 let mid = (rtop + rbot) / 2 234 let heights = prepared.view.measure.heights 235 let i = 0 236 for (; i < heights.length - 1; i++) 237 if (mid < heights[i]) break 238 let top = i ? heights[i - 1] : 0, bot = heights[i] 239 let result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, 240 right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, 241 top: top, bottom: bot} 242 if (!rect.left && !rect.right) result.bogus = true 243 if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot } 244 245 return result 246 } 247 248 // Work around problem with bounding client rects on ranges being 249 // returned incorrectly when zoomed on IE10 and below. 250 function maybeUpdateRectForZooming(measure, rect) { 251 if (!window.screen || screen.logicalXDPI == null || 252 screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) 253 return rect 254 let scaleX = screen.logicalXDPI / screen.deviceXDPI 255 let scaleY = screen.logicalYDPI / screen.deviceYDPI 256 return {left: rect.left * scaleX, right: rect.right * scaleX, 257 top: rect.top * scaleY, bottom: rect.bottom * scaleY} 258 } 259 260 export function clearLineMeasurementCacheFor(lineView) { 261 if (lineView.measure) { 262 lineView.measure.cache = {} 263 lineView.measure.heights = null 264 if (lineView.rest) for (let i = 0; i < lineView.rest.length; i++) 265 lineView.measure.caches[i] = {} 266 } 267 } 268 269 export function clearLineMeasurementCache(cm) { 270 cm.display.externalMeasure = null 271 removeChildren(cm.display.lineMeasure) 272 for (let i = 0; i < cm.display.view.length; i++) 273 clearLineMeasurementCacheFor(cm.display.view[i]) 274 } 275 276 export function clearCaches(cm) { 277 clearLineMeasurementCache(cm) 278 cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null 279 if (!cm.options.lineWrapping) cm.display.maxLineChanged = true 280 cm.display.lineNumChars = null 281 } 282 283 function pageScrollX() { 284 // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206 285 // which causes page_Offset and bounding client rects to use 286 // different reference viewports and invalidate our calculations. 287 if (chrome && android) return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)) 288 return window.pageXOffset || (document.documentElement || document.body).scrollLeft 289 } 290 function pageScrollY() { 291 if (chrome && android) return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)) 292 return window.pageYOffset || (document.documentElement || document.body).scrollTop 293 } 294 295 function widgetTopHeight(lineObj) { 296 let height = 0 297 if (lineObj.widgets) for (let i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) 298 height += widgetHeight(lineObj.widgets[i]) 299 return height 300 } 301 302 // Converts a {top, bottom, left, right} box from line-local 303 // coordinates into another coordinate system. Context may be one of 304 // "line", "div" (display.lineDiv), "local"./null (editor), "window", 305 // or "page". 306 export function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) { 307 if (!includeWidgets) { 308 let height = widgetTopHeight(lineObj) 309 rect.top += height; rect.bottom += height 310 } 311 if (context == "line") return rect 312 if (!context) context = "local" 313 let yOff = heightAtLine(lineObj) 314 if (context == "local") yOff += paddingTop(cm.display) 315 else yOff -= cm.display.viewOffset 316 if (context == "page" || context == "window") { 317 let lOff = cm.display.lineSpace.getBoundingClientRect() 318 yOff += lOff.top + (context == "window" ? 0 : pageScrollY()) 319 let xOff = lOff.left + (context == "window" ? 0 : pageScrollX()) 320 rect.left += xOff; rect.right += xOff 321 } 322 rect.top += yOff; rect.bottom += yOff 323 return rect 324 } 325 326 // Coverts a box from "div" coords to another coordinate system. 327 // Context may be "window", "page", "div", or "local"./null. 328 export function fromCoordSystem(cm, coords, context) { 329 if (context == "div") return coords 330 let left = coords.left, top = coords.top 331 // First move into "page" coordinate system 332 if (context == "page") { 333 left -= pageScrollX() 334 top -= pageScrollY() 335 } else if (context == "local" || !context) { 336 let localBox = cm.display.sizer.getBoundingClientRect() 337 left += localBox.left 338 top += localBox.top 339 } 340 341 let lineSpaceBox = cm.display.lineSpace.getBoundingClientRect() 342 return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top} 343 } 344 345 export function charCoords(cm, pos, context, lineObj, bias) { 346 if (!lineObj) lineObj = getLine(cm.doc, pos.line) 347 return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context) 348 } 349 350 // Returns a box for a given cursor position, which may have an 351 // 'other' property containing the position of the secondary cursor 352 // on a bidi boundary. 353 // A cursor Pos(line, char, "before") is on the same visual line as `char - 1` 354 // and after `char - 1` in writing order of `char - 1` 355 // A cursor Pos(line, char, "after") is on the same visual line as `char` 356 // and before `char` in writing order of `char` 357 // Examples (upper-case letters are RTL, lower-case are LTR): 358 // Pos(0, 1, ...) 359 // before after 360 // ab a|b a|b 361 // aB a|B aB| 362 // Ab |Ab A|b 363 // AB B|A B|A 364 // Every position after the last character on a line is considered to stick 365 // to the last character on the line. 366 export function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { 367 lineObj = lineObj || getLine(cm.doc, pos.line) 368 if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj) 369 function get(ch, right) { 370 let m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight) 371 if (right) m.left = m.right; else m.right = m.left 372 return intoCoordSystem(cm, lineObj, m, context) 373 } 374 let order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky 375 if (ch >= lineObj.text.length) { 376 ch = lineObj.text.length 377 sticky = "before" 378 } else if (ch <= 0) { 379 ch = 0 380 sticky = "after" 381 } 382 if (!order) return get(sticky == "before" ? ch - 1 : ch, sticky == "before") 383 384 function getBidi(ch, partPos, invert) { 385 let part = order[partPos], right = part.level == 1 386 return get(invert ? ch - 1 : ch, right != invert) 387 } 388 let partPos = getBidiPartAt(order, ch, sticky) 389 let other = bidiOther 390 let val = getBidi(ch, partPos, sticky == "before") 391 if (other != null) val.other = getBidi(ch, other, sticky != "before") 392 return val 393 } 394 395 // Used to cheaply estimate the coordinates for a position. Used for 396 // intermediate scroll updates. 397 export function estimateCoords(cm, pos) { 398 let left = 0 399 pos = clipPos(cm.doc, pos) 400 if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch 401 let lineObj = getLine(cm.doc, pos.line) 402 let top = heightAtLine(lineObj) + paddingTop(cm.display) 403 return {left: left, right: left, top: top, bottom: top + lineObj.height} 404 } 405 406 // Positions returned by coordsChar contain some extra information. 407 // xRel is the relative x position of the input coordinates compared 408 // to the found position (so xRel > 0 means the coordinates are to 409 // the right of the character position, for example). When outside 410 // is true, that means the coordinates lie outside the line's 411 // vertical range. 412 function PosWithInfo(line, ch, sticky, outside, xRel) { 413 let pos = Pos(line, ch, sticky) 414 pos.xRel = xRel 415 if (outside) pos.outside = true 416 return pos 417 } 418 419 // Compute the character position closest to the given coordinates. 420 // Input must be lineSpace-local ("div" coordinate system). 421 export function coordsChar(cm, x, y) { 422 let doc = cm.doc 423 y += cm.display.viewOffset 424 if (y < 0) return PosWithInfo(doc.first, 0, null, true, -1) 425 let lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1 426 if (lineN > last) 427 return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, true, 1) 428 if (x < 0) x = 0 429 430 let lineObj = getLine(doc, lineN) 431 for (;;) { 432 let found = coordsCharInner(cm, lineObj, lineN, x, y) 433 let merged = collapsedSpanAtEnd(lineObj) 434 let mergedPos = merged && merged.find(0, true) 435 if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) 436 lineN = lineNo(lineObj = mergedPos.to.line) 437 else 438 return found 439 } 440 } 441 442 function wrappedLineExtent(cm, lineObj, preparedMeasure, y) { 443 y -= widgetTopHeight(lineObj) 444 let end = lineObj.text.length 445 let begin = findFirst(ch => measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y, end, 0) 446 end = findFirst(ch => measureCharPrepared(cm, preparedMeasure, ch).top > y, begin, end) 447 return {begin, end} 448 } 449 450 export function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) { 451 if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj) 452 let targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top 453 return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop) 454 } 455 456 // Returns true if the given side of a box is after the given 457 // coordinates, in top-to-bottom, left-to-right order. 458 function boxIsAfter(box, x, y, left) { 459 return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x 460 } 461 462 function coordsCharInner(cm, lineObj, lineNo, x, y) { 463 // Move y into line-local coordinate space 464 y -= heightAtLine(lineObj) 465 let preparedMeasure = prepareMeasureForLine(cm, lineObj) 466 // When directly calling `measureCharPrepared`, we have to adjust 467 // for the widgets at this line. 468 let widgetHeight = widgetTopHeight(lineObj) 469 let begin = 0, end = lineObj.text.length, ltr = true 470 471 let order = getOrder(lineObj, cm.doc.direction) 472 // If the line isn't plain left-to-right text, first figure out 473 // which bidi section the coordinates fall into. 474 if (order) { 475 let part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart) 476 (cm, lineObj, lineNo, preparedMeasure, order, x, y) 477 ltr = part.level != 1 478 // The awkward -1 offsets are needed because findFirst (called 479 // on these below) will treat its first bound as inclusive, 480 // second as exclusive, but we want to actually address the 481 // characters in the part's range 482 begin = ltr ? part.from : part.to - 1 483 end = ltr ? part.to : part.from - 1 484 } 485 486 // A binary search to find the first character whose bounding box 487 // starts after the coordinates. If we run across any whose box wrap 488 // the coordinates, store that. 489 let chAround = null, boxAround = null 490 let ch = findFirst(ch => { 491 let box = measureCharPrepared(cm, preparedMeasure, ch) 492 box.top += widgetHeight; box.bottom += widgetHeight 493 if (!boxIsAfter(box, x, y, false)) return false 494 if (box.top <= y && box.left <= x) { 495 chAround = ch 496 boxAround = box 497 } 498 return true 499 }, begin, end) 500 501 let baseX, sticky, outside = false 502 // If a box around the coordinates was found, use that 503 if (boxAround) { 504 // Distinguish coordinates nearer to the left or right side of the box 505 let atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr 506 ch = chAround + (atStart ? 0 : 1) 507 sticky = atStart ? "after" : "before" 508 baseX = atLeft ? boxAround.left : boxAround.right 509 } else { 510 // (Adjust for extended bound, if necessary.) 511 if (!ltr && (ch == end || ch == begin)) ch++ 512 // To determine which side to associate with, get the box to the 513 // left of the character and compare it's vertical position to the 514 // coordinates 515 sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" : 516 (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight <= y) == ltr ? 517 "after" : "before" 518 // Now get accurate coordinates for this place, in order to get a 519 // base X position 520 let coords = cursorCoords(cm, Pos(lineNo, ch, sticky), "line", lineObj, preparedMeasure) 521 baseX = coords.left 522 outside = y < coords.top || y >= coords.bottom 523 } 524 525 ch = skipExtendingChars(lineObj.text, ch, 1) 526 return PosWithInfo(lineNo, ch, sticky, outside, x - baseX) 527 } 528 529 function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) { 530 // Bidi parts are sorted left-to-right, and in a non-line-wrapping 531 // situation, we can take this ordering to correspond to the visual 532 // ordering. This finds the first part whose end is after the given 533 // coordinates. 534 let index = findFirst(i => { 535 let part = order[i], ltr = part.level != 1 536 return boxIsAfter(cursorCoords(cm, Pos(lineNo, ltr ? part.to : part.from, ltr ? "before" : "after"), 537 "line", lineObj, preparedMeasure), x, y, true) 538 }, 0, order.length - 1) 539 let part = order[index] 540 // If this isn't the first part, the part's start is also after 541 // the coordinates, and the coordinates aren't on the same line as 542 // that start, move one part back. 543 if (index > 0) { 544 let ltr = part.level != 1 545 let start = cursorCoords(cm, Pos(lineNo, ltr ? part.from : part.to, ltr ? "after" : "before"), 546 "line", lineObj, preparedMeasure) 547 if (boxIsAfter(start, x, y, true) && start.top > y) 548 part = order[index - 1] 549 } 550 return part 551 } 552 553 function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) { 554 // In a wrapped line, rtl text on wrapping boundaries can do things 555 // that don't correspond to the ordering in our `order` array at 556 // all, so a binary search doesn't work, and we want to return a 557 // part that only spans one line so that the binary search in 558 // coordsCharInner is safe. As such, we first find the extent of the 559 // wrapped line, and then do a flat search in which we discard any 560 // spans that aren't on the line. 561 let {begin, end} = wrappedLineExtent(cm, lineObj, preparedMeasure, y) 562 if (/\s/.test(lineObj.text.charAt(end - 1))) end-- 563 let part = null, closestDist = null 564 for (let i = 0; i < order.length; i++) { 565 let p = order[i] 566 if (p.from >= end || p.to <= begin) continue 567 let ltr = p.level != 1 568 let endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right 569 // Weigh against spans ending before this, so that they are only 570 // picked if nothing ends after 571 let dist = endX < x ? x - endX + 1e9 : endX - x 572 if (!part || closestDist > dist) { 573 part = p 574 closestDist = dist 575 } 576 } 577 if (!part) part = order[order.length - 1] 578 // Clip the part to the wrapped line. 579 if (part.from < begin) part = {from: begin, to: part.to, level: part.level} 580 if (part.to > end) part = {from: part.from, to: end, level: part.level} 581 return part 582 } 583 584 let measureText 585 // Compute the default text height. 586 export function textHeight(display) { 587 if (display.cachedTextHeight != null) return display.cachedTextHeight 588 if (measureText == null) { 589 measureText = elt("pre") 590 // Measure a bunch of lines, for browsers that compute 591 // fractional heights. 592 for (let i = 0; i < 49; ++i) { 593 measureText.appendChild(document.createTextNode("x")) 594 measureText.appendChild(elt("br")) 595 } 596 measureText.appendChild(document.createTextNode("x")) 597 } 598 removeChildrenAndAdd(display.measure, measureText) 599 let height = measureText.offsetHeight / 50 600 if (height > 3) display.cachedTextHeight = height 601 removeChildren(display.measure) 602 return height || 1 603 } 604 605 // Compute the default character width. 606 export function charWidth(display) { 607 if (display.cachedCharWidth != null) return display.cachedCharWidth 608 let anchor = elt("span", "xxxxxxxxxx") 609 let pre = elt("pre", [anchor]) 610 removeChildrenAndAdd(display.measure, pre) 611 let rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10 612 if (width > 2) display.cachedCharWidth = width 613 return width || 10 614 } 615 616 // Do a bulk-read of the DOM positions and sizes needed to draw the 617 // view, so that we don't interleave reading and writing to the DOM. 618 export function getDimensions(cm) { 619 let d = cm.display, left = {}, width = {} 620 let gutterLeft = d.gutters.clientLeft 621 for (let n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { 622 left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft 623 width[cm.options.gutters[i]] = n.clientWidth 624 } 625 return {fixedPos: compensateForHScroll(d), 626 gutterTotalWidth: d.gutters.offsetWidth, 627 gutterLeft: left, 628 gutterWidth: width, 629 wrapperWidth: d.wrapper.clientWidth} 630 } 631 632 // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, 633 // but using getBoundingClientRect to get a sub-pixel-accurate 634 // result. 635 export function compensateForHScroll(display) { 636 return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left 637 } 638 639 // Returns a function that estimates the height of a line, to use as 640 // first approximation until the line becomes visible (and is thus 641 // properly measurable). 642 export function estimateHeight(cm) { 643 let th = textHeight(cm.display), wrapping = cm.options.lineWrapping 644 let perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3) 645 return line => { 646 if (lineIsHidden(cm.doc, line)) return 0 647 648 let widgetsHeight = 0 649 if (line.widgets) for (let i = 0; i < line.widgets.length; i++) { 650 if (line.widgets[i].height) widgetsHeight += line.widgets[i].height 651 } 652 653 if (wrapping) 654 return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th 655 else 656 return widgetsHeight + th 657 } 658 } 659 660 export function estimateLineHeights(cm) { 661 let doc = cm.doc, est = estimateHeight(cm) 662 doc.iter(line => { 663 let estHeight = est(line) 664 if (estHeight != line.height) updateLineHeight(line, estHeight) 665 }) 666 } 667 668 // Given a mouse event, find the corresponding position. If liberal 669 // is false, it checks whether a gutter or scrollbar was clicked, 670 // and returns null if it was. forRect is used by rectangular 671 // selections, and tries to estimate a character position even for 672 // coordinates beyond the right of the text. 673 export function posFromMouse(cm, e, liberal, forRect) { 674 let display = cm.display 675 if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") return null 676 677 let x, y, space = display.lineSpace.getBoundingClientRect() 678 // Fails unpredictably on IE[67] when mouse is dragged around quickly. 679 try { x = e.clientX - space.left; y = e.clientY - space.top } 680 catch (e) { return null } 681 let coords = coordsChar(cm, x, y), line 682 if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { 683 let colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length 684 coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)) 685 } 686 return coords 687 } 688 689 // Find the view element corresponding to a given line. Return null 690 // when the line isn't visible. 691 export function findViewIndex(cm, n) { 692 if (n >= cm.display.viewTo) return null 693 n -= cm.display.viewFrom 694 if (n < 0) return null 695 let view = cm.display.view 696 for (let i = 0; i < view.length; i++) { 697 n -= view[i].size 698 if (n < 0) return i 699 } 700 }
Downloadmodules/editor/codemirror/src/measurement/position_measurement.min.js
History Tue, 22 May 2018 22:39:52 +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'.