UNPKG

vite-plugin-vscode-jumper

Version:

A Vite plugin to enable element picker and jump to Vue SFC source files in VSCode via shortcut

220 lines (217 loc) 6.41 kB
// src/index.ts import { parse } from "@vue/compiler-sfc"; // src/enableElementPicker.ts var createHighlightOverlay = () => { const overlay = document.createElement("div"); overlay.id = "__element_selector_overlay__"; Object.assign(overlay.style, { position: "absolute", pointerEvents: "none", border: "2px solid #00a8ff", zIndex: "999999", background: "rgba(0, 168, 255, 0.1)", display: "none" }); document.body.appendChild(overlay); return overlay; }; function enableElementPicker(onPick, isActiveGetter, ESC) { let overlay = null; const addGlobalPickingStyle = () => { document.body.style.cursor = "pointer"; document.body.style.userSelect = "none"; }; const removeGlobalPickingStyle = () => { document.body.style.cursor = ""; document.body.style.userSelect = ""; }; const moveHandler = (e) => { if (!isActiveGetter()) return; const target = e.target; if (!target || target === overlay) return; const rect = target.getBoundingClientRect(); if (!overlay) { overlay = createHighlightOverlay(); addGlobalPickingStyle(); } Object.assign(overlay.style, { top: `${rect.top + window.scrollY}px`, left: `${rect.left + window.scrollX}px`, width: `${rect.width}px`, height: `${rect.height}px`, display: "block" }); }; const clickHandler = (e) => { if (!isActiveGetter() || !overlay) return; e.preventDefault(); e.stopPropagation(); const target = e.target; onPick(target); cleanup(); }; const keydownHandler = (e) => { if (e.key === "Escape") { cleanup(); if (ESC) ESC(); } }; const cleanup = () => { document.removeEventListener("mousemove", moveHandler, true); document.removeEventListener("click", clickHandler, true); document.removeEventListener("keydown", keydownHandler, true); overlay?.remove(); overlay = null; removeGlobalPickingStyle(); }; document.addEventListener("mousemove", moveHandler, true); document.addEventListener("click", clickHandler, true); document.addEventListener("keydown", keydownHandler, true); return cleanup; } // src/client.ts function findElementWithCodePath(el) { while (el && el !== document.body) { if (el.hasAttribute("code-path")) return el; el = el.parentElement; } return null; } function parseShortcut(shortcut) { const parts = shortcut.toLowerCase().split("+"); const keySet = new Set(parts); return { ctrl: keySet.has("ctrl"), shift: keySet.has("shift"), alt: keySet.has("alt"), meta: keySet.has("meta"), key: parts.find((k) => !["ctrl", "shift", "alt", "meta"].includes(k)) || "", allKeys: parts }; } function useGlobalElementShortcut(shortcut, callback) { let isPicking = false; let cleanupPicker = null; const { ctrl, shift, alt, meta, key, allKeys } = parseShortcut(shortcut); const isShortcutPressed = (e) => { return ctrl === e.ctrlKey && shift === e.shiftKey && alt === e.altKey && meta === e.metaKey && (key ? e.key.toLowerCase() === key : true); }; const keydownHandler = (e) => { if (isShortcutPressed(e) && !isPicking) { isPicking = true; cleanupPicker = enableElementPicker( (el) => { callback(el); cleanupPicker?.(); cleanupPicker = null; isPicking = false; if (document.activeElement instanceof HTMLElement) { document.activeElement.blur(); } }, () => isPicking ); } }; const keyupHandler = (e) => { if (!isPicking) return; const releasedKey = e.key.toLowerCase(); const keyMap = /* @__PURE__ */ new Map([ ["ctrl", e.ctrlKey], ["shift", e.shiftKey], ["alt", e.altKey], ["meta", e.metaKey] ]); const comboKeyReleased = allKeys.some((k) => { if (["ctrl", "shift", "alt", "meta"].includes(k)) { return !keyMap.get(k); } else { return k === releasedKey; } }); if (comboKeyReleased) { cleanupPicker?.(); cleanupPicker = null; isPicking = false; if (document.activeElement instanceof HTMLElement) { document.activeElement.blur(); } } }; window.addEventListener("keydown", keydownHandler, true); window.addEventListener("keyup", keyupHandler, true); return () => { window.removeEventListener("keydown", keydownHandler, true); window.removeEventListener("keyup", keyupHandler, true); cleanupPicker?.(); cleanupPicker = null; isPicking = false; }; } if (import.meta.env.DEV) { useGlobalElementShortcut("alt+shift", (el) => { const target = findElementWithCodePath(el); if (target) { const url = target.getAttribute("code-path"); if (url?.startsWith("vscode://file/")) { window.location.href = url; } } }); } var client_default = ` // \u8FD9\u91CC\u662F\u4E0A\u9762\u5199\u7684\u811A\u672C\u5185\u5BB9\uFF0C\u6CE8\u610F\u8F6C\u4E49\u548C\u6253\u5305\u65B9\u5F0F `; // src/index.ts function vscodeJumper() { return { name: "vite-plugin-vscode-jumper", enforce: "pre", transform(code, id) { if (!id.endsWith(".vue") || process.env.NODE_ENV === "production") return null; const { descriptor } = parse(code); const { template } = descriptor; if (!template) return null; if (template.content.includes("code-path=")) { return null; } const absPath = `vscode://file/${id.replace(/\\/g, "/")}`; const newTemplate = template.content.replace( /<([a-zA-Z0-9\-]+)([^>]*)>/, `<$1$2 code-path="${absPath}">` ); const newCode = code.replace(template.content, newTemplate); return { code: newCode, map: null }; }, configureServer(server) { server.middlewares.use((req, res, next) => { if (req.url === "/@vscode-jumper-client") { res.setHeader("Content-Type", "application/javascript"); res.end(client_default); } else { next(); } }); }, transformIndexHtml(html) { const scriptTag = `<script type="module" src="/@vscode-jumper-client"></script>`; return html.replace(/<\/head>/, `${scriptTag} </head>`); } }; } export { vscodeJumper as default }; //# sourceMappingURL=index.js.map