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
JavaScript
// 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