89 lines
3.3 KiB
TypeScript
89 lines
3.3 KiB
TypeScript
import { layer, RectangleMarker, type EditorView } from "@codemirror/view";
|
|
|
|
function getLineElement(view: EditorView, pos: number): HTMLElement | null {
|
|
try {
|
|
const domAt = view.domAtPos(pos);
|
|
let node: Node | null = domAt.node;
|
|
while (node) {
|
|
if (node instanceof HTMLElement && node.classList.contains("cm-line")) return node;
|
|
node = node.parentElement;
|
|
}
|
|
} catch {
|
|
// domAtPos may throw for positions outside the viewport
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function vscodeSelectionLayer() {
|
|
return layer({
|
|
above: false,
|
|
class: "cm-vscodeSelectionLayer",
|
|
markers(view) {
|
|
const markers: InstanceType<typeof RectangleMarker>[] = [];
|
|
const contentRect = view.contentDOM.getBoundingClientRect();
|
|
const baseRect = view.scrollDOM.getBoundingClientRect();
|
|
const base = {
|
|
left: baseRect.left - view.scrollDOM.scrollLeft * view.scaleX,
|
|
top: baseRect.top - view.scrollDOM.scrollTop * view.scaleY,
|
|
};
|
|
const lineElt = view.contentDOM.querySelector(".cm-line");
|
|
const lineStyle = lineElt && window.getComputedStyle(lineElt);
|
|
const contentLeft =
|
|
contentRect.left +
|
|
(lineStyle ? parseInt(lineStyle.paddingLeft) + Math.min(0, parseInt(lineStyle.textIndent)) : 0);
|
|
// single character width for empty-line fallback
|
|
const sampleCoords = view.coordsAtPos(view.viewport.from);
|
|
const charWidth = sampleCoords ? sampleCoords.right - sampleCoords.left : 8;
|
|
for (const r of view.state.selection.ranges) {
|
|
if (r.empty) continue;
|
|
const fromLine = view.lineBlockAt(r.from);
|
|
const toLine = view.lineBlockAt(r.to);
|
|
for (let pos = fromLine.from; pos <= toLine.from; ) {
|
|
const line = view.lineBlockAt(pos);
|
|
const lineEl = getLineElement(view, line.from);
|
|
if (!lineEl) break;
|
|
const lineRect = lineEl.getBoundingClientRect();
|
|
let left = contentLeft - base.left;
|
|
let right: number;
|
|
const isFirst = line.from === fromLine.from;
|
|
const isLast = line.from === toLine.from;
|
|
if (isFirst) {
|
|
const c = view.coordsAtPos(r.from);
|
|
if (c) left = c.left - base.left;
|
|
}
|
|
if (isLast) {
|
|
const c = view.coordsAtPos(r.to);
|
|
right = c ? c.left - base.left : contentLeft - base.left;
|
|
} else {
|
|
// not the last line: measure text content width from DOM
|
|
const range = document.createRange();
|
|
range.selectNodeContents(lineEl);
|
|
const textRect = range.getBoundingClientRect();
|
|
const textRight = textRect.right - base.left;
|
|
right = textRight > left ? textRight : left + charWidth;
|
|
}
|
|
const w = right! - left;
|
|
if (w > 0) {
|
|
markers.push(
|
|
new RectangleMarker(
|
|
"cm-vscodeSelection",
|
|
left,
|
|
lineRect.top - base.top,
|
|
w,
|
|
lineRect.bottom - lineRect.top,
|
|
),
|
|
);
|
|
}
|
|
if (isLast) break;
|
|
pos = line.to + 1;
|
|
if (pos > view.state.doc.length) break;
|
|
}
|
|
}
|
|
return markers;
|
|
},
|
|
update(update, _dom) {
|
|
return update.docChanged || update.selectionSet || update.viewportChanged;
|
|
},
|
|
});
|
|
}
|