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