import { escapeChar } from "./html.js"; function bufferColToDomOffset(lineText, col) { let offset = 0; for (let i = 0; i < col && i < lineText.length; i++) offset += escapeChar(lineText[i]).length; return offset; } function findTextNode(parent, targetOffset) { let remaining = targetOffset; const walker = document.createTreeWalker(parent, NodeFilter.SHOW_TEXT); let node; while ((node = walker.nextNode())) { if (remaining <= node.textContent.length) return { node, offset: remaining }; remaining -= node.textContent.length; } return { node: parent, offset: parent.childNodes.length }; } function posToElement(line, col, buffer) { const spans = document.querySelectorAll("#text > span"); if (line >= spans.length) return null; const span = spans[line]; const range = document.createRange(); const lineText = buffer[line] || ""; const offset = bufferColToDomOffset(lineText, col); const tn = findTextNode(span, offset); range.setStart(tn.node, tn.offset); range.collapse(true); const rect = range.getBoundingClientRect(); return { x: rect.left, y: rect.top, height: rect.height }; } export function elementToPos(x, y, buffer) { const spans = document.querySelectorAll("#text > span"); let bestLine = 0; let bestDistY = Infinity; for (let i = 0; i < spans.length; i++) { const rect = spans[i].getBoundingClientRect(); const midY = rect.top + rect.height / 2; const distY = Math.abs(y - midY); if (distY < bestDistY) { bestDistY = distY; bestLine = i; } } let bestCol = 0; let bestDistX = Infinity; const lineText = buffer[bestLine] || ""; for (let c = 0; c <= lineText.length; c++) { const pos = posToElement(bestLine, c, buffer); if (!pos) continue; const dist = Math.abs(x - pos.x); if (dist < bestDistX) { bestDistX = dist; bestCol = c; } } return { line: bestLine, col: bestCol }; }