diff --git a/editors/code/.prettierrc.js b/editors/code/.prettierrc.js
index cafb12f0e6dd..b38dc40bdb73 100644
--- a/editors/code/.prettierrc.js
+++ b/editors/code/.prettierrc.js
@@ -2,4 +2,5 @@ module.exports = {
// use 100 because it's Rustfmt's default
// https://rust-lang.github.io/rustfmt/?version=v1.4.38&search=#max_width
printWidth: 100,
+ tabWidth: 4,
};
diff --git a/editors/code/.vscodeignore b/editors/code/.vscodeignore
index 09dc27056b37..05954c4fd385 100644
--- a/editors/code/.vscodeignore
+++ b/editors/code/.vscodeignore
@@ -7,6 +7,7 @@
!node_modules/d3-graphviz/build/d3-graphviz.min.js
!node_modules/d3/dist/d3.min.js
!out/main.js
+!out/webview/
!package-lock.json
!package.json
!ra_syntax_tree.tmGrammar.json
diff --git a/editors/code/README.md b/editors/code/README.md
index 36ab98188220..eeb6b65653ee 100644
--- a/editors/code/README.md
+++ b/editors/code/README.md
@@ -5,15 +5,15 @@ It is recommended over and replaces `rust-lang.rust`.
## Features
-- [code completion] with [imports insertion]
-- go to [definition], [implementation], [type definition]
-- [find all references], [workspace symbol search], [symbol renaming]
-- [types and documentation on hover]
-- [inlay hints] for types and parameter names
-- [semantic syntax highlighting]
-- a lot of [assists (code actions)]
-- apply suggestions from errors
-- ... and many more, check out the [manual] to see them all
+- [code completion] with [imports insertion]
+- go to [definition], [implementation], [type definition]
+- [find all references], [workspace symbol search], [symbol renaming]
+- [types and documentation on hover]
+- [inlay hints] for types and parameter names
+- [semantic syntax highlighting]
+- a lot of [assists (code actions)]
+- apply suggestions from errors
+- ... and many more, check out the [manual] to see them all
[code completion]: https://rust-analyzer.github.io/manual.html#magic-completions
[imports insertion]: https://rust-analyzer.github.io/manual.html#completion-with-autoimport
diff --git a/editors/code/build.mjs b/editors/code/build.mjs
new file mode 100644
index 000000000000..0c4133cfe437
--- /dev/null
+++ b/editors/code/build.mjs
@@ -0,0 +1,94 @@
+import * as path from "node:path";
+import { parseArgs } from "node:util";
+import * as esbuild from "esbuild";
+
+function parseCliOptions() {
+ const { values } = parseArgs({
+ options: {
+ minify: {
+ type: "boolean",
+ default: false,
+ },
+ sourcemap: {
+ type: "boolean",
+ default: false,
+ },
+ watch: {
+ type: "boolean",
+ default: false,
+ },
+ },
+ strict: true,
+ });
+ return {
+ shouldMinify: !!values.minify,
+ shouldEmitSourceMap: !!values.sourcemap,
+ isWatchMode: !!values.watch,
+ };
+}
+
+const { shouldMinify, shouldEmitSourceMap, isWatchMode } = parseCliOptions();
+
+const OUT_DIR = "./out";
+const OUT_WEBVIEW_DIR = path.resolve(OUT_DIR, "webview");
+
+/** @type {esbuild.BuildOptions} */
+const BASE_OPTIONS = {
+ minify: shouldMinify,
+ sourcemap: shouldEmitSourceMap ? "external" : false,
+ bundle: true,
+};
+
+function createBuildOption(entryPoints) {
+ /** @type {esbuild.BuildOptions} */
+ const options = {
+ ...BASE_OPTIONS,
+ entryPoints,
+ external: ["vscode"],
+ format: "cjs",
+ platform: "node",
+ target: "node16",
+ outdir: OUT_DIR,
+ };
+ return options;
+}
+
+function createBuildOptionForWebView(entryPoints) {
+ /** @type {esbuild.BuildOptions} */
+ const options = {
+ ...BASE_OPTIONS,
+ entryPoints,
+ format: "esm",
+ platform: "browser",
+ // VSCode v1.78 (Electron 22) uses Chromium 108.
+ // https://code.visualstudio.com/updates/v1_78
+ target: "chrome108",
+ outdir: OUT_WEBVIEW_DIR,
+ };
+ return options;
+}
+
+async function bundleSource(options) {
+ if (!isWatchMode) {
+ return esbuild.build(options);
+ }
+
+ const ctx = await esbuild.context(options);
+ return ctx.watch();
+}
+
+await Promise.all([
+ bundleSource(createBuildOption(["src/main.ts"])),
+ bundleSource(
+ createBuildOptionForWebView([
+ "src/webview/show_crate_graph.ts",
+ "src/webview/show_crate_graph.css",
+ ]),
+ ),
+ bundleSource(
+ createBuildOptionForWebView([
+ "src/webview/view_memory_layout.ts",
+ "src/webview/view_memory_layout.css",
+ ]),
+ ),
+]);
diff --git a/editors/code/package.json b/editors/code/package.json
index 76d7e91f3810..637fe3246ce5 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -32,7 +32,7 @@
"scripts": {
"vscode:prepublish": "npm run build-base -- --minify",
"package": "vsce package -o rust-analyzer.vsix",
- "build-base": "esbuild ./src/main.ts --bundle --outfile=out/main.js --external:vscode --format=cjs --platform=node --target=node16",
+ "build-base": "node ./build.mjs",
"build": "npm run build-base -- --sourcemap",
"watch": "npm run build-base -- --sourcemap --watch",
"format": "prettier --write .",
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index e21f536f26aa..b78a8b916c6d 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -21,6 +21,7 @@ import type { LanguageClient } from "vscode-languageclient/node";
import { LINKED_COMMANDS } from "./client";
import type { DependencyId } from "./dependencies_provider";
import { unwrapUndefinable } from "./undefinable";
+import { getNodeModulePath, getWebViewModulePath } from "./uri";
export * from "./ast_inspector";
export * from "./run";
@@ -736,7 +737,8 @@ export function viewItemTree(ctx: CtxInit): Cmd {
function crateGraph(ctx: CtxInit, full: boolean): Cmd {
return async () => {
- const nodeModulesPath = vscode.Uri.file(path.join(ctx.extensionPath, "node_modules"));
+ const nodeModulesPath = getNodeModulePath(ctx);
+ const webviewModulePath = getWebViewModulePath(ctx);
const panel = vscode.window.createWebviewPanel(
"rust-analyzer.crate-graph",
@@ -745,7 +747,7 @@ function crateGraph(ctx: CtxInit, full: boolean): Cmd {
{
enableScripts: true,
retainContextWhenHidden: true,
- localResourceRoots: [nodeModulesPath],
+ localResourceRoots: [nodeModulesPath, webviewModulePath],
},
);
const params = {
@@ -753,47 +755,25 @@ function crateGraph(ctx: CtxInit, full: boolean): Cmd {
};
const client = ctx.client;
const dot = await client.sendRequest(ra.viewCrateGraph, params);
- const uri = panel.webview.asWebviewUri(nodeModulesPath);
+
+ const nodeModuleUri = panel.webview.asWebviewUri(nodeModulesPath);
+ const webviewModuleUri = panel.webview.asWebviewUri(webviewModulePath);
const html = `
-
+
-
-
-
+
+
+
-
`;
@@ -1142,265 +1122,32 @@ export function viewMemoryLayout(ctx: CtxInit): Cmd {
position,
});
+ const webviewModulePath = getWebViewModulePath(ctx);
+
const document = vscode.window.createWebviewPanel(
"memory_layout",
"[Memory Layout]",
vscode.ViewColumn.Two,
- { enableScripts: true },
+ { enableScripts: true, localResourceRoots: [webviewModulePath] },
);
+ const uri = document.webview.asWebviewUri(webviewModulePath);
+
document.webview.html = `
-
- Document
-
+
-
`;
diff --git a/editors/code/src/uri.ts b/editors/code/src/uri.ts
new file mode 100644
index 000000000000..0cd6bea1fc6e
--- /dev/null
+++ b/editors/code/src/uri.ts
@@ -0,0 +1,16 @@
+import * as path from "node:path";
+import { Uri } from "vscode";
+import type { CtxInit } from "./ctx";
+
+function getBundledAssetsUri(ctx: CtxInit, pathname: string): Uri {
+ const resolved = path.join(ctx.extensionPath, pathname);
+ return Uri.file(resolved);
+}
+
+export function getWebViewModulePath(ctx: CtxInit) {
+ return getBundledAssetsUri(ctx, "out/webview");
+}
+
+export function getNodeModulePath(ctx: CtxInit) {
+ return getBundledAssetsUri(ctx, "node_modules");
+}
diff --git a/editors/code/src/webview/show_crate_graph.css b/editors/code/src/webview/show_crate_graph.css
new file mode 100644
index 000000000000..14a06d3ec1c9
--- /dev/null
+++ b/editors/code/src/webview/show_crate_graph.css
@@ -0,0 +1,27 @@
+/* Fill the entire view */
+html,
+body {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+}
+svg {
+ position: fixed;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+}
+
+/* Disable the graphviz background and fill the polygons */
+.graph > polygon {
+ display: none;
+}
+:is(.node, .edge) polygon {
+ fill: white;
+}
+
+/* Invert the line colours for dark themes */
+body:not(.vscode-light) .edge path {
+ stroke: white;
+}
diff --git a/editors/code/src/webview/show_crate_graph.ts b/editors/code/src/webview/show_crate_graph.ts
new file mode 100644
index 000000000000..a05b1dac4695
--- /dev/null
+++ b/editors/code/src/webview/show_crate_graph.ts
@@ -0,0 +1,19 @@
+// @ts-nocheck
+export function showCrateDependencyGraph(dot): void {
+ const graph = d3
+ .select("#graph")
+ .graphviz({ useWorker: false, useSharedWorker: false })
+ .fit(true)
+ .zoomScaleExtent([0.1, Infinity])
+ .renderDot(dot);
+
+ d3.select(window).on("click", (event) => {
+ if (event.ctrlKey) {
+ graph.resetZoom(d3.transition().duration(100));
+ }
+ });
+ d3.select(window).on("copy", (event) => {
+ event.clipboardData.setData("text/plain", dot);
+ event.preventDefault();
+ });
+}
diff --git a/editors/code/src/webview/view_memory_layout.css b/editors/code/src/webview/view_memory_layout.css
new file mode 100644
index 000000000000..a5df65dee252
--- /dev/null
+++ b/editors/code/src/webview/view_memory_layout.css
@@ -0,0 +1,113 @@
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ overflow: hidden;
+ min-height: 100%;
+ height: 100vh;
+ padding: 32px;
+ position: relative;
+ display: block;
+
+ background-color: var(--vscode-editor-background);
+ font-family: var(--vscode-editor-font-family);
+ font-size: var(--vscode-editor-font-size);
+ color: var(--vscode-editor-foreground);
+}
+
+.container {
+ position: relative;
+}
+
+.trans {
+ transition: all 0.2s ease-in-out;
+}
+
+.grid {
+ height: 100%;
+ position: relative;
+ color: var(--vscode-commandCenter-activeBorder);
+ pointer-events: none;
+}
+
+.grid-line {
+ position: absolute;
+ width: 100%;
+ height: 1px;
+ background-color: var(--vscode-commandCenter-activeBorder);
+}
+
+#tooltip {
+ position: fixed;
+ display: none;
+ z-index: 1;
+ pointer-events: none;
+ padding: 4px 8px;
+ z-index: 2;
+
+ color: var(--vscode-editorHoverWidget-foreground);
+ background-color: var(--vscode-editorHoverWidget-background);
+ border: 1px solid var(--vscode-editorHoverWidget-border);
+}
+
+#tooltip b {
+ color: var(--vscode-editorInlayHint-typeForeground);
+}
+
+#tooltip ul {
+ margin-left: 0;
+ padding-left: 20px;
+}
+
+table {
+ position: absolute;
+ transform: rotateZ(90deg) rotateX(180deg);
+ transform-origin: top left;
+ border-collapse: collapse;
+ table-layout: fixed;
+ left: 48px;
+ top: 0;
+ max-height: calc(100vw - 64px - 48px);
+ z-index: 1;
+}
+
+td {
+ border: 1px solid var(--vscode-focusBorder);
+ writing-mode: vertical-rl;
+ text-orientation: sideways-right;
+
+ height: 80px;
+}
+
+td p {
+ height: calc(100% - 16px);
+ width: calc(100% - 8px);
+ margin: 8px 4px;
+ display: inline-block;
+ transform: rotateY(180deg);
+ pointer-events: none;
+ overflow: hidden;
+}
+
+td p * {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ display: inline-block;
+ height: 100%;
+}
+
+td p b {
+ color: var(--vscode-editorInlayHint-typeForeground);
+}
+
+td:hover {
+ background-color: var(--vscode-editor-hoverHighlightBackground);
+}
+
+td:empty {
+ visibility: hidden;
+ border: 0;
+}
diff --git a/editors/code/src/webview/view_memory_layout.ts b/editors/code/src/webview/view_memory_layout.ts
new file mode 100644
index 000000000000..60f5f4630c0e
--- /dev/null
+++ b/editors/code/src/webview/view_memory_layout.ts
@@ -0,0 +1,149 @@
+// @ts-nocheck
+
+export function showMemoryLayout(data): void {
+ if (!(data && data.nodes.length)) {
+ document.body.innerText = "Not Available";
+ return;
+ }
+
+ data.nodes.map((n) => {
+ n.typename = n.typename
+ .replaceAll("&", "&")
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll('"', " & quot; ")
+ .replaceAll("'", "'");
+ return n;
+ });
+
+ let height = window.innerHeight - 64;
+
+ window.addEventListener("resize", (e) => {
+ const newHeight = window.innerHeight - 64;
+ height = newHeight;
+ container.classList.remove("trans");
+ table.classList.remove("trans");
+ locate();
+ setTimeout(() => {
+ // give delay to redraw, annoying but needed
+ container.classList.add("trans");
+ table.classList.add("trans");
+ }, 0);
+ });
+
+ const container = document.createElement("div");
+ container.classList.add("container");
+ container.classList.add("trans");
+ document.body.appendChild(container);
+
+ const tooltip = document.getElementById("tooltip");
+
+ let y = 0;
+ let zoom = 1.0;
+
+ const table = document.createElement("table");
+ table.classList.add("trans");
+ container.appendChild(table);
+ const rows = [];
+
+ function nodeT(idx, depth, offset) {
+ if (!rows[depth]) {
+ rows[depth] = { el: document.createElement("tr"), offset: 0 };
+ }
+
+ if (rows[depth].offset < offset) {
+ const pad = document.createElement("td");
+ pad.colSpan = offset - rows[depth].offset;
+ rows[depth].el.appendChild(pad);
+ rows[depth].offset += offset - rows[depth].offset;
+ }
+
+ const td = document.createElement("td");
+ td.innerHTML =
+ "" +
+ data.nodes[idx].itemName +
+ ": " +
+ data.nodes[idx].typename +
+ "
";
+
+ td.colSpan = data.nodes[idx].size;
+
+ td.addEventListener("mouseover", (e) => {
+ const node = data.nodes[idx];
+ tooltip.innerHTML =
+ node.itemName +
+ ": " +
+ node.typename +
+ "
" +
+ "" +
+ "- size = " +
+ node.size +
+ "
" +
+ "- align = " +
+ node.alignment +
+ "
" +
+ "- field offset = " +
+ node.offset +
+ "
" +
+ "
" +
+ "double click to focus";
+
+ tooltip.style.display = "block";
+ });
+ td.addEventListener("mouseleave", (_) => (tooltip.style.display = "none"));
+
+ const totalOffset = rows[depth].offset;
+ td.addEventListener("dblclick", (e) => {
+ const node = data.nodes[idx];
+ zoom = data.nodes[0].size / node.size;
+ y = (-totalOffset / data.nodes[0].size) * zoom;
+ x = 0;
+ locate();
+ });
+
+ rows[depth].el.appendChild(td);
+ rows[depth].offset += data.nodes[idx].size;
+
+ if (data.nodes[idx].childrenStart !== -1) {
+ for (let i = 0; i < data.nodes[idx].childrenLen; i++) {
+ if (data.nodes[data.nodes[idx].childrenStart + i].size) {
+ nodeT(
+ data.nodes[idx].childrenStart + i,
+ depth + 1,
+ offset + data.nodes[data.nodes[idx].childrenStart + i].offset,
+ );
+ }
+ }
+ }
+ }
+
+ nodeT(0, 0, 0);
+
+ for (const row of rows) table.appendChild(row.el);
+
+ const grid = document.createElement("div");
+ grid.classList.add("grid");
+ container.appendChild(grid);
+
+ for (let i = 0; i < data.nodes[0].size / 8 + 1; i++) {
+ const el = document.createElement("div");
+ el.classList.add("grid-line");
+ el.style.top = (i / (data.nodes[0].size / 8)) * 100 + "%";
+ el.innerText = i * 8;
+ grid.appendChild(el);
+ }
+
+ window.addEventListener("mousemove", (e) => {
+ tooltip.style.top = e.clientY + 10 + "px";
+ tooltip.style.left = e.clientX + 10 + "px";
+ });
+
+ function locate() {
+ container.style.top = height * y + "px";
+ container.style.height = height * zoom + "px";
+
+ table.style.width = container.style.height;
+ }
+
+ locate();
+}
diff --git a/editors/code/tsconfig.eslint.json b/editors/code/tsconfig.eslint.json
index 5e2b33ca39f8..21f38c40c853 100644
--- a/editors/code/tsconfig.eslint.json
+++ b/editors/code/tsconfig.eslint.json
@@ -6,6 +6,7 @@
"src",
"tests",
// these are the eslint-only inclusions
- ".eslintrc.js"
+ ".eslintrc.js",
+ "./build.mjs"
]
}
diff --git a/editors/code/tsconfig.json b/editors/code/tsconfig.json
index ee353c28dd67..f06a9d9111cc 100644
--- a/editors/code/tsconfig.json
+++ b/editors/code/tsconfig.json
@@ -6,7 +6,6 @@
"moduleResolution": "node16",
"target": "es2021",
"outDir": "out",
- "lib": ["es2021"],
"sourceMap": true,
"rootDir": ".",
"newLine": "LF",