selection.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import { Pos } from "../line/pos.js"
  2. import { visualLine } from "../line/spans.js"
  3. import { getLine } from "../line/utils_line.js"
  4. import { charCoords, cursorCoords, displayWidth, paddingH, wrappedLineExtentChar } from "../measurement/position_measurement.js"
  5. import { getOrder, iterateBidiSections } from "../util/bidi.js"
  6. import { elt } from "../util/dom.js"
  7. export function updateSelection(cm) {
  8. cm.display.input.showSelection(cm.display.input.prepareSelection())
  9. }
  10. export function prepareSelection(cm, primary = true) {
  11. let doc = cm.doc, result = {}
  12. let curFragment = result.cursors = document.createDocumentFragment()
  13. let selFragment = result.selection = document.createDocumentFragment()
  14. for (let i = 0; i < doc.sel.ranges.length; i++) {
  15. if (!primary && i == doc.sel.primIndex) continue
  16. let range = doc.sel.ranges[i]
  17. if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) continue
  18. let collapsed = range.empty()
  19. if (collapsed || cm.options.showCursorWhenSelecting)
  20. drawSelectionCursor(cm, range.head, curFragment)
  21. if (!collapsed)
  22. drawSelectionRange(cm, range, selFragment)
  23. }
  24. return result
  25. }
  26. // Draws a cursor for the given range
  27. export function drawSelectionCursor(cm, head, output) {
  28. let pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine)
  29. let cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor"))
  30. cursor.style.left = pos.left + "px"
  31. cursor.style.top = pos.top + "px"
  32. cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"
  33. if (pos.other) {
  34. // Secondary cursor, shown when on a 'jump' in bi-directional text
  35. let otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"))
  36. otherCursor.style.display = ""
  37. otherCursor.style.left = pos.other.left + "px"
  38. otherCursor.style.top = pos.other.top + "px"
  39. otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"
  40. }
  41. }
  42. function cmpCoords(a, b) { return a.top - b.top || a.left - b.left }
  43. // Draws the given range as a highlighted selection
  44. function drawSelectionRange(cm, range, output) {
  45. let display = cm.display, doc = cm.doc
  46. let fragment = document.createDocumentFragment()
  47. let padding = paddingH(cm.display), leftSide = padding.left
  48. let rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right
  49. let docLTR = doc.direction == "ltr"
  50. function add(left, top, width, bottom) {
  51. if (top < 0) top = 0
  52. top = Math.round(top)
  53. bottom = Math.round(bottom)
  54. fragment.appendChild(elt("div", null, "CodeMirror-selected", `position: absolute; left: ${left}px;
  55. top: ${top}px; width: ${width == null ? rightSide - left : width}px;
  56. height: ${bottom - top}px`))
  57. }
  58. function drawForLine(line, fromArg, toArg) {
  59. let lineObj = getLine(doc, line)
  60. let lineLen = lineObj.text.length
  61. let start, end
  62. function coords(ch, bias) {
  63. return charCoords(cm, Pos(line, ch), "div", lineObj, bias)
  64. }
  65. function wrapX(pos, dir, side) {
  66. let extent = wrappedLineExtentChar(cm, lineObj, null, pos)
  67. let prop = (dir == "ltr") == (side == "after") ? "left" : "right"
  68. let ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1)
  69. return coords(ch, prop)[prop]
  70. }
  71. let order = getOrder(lineObj, doc.direction)
  72. iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, (from, to, dir, i) => {
  73. let ltr = dir == "ltr"
  74. let fromPos = coords(from, ltr ? "left" : "right")
  75. let toPos = coords(to - 1, ltr ? "right" : "left")
  76. let openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen
  77. let first = i == 0, last = !order || i == order.length - 1
  78. if (toPos.top - fromPos.top <= 3) { // Single line
  79. let openLeft = (docLTR ? openStart : openEnd) && first
  80. let openRight = (docLTR ? openEnd : openStart) && last
  81. let left = openLeft ? leftSide : (ltr ? fromPos : toPos).left
  82. let right = openRight ? rightSide : (ltr ? toPos : fromPos).right
  83. add(left, fromPos.top, right - left, fromPos.bottom)
  84. } else { // Multiple lines
  85. let topLeft, topRight, botLeft, botRight
  86. if (ltr) {
  87. topLeft = docLTR && openStart && first ? leftSide : fromPos.left
  88. topRight = docLTR ? rightSide : wrapX(from, dir, "before")
  89. botLeft = docLTR ? leftSide : wrapX(to, dir, "after")
  90. botRight = docLTR && openEnd && last ? rightSide : toPos.right
  91. } else {
  92. topLeft = !docLTR ? leftSide : wrapX(from, dir, "before")
  93. topRight = !docLTR && openStart && first ? rightSide : fromPos.right
  94. botLeft = !docLTR && openEnd && last ? leftSide : toPos.left
  95. botRight = !docLTR ? rightSide : wrapX(to, dir, "after")
  96. }
  97. add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom)
  98. if (fromPos.bottom < toPos.top) add(leftSide, fromPos.bottom, null, toPos.top)
  99. add(botLeft, toPos.top, botRight - botLeft, toPos.bottom)
  100. }
  101. if (!start || cmpCoords(fromPos, start) < 0) start = fromPos
  102. if (cmpCoords(toPos, start) < 0) start = toPos
  103. if (!end || cmpCoords(fromPos, end) < 0) end = fromPos
  104. if (cmpCoords(toPos, end) < 0) end = toPos
  105. })
  106. return {start: start, end: end}
  107. }
  108. let sFrom = range.from(), sTo = range.to()
  109. if (sFrom.line == sTo.line) {
  110. drawForLine(sFrom.line, sFrom.ch, sTo.ch)
  111. } else {
  112. let fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line)
  113. let singleVLine = visualLine(fromLine) == visualLine(toLine)
  114. let leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end
  115. let rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start
  116. if (singleVLine) {
  117. if (leftEnd.top < rightStart.top - 2) {
  118. add(leftEnd.right, leftEnd.top, null, leftEnd.bottom)
  119. add(leftSide, rightStart.top, rightStart.left, rightStart.bottom)
  120. } else {
  121. add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom)
  122. }
  123. }
  124. if (leftEnd.bottom < rightStart.top)
  125. add(leftSide, leftEnd.bottom, null, rightStart.top)
  126. }
  127. output.appendChild(fragment)
  128. }
  129. // Cursor-blinking
  130. export function restartBlink(cm) {
  131. if (!cm.state.focused) return
  132. let display = cm.display
  133. clearInterval(display.blinker)
  134. let on = true
  135. display.cursorDiv.style.visibility = ""
  136. if (cm.options.cursorBlinkRate > 0)
  137. display.blinker = setInterval(() => display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden",
  138. cm.options.cursorBlinkRate)
  139. else if (cm.options.cursorBlinkRate < 0)
  140. display.cursorDiv.style.visibility = "hidden"
  141. }