UNPKG

@pierre/diffs

Version:

245 lines (244 loc) 8.57 kB
import { addEventListener, getLineNumberAttr, h } from "./utils.js"; import { setPopoverPositionStyles } from "./popover.js"; import { selectionIntersects } from "./selection.js"; //#region src/editor/marker.ts const MARKER_POPOVER_SHOW_DELAY_MS = 300; const MARKER_POPOVER_HIDE_DELAY_MS = 100; const MARKER_POPOVER_PLACEMENT_KEY = "marker-popover"; var MarkerRenderer = class { #options; #markers = []; #lineCount = 0; #markerPopoverElement; #markerPopoverEventDisposes; #markerEventDisposes; #markerPopoverShowTimeout; #markerPopoverHideTimeout; #pendingMarkerPopoverIndex; #hoveredMarkerIndex; #isMarkerPopoverHovered = false; constructor(editor) { this.#options = editor; } get markers() { return this.#markers; } isPopoverVisible() { return this.#hoveredMarkerIndex !== void 0; } updatePopoverPosition() { const hoveredMarkerIndex = this.#hoveredMarkerIndex; const popover = this.#markerPopoverElement; if (hoveredMarkerIndex === void 0 || popover === void 0) return; const marker = this.#markers[hoveredMarkerIndex]; if (marker === void 0) { this.removePopover(); return; } this.#positionMarkerPopover(popover, marker.start.line, marker.start.character); } setMarkers(markers, textDocument) { this.#lineCount = textDocument.lineCount; this.#markers = markers.map((marker) => ({ ...marker, start: textDocument.normalizePosition(marker.start), end: textDocument.normalizePosition(marker.end) })); this.removePopover(); } listenHover(contentEl) { this.#markerEventDisposes?.forEach((dispose) => dispose()); this.#markerEventDisposes = void 0; if (this.#markers.length === 0) return; this.#markerEventDisposes = [addEventListener(contentEl, "mouseover", (e) => { if (this.#options.isMouseDown()) return; const target = e.composedPath()[0]; if (target === void 0) return; const hoverMarkerIndex = this.#findHoveredMarkerIndex(target); if (hoverMarkerIndex !== void 0) this.#scheduleMarkerPopover(hoverMarkerIndex); else { this.#cancelMarkerPopoverShow(); this.#scheduleMarkerPopoverHide(); } }), addEventListener(contentEl, "mouseleave", () => { this.#cancelMarkerPopoverShow(); this.#scheduleMarkerPopoverHide(); })]; } removePopover() { this.#cancelMarkerPopoverShow(); this.#cancelMarkerPopoverHide(); this.#dismissMarkerPopover(); } cleanup() { this.#markerEventDisposes?.forEach((dispose) => dispose()); this.#markerEventDisposes = void 0; this.removePopover(); this.#markers = []; } #findHoveredMarkerIndex(target) { const lineElement = target.closest("[data-line]"); if (lineElement == null) return; const lineNumber = getLineNumberAttr(lineElement); if (lineNumber === void 0) return; let character; if (target.tagName === "SPAN") { const char = target.dataset.char; if (char === void 0) return; character = parseInt(char, 10); if (Number.isNaN(character)) return; } else if (target.tagName === "BR") character = 0; else return; const position = { line: lineNumber - 1, character }; for (let i = this.#markers.length - 1; i >= 0; i--) if (selectionIntersects({ start: position, end: position }, this.#markers[i])) return i; } #cancelMarkerPopoverShow() { if (this.#markerPopoverShowTimeout !== void 0) { clearTimeout(this.#markerPopoverShowTimeout); this.#markerPopoverShowTimeout = void 0; } this.#pendingMarkerPopoverIndex = void 0; } #cancelMarkerPopoverHide() { if (this.#markerPopoverHideTimeout !== void 0) { clearTimeout(this.#markerPopoverHideTimeout); this.#markerPopoverHideTimeout = void 0; } } #scheduleMarkerPopover(markerIndex) { if (markerIndex === this.#hoveredMarkerIndex || markerIndex === this.#pendingMarkerPopoverIndex) { this.#cancelMarkerPopoverHide(); return; } this.#cancelMarkerPopoverShow(); this.#cancelMarkerPopoverHide(); if (this.#markerPopoverElement !== void 0) { this.#renderMarkerPopover(markerIndex); return; } this.#pendingMarkerPopoverIndex = markerIndex; this.#markerPopoverShowTimeout = setTimeout(() => { this.#markerPopoverShowTimeout = void 0; this.#pendingMarkerPopoverIndex = void 0; this.#renderMarkerPopover(markerIndex); }, MARKER_POPOVER_SHOW_DELAY_MS); } #scheduleMarkerPopoverHide() { if (this.#isMarkerPopoverHovered) return; this.#cancelMarkerPopoverHide(); this.#markerPopoverHideTimeout = setTimeout(() => { this.#markerPopoverHideTimeout = void 0; if (!this.#isMarkerPopoverHovered) this.removePopover(); }, MARKER_POPOVER_HIDE_DELAY_MS); } #setMarkerPopoverPosition(popover, x, y, placeAbove, viewport = this.#options.popoverManager.getPlacementBounds()) { setPopoverPositionStyles(popover, { gutterWidth: this.#options.getGutterWidth(), placeAbove, viewport, x, y }); } #positionMarkerPopover(popover, line, character) { const { getCharX, getLineY, getLineHeight, popoverManager } = this.#options; const [left, wrapLine] = getCharX(line, character); const lineHeight = getLineHeight(); const rowTop = getLineY(line) + wrapLine * lineHeight; const popoverHeight = popover.offsetHeight; const preferred = { top: rowTop + lineHeight, bottom: rowTop + lineHeight + popoverHeight }; const fallback = { top: rowTop - popoverHeight, bottom: rowTop }; const atDocumentEdge = line >= this.#lineCount - 3; const viewport = popoverManager.getPlacementBounds(); const placeAbove = popoverManager.choosePlacement({ preferred, fallback, viewport, popoverHeight, atDocumentEdge, placementKey: MARKER_POPOVER_PLACEMENT_KEY }) === "fallback"; this.#setMarkerPopoverPosition(popover, left, placeAbove ? rowTop : rowTop + lineHeight, placeAbove, viewport); } #dismissMarkerPopover() { this.#markerPopoverEventDisposes?.forEach((dispose) => dispose()); this.#markerPopoverEventDisposes = void 0; this.#markerPopoverElement?.remove(); this.#markerPopoverElement = void 0; this.#hoveredMarkerIndex = void 0; this.#isMarkerPopoverHovered = false; this.#options.popoverManager.resetPlacement(MARKER_POPOVER_PLACEMENT_KEY); } #renderMarkerPopover(hoveredMarkerIndex) { if (hoveredMarkerIndex === this.#hoveredMarkerIndex) return; const overlayElement = this.#options.getOverlayElement(); if (hoveredMarkerIndex >= this.#markers.length || overlayElement == null) return; const { start, message, severity } = this.#markers[hoveredMarkerIndex]; const { line, character } = start; const popover = this.#markerPopoverElement; this.#options.popoverManager.resetPlacement(MARKER_POPOVER_PLACEMENT_KEY); if (popover !== void 0) { setMarkerPopoverSeverity(popover, severity); const content = popover.firstElementChild; if (content?.dataset.markerMessage !== void 0) if (typeof message === "string") content.textContent = message; else if (message instanceof HTMLElement) content.replaceChildren(message); else content.innerHTML = message.html; this.#positionMarkerPopover(popover, line, character); this.#hoveredMarkerIndex = hoveredMarkerIndex; return; } this.#markerPopoverElement = h("div", { dataset: { editorWidget: "", markerPopover: "", [markerSeverityDatasetKey(severity)]: "" }, children: [h("div", { dataset: "markerMessage", ...typeof message === "string" ? { textContent: message } : message instanceof HTMLElement ? { children: [message] } : { innerHTML: message.html } })] }, overlayElement); this.#positionMarkerPopover(this.#markerPopoverElement, line, character); this.#hoveredMarkerIndex = hoveredMarkerIndex; this.#markerPopoverEventDisposes = [addEventListener(this.#markerPopoverElement, "mouseenter", () => { this.#isMarkerPopoverHovered = true; this.#cancelMarkerPopoverHide(); }), addEventListener(this.#markerPopoverElement, "mouseleave", () => { this.#isMarkerPopoverHovered = false; this.#scheduleMarkerPopoverHide(); })]; } }; const MARKER_SEVERITIES = [ "error", "warning", "info", "hint" ]; function setMarkerPopoverSeverity(popover, severity) { for (const candidate of MARKER_SEVERITIES) delete popover.dataset[markerSeverityDatasetKey(candidate)]; popover.dataset[markerSeverityDatasetKey(severity)] = ""; } function markerSeverityDatasetKey(severity) { switch (severity) { case "error": return "markerError"; case "warning": return "markerWarning"; case "info": return "markerInfo"; case "hint": return "markerHint"; } } //#endregion export { MarkerRenderer, markerSeverityDatasetKey }; //# sourceMappingURL=marker.js.map