From e5e2b4bcb4d6cdb1dc7561614d6e2688ffbe7916 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Tue, 14 Jan 2025 21:17:36 +0200 Subject: [PATCH 01/22] Proof of concept VSCode webview with Vite Changes the viteconfig and the VSCode extension, and uses the assets from the demo (need to be copied into the VSCode directory). This allows building the webview content using vite!!! Related to #62 --- src/demo/vite.config.js | 9 ++++++++- src/vscode/overview-view.ts | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/demo/vite.config.js b/src/demo/vite.config.js index ef0f34d..5691f36 100644 --- a/src/demo/vite.config.js +++ b/src/demo/vite.config.js @@ -8,6 +8,13 @@ export default defineConfig({ copyPublicDir: true, outDir: "../../dist/demo", emptyOutDir: true, + rollupOptions: { + output: { + entryFileNames: "assets/[name].js", + chunkFileNames: "assets/[name].js", + assetFileNames: "assets/[name].[ext]", + }, + }, }, - base: "/function-graph-overview/", + base: "", }); diff --git a/src/vscode/overview-view.ts b/src/vscode/overview-view.ts index 5429def..ef82f16 100644 --- a/src/vscode/overview-view.ts +++ b/src/vscode/overview-view.ts @@ -40,7 +40,9 @@ export class OverviewViewProvider implements vscode.WebviewViewProvider { localResourceRoots: [this._extensionUri], }; - webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); + console.log("webview options", webviewView.webview.options); + + webviewView.webview.html = this._getWebviewContent(webviewView.webview); webviewView.webview.onDidReceiveMessage((message) => { switch (message.event) { case "node-clicked": @@ -57,6 +59,8 @@ export class OverviewViewProvider implements vscode.WebviewViewProvider { return webview.asWebviewUri(this.getUri(filename)); } + + private _getHtmlForWebview(webview: vscode.Webview): string { // Get the local path to main script run in the webview, then convert it to a uri we can use in the webview. const scriptUri = this.getWebviewUri(webview, "main.js"); @@ -95,6 +99,34 @@ export class OverviewViewProvider implements vscode.WebviewViewProvider { return html; } + + private _getWebviewContent(webview: vscode.Webview) { + // The CSS file from the React build output + const stylesUri = this.getWebviewUri(webview, "assets/index.css"); + // The JS file from the React build output + const scriptUri = this.getWebviewUri(webview, "assets/index.js"); + + const nonce = getNonce(); + console.log("CSP Source", webview.cspSource); + console.log("script uri", scriptUri); + // Tip: Install the es6-string-html VS Code extension to enable code highlighting below + return /*html*/ ` + + + + + + + + Hello World + + +
+ + + + `; + } } function getNonce(): string { From 7aea54a2a07a2868105e68c5257c6f7a6071b4d9 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Tue, 14 Jan 2025 22:00:41 +0200 Subject: [PATCH 02/22] We can use nonce! The only thing that causes issues with inline styles is CodeMirror, and that won't be part of the final result. --- src/vscode/overview-view.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vscode/overview-view.ts b/src/vscode/overview-view.ts index ef82f16..76d90a8 100644 --- a/src/vscode/overview-view.ts +++ b/src/vscode/overview-view.ts @@ -116,7 +116,7 @@ export class OverviewViewProvider implements vscode.WebviewViewProvider { - + Hello World From 525285af7eed8a62a76bb1f9e727d9d9e81b8d80 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Wed, 15 Jan 2025 16:57:56 +0200 Subject: [PATCH 03/22] Working POC! --- .vscode/tasks.json | 2 +- src/jetbrains/src/App.svelte | 58 ++++++++++++++++++++++++++++++++++-- src/jetbrains/vite.config.js | 7 +++++ src/vscode/extension.ts | 57 ++++------------------------------- src/vscode/overview-view.ts | 18 +++++++---- 5 files changed, 83 insertions(+), 59 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8e9c1f9..33cd468 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,7 +3,7 @@ "tasks": [ { "type": "bun", - "script": "bun run --cwd ${workspaceFolder} ./scripts/build-with-esbuild.ts", + "script": "bun run --cwd ${workspaceFolder} ./scripts/build-with-esbuild.ts && bun build-jetbrains", "problemMatcher": [], "label": "bun: build", "detail": "bun run ./scripts/build-with-esbuild.ts - package.json", diff --git a/src/jetbrains/src/App.svelte b/src/jetbrains/src/App.svelte index 18468cf..8b45df1 100644 --- a/src/jetbrains/src/App.svelte +++ b/src/jetbrains/src/App.svelte @@ -2,7 +2,7 @@ import { isDark } from "../../components/lightdark"; import { onDestroy } from "svelte"; import Jetbrains from "../../components/Jetbrains.svelte"; - import { isValidLanguage, type Language } from "../../control-flow/cfg"; + import { isValidLanguage, type Language, newCFGBuilder } from "../../control-flow/cfg"; import { deserializeColorList, type ColorList, @@ -23,6 +23,9 @@ let display: Jetbrains; + const vscode = acquireVsCodeApi?acquireVsCodeApi():undefined; + + let codeAndOffset: { code: string; offset: number; @@ -44,8 +47,20 @@ e: CustomEvent<{ node: string; offset: number | null }>, ): void { console.log("navigateTo", e); - if (e.detail.offset !== null && window.navigateTo) + if (e.detail.offset === null) { + // We don't know the offset, so we can't navigate to it. + // TODO: Check if this can actually happen now. + // We changed the representation of nodes, so it shouldn't. + return; + } + // Handle JetBrains, which registers a `navigateTo` global function + if (window.navigateTo) { window.navigateTo(e.detail.offset.toString()); + }else { + // Handle VSCode + console.log("Node clicked! Posting message", e.detail.offset) + vscode?.postMessage({ event: "node-clicked", offset: e.detail.offset }); + } } window.setCode = setCode; @@ -70,6 +85,45 @@ window.setSimplify = (flag: boolean) => (simplify = flag); window.setFlatSwitch = (flag: boolean) => (flatSwitch = flag); window.setHighlight = (flag: boolean) => (highlight = flag); + + + function initVSCode() { + if (!vscode) { + // We're not running in VSCode + return; + } + console.log("Initializing VSCode API") + // Handle messages sent from the extension to the webview + window.addEventListener("message", (event) => { + console.log("Received message", event.data); + const message = event.data; // The json data that the extension sent + switch (message.type) { + case "updateCode": { + setCode(message.code, message.offset, message.language); + break; + } + } + }); + + const onClick = (event)=> { + let target = event.target; + while ( + target.tagName !== "div" && + target.tagName !== "svg" && + !target.classList.contains("node") + ) { + target = target.parentElement; + } + if (!target.classList.contains("node")) { + return; + } + vscode.postMessage({ event: "node-clicked", node: target.id }); + } + + window.addEventListener("click", onClick); + } + + initVSCode();
diff --git a/src/jetbrains/vite.config.js b/src/jetbrains/vite.config.js index 43bff10..4ab2137 100644 --- a/src/jetbrains/vite.config.js +++ b/src/jetbrains/vite.config.js @@ -8,6 +8,13 @@ export default defineConfig({ copyPublicDir: true, outDir: "../../dist/jetbrains", emptyOutDir: true, + rollupOptions: { + output: { + entryFileNames: "assets/[name].js", + chunkFileNames: "assets/[name].js", + assetFileNames: "assets/[name].[ext]", + }, + }, }, base: "", }); diff --git a/src/vscode/extension.ts b/src/vscode/extension.ts index c3803a3..58fe8f1 100644 --- a/src/vscode/extension.ts +++ b/src/vscode/extension.ts @@ -236,15 +236,17 @@ function focusEditor() { preview: false, // Don't open in preview mode viewColumn: editor.viewColumn, }); + console.log("Focus!!!"); } } function moveCursorAndReveal(offset: number) { + console.log("yo yo yo"); const editor = vscode.window.activeTextEditor; if (!editor) { return; } - + console.log("Moving!"); const position = editor.document.positionAt(offset); editor.selection = new vscode.Selection(position, position); @@ -301,9 +303,8 @@ export async function activate(context: vscode.ExtensionContext) { let cfgKey: CFGKey | undefined; let savedCFG: CFG | undefined; - function onNodeClick(node: string): void { - if (!savedCFG) return; - const offset = savedCFG.graph.getNodeAttribute(node, "startOffset"); + function onNodeClick(offset: number): void { + // if (!savedCFG) return; moveCursorAndReveal(offset); focusEditor(); } @@ -357,53 +358,7 @@ export async function activate(context: vscode.ExtensionContext) { return; } - const tree = parsers[language].parse(code); - - const functionSyntax = getFunctionAtPosition(tree, position, language); - if (!functionSyntax) return; - - console.log(functionSyntax); - const nameSyntax = functionSyntax.childForFieldName("name"); - if (nameSyntax) { - console.log("Currently in", nameSyntax.text); - } - - const { flatSwitch, simplify, highlightCurrentNode, colorScheme } = - loadSettings(); - // We'd like to avoid re-running CFG generation for a function if nothing changed. - const newKey: CFGKey = { - flatSwitch, - simplify, - functionText: functionSyntax.text, - }; - let cfg: CFG; - if (cfgKey && isSameKey(newKey, cfgKey) && savedCFG) { - cfg = savedCFG; - } else { - cfgKey = newKey; - - const builder = newCFGBuilder(language, { flatSwitch }); - cfg = builder.buildCFG(functionSyntax); - cfg = trimFor(cfg); - if (simplify) { - cfg = simplifyCFG(cfg, mergeNodeAttrs); - } - cfg = remapNodeTargets(cfg); - - savedCFG = cfg; - } - // TODO: Highlighting in the DOT is a cute trick, but might become less effective on larger functions. - // So it works for now, but I'll probably need to replace it with CSS so that I only render once per function. - - // Only highlight if there's more than one node to the graph. - const shouldHighlight = highlightCurrentNode && cfg.graph.order > 1; - const nodeToHighlight = shouldHighlight - ? cfg.offsetToNode.get(offset) - : undefined; - const dot = graphToDot(cfg, false, nodeToHighlight, colorScheme); - const svg = graphviz.dot(dot); - - provider.setSVG(svg, colorScheme["graph.background"]); + provider.setCode(code, offset, language); }, ), ); diff --git a/src/vscode/overview-view.ts b/src/vscode/overview-view.ts index 76d90a8..89fb5f2 100644 --- a/src/vscode/overview-view.ts +++ b/src/vscode/overview-view.ts @@ -1,19 +1,20 @@ import * as crypto from "node:crypto"; import * as fs from "node:fs"; import * as vscode from "vscode"; +import type { Language } from "../control-flow/cfg.ts"; export class OverviewViewProvider implements vscode.WebviewViewProvider { public static readonly viewType = "functionGraphOverview.overview"; private readonly helloWorldSvg: string; private readonly helloWorldBGColor: string; - private readonly _nodeClickHandler: (node: string) => void; + private readonly _nodeClickHandler: (offset: number) => void; private _view?: vscode.WebviewView; constructor( private readonly _extensionUri: vscode.Uri, helloWorldSvg: string, helloWorldBGColor: string, - nodeClickHandler: (node: string) => void, + nodeClickHandler: (offset: number) => void, ) { this.helloWorldSvg = helloWorldSvg; this.helloWorldBGColor = helloWorldBGColor; @@ -26,6 +27,12 @@ export class OverviewViewProvider implements vscode.WebviewViewProvider { } } + public setCode(code:string, offset:number, language:Language) { + if (this._view) { + this._view.webview.postMessage({ type: "updateCode", code, offset, language }); + } + } + resolveWebviewView( webviewView: vscode.WebviewView, _context: vscode.WebviewViewResolveContext, @@ -46,13 +53,14 @@ export class OverviewViewProvider implements vscode.WebviewViewProvider { webviewView.webview.onDidReceiveMessage((message) => { switch (message.event) { case "node-clicked": - this._nodeClickHandler(message.node); + console.log("Node clicked!", message.offset, this._nodeClickHandler); + this._nodeClickHandler(message.offset); } }); } private getUri(filename: string): vscode.Uri { - return vscode.Uri.joinPath(this._extensionUri, "webview-content", filename); + return vscode.Uri.joinPath(this._extensionUri, "dist","jetbrains", filename); } private getWebviewUri(webview: vscode.Webview, filename: string): vscode.Uri { @@ -116,7 +124,7 @@ export class OverviewViewProvider implements vscode.WebviewViewProvider { - + Hello World From e50ec3266f0eb7f80ba68b812b29a280d2c0b728 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Wed, 15 Jan 2025 17:05:44 +0200 Subject: [PATCH 04/22] Removed code! --- src/vscode/extension.ts | 91 +---------------------------------------- 1 file changed, 2 insertions(+), 89 deletions(-) diff --git a/src/vscode/extension.ts b/src/vscode/extension.ts index 58fe8f1..43061bc 100644 --- a/src/vscode/extension.ts +++ b/src/vscode/extension.ts @@ -1,18 +1,8 @@ import { Graphviz } from "@hpcc-js/wasm-graphviz"; -// The module 'vscode' contains the VS Code extensibility API -// Import the module and reference it with the alias vscode in your code below import * as vscode from "vscode"; -import Parser, { type SyntaxNode } from "web-tree-sitter"; -import { - type Language, - functionNodeTypes, - newCFGBuilder, +import type { + Language, } from "../control-flow/cfg"; -import { - type CFG, - mergeNodeAttrs, - remapNodeTargets, -} from "../control-flow/cfg-defs"; import { type ColorScheme, deserializeColorList, @@ -20,7 +10,6 @@ import { getLightColorList, listToScheme, } from "../control-flow/colors"; -import { simplifyCFG, trimFor } from "../control-flow/graph-ops"; import { graphToDot } from "../control-flow/render"; import { OverviewViewProvider } from "./overview-view"; @@ -34,52 +23,40 @@ interface SupportedLanguage { * The CFG language */ language: Language; - /** - * Name of the parser to use - */ - parserName: string; } // ADD-LANGUAGES-HERE const supportedLanguages: SupportedLanguage[] = [ { languageId: "c", language: "C" as Language, - parserName: "tree-sitter-c.wasm", }, { languageId: "cpp", language: "C++" as Language, - parserName: "tree-sitter-cpp.wasm", }, { languageId: "go", language: "Go" as Language, - parserName: "tree-sitter-go.wasm", }, { languageId: "python", language: "Python" as Language, - parserName: "tree-sitter-python.wasm", }, { languageId: "typescript", language: "TypeScript" as Language, - parserName: "tree-sitter-typescript.wasm", }, { languageId: "javascript", language: "TypeScript" as Language, - parserName: "tree-sitter-typescript.wasm", }, { languageId: "typescriptreact", language: "TSX" as Language, - parserName: "tree-sitter-tsx.wasm", }, { languageId: "javascriptreact", language: "TSX" as Language, - parserName: "tree-sitter-tsx.wasm", }, ]; @@ -95,42 +72,6 @@ const idToLanguage = (languageId: string): Language | null => { return null; }; -async function initializeParsers( - context: vscode.ExtensionContext, -): Promise> { - // const parsers: { [key in Language]: Parser } = {}; - const parsers: Partial> = {}; - - for (const lang of supportedLanguages) { - const languagePath = vscode.Uri.joinPath( - context.extensionUri, - "parsers", - lang.parserName, - ).fsPath; - parsers[lang.language] = await initializeParser(context, languagePath); - } - - return parsers as Record; -} - -async function initializeParser( - context: vscode.ExtensionContext, - languagePath: string, -) { - await Parser.init({ - locateFile(_scriptName: string, _scriptDirectory: string) { - return vscode.Uri.joinPath( - context.extensionUri, - "parsers", - "tree-sitter.wasm", - ).fsPath; - }, - }); - const parser = new Parser(); - const Go = await Parser.Language.load(languagePath); - parser.setLanguage(Go); - return parser; -} function getCurrentCode(): { code: string; @@ -203,30 +144,8 @@ function loadSettings(): Settings { colorScheme: listToScheme(colorList), }; } -type CFGKey = { functionText: string; flatSwitch: boolean; simplify: boolean }; -function isSameKey(a: CFGKey, b: CFGKey): boolean { - return JSON.stringify(a) === JSON.stringify(b); -} -function getFunctionAtPosition( - tree: Parser.Tree, - position: vscode.Position, - language: Language, -): Parser.SyntaxNode | null { - let syntax: SyntaxNode | null = tree.rootNode.descendantForPosition({ - row: position.line, - column: position.character, - }); - - while (syntax) { - if (functionNodeTypes[language].includes(syntax.type)) { - break; - } - syntax = syntax.parent; - } - return syntax; -} function focusEditor() { const editor = vscode.window.activeTextEditor; @@ -298,13 +217,9 @@ export async function activate(context: vscode.ExtensionContext) { 'Congratulations, your extension "function-graph-overview" is now active!', ); - const parsers = await initializeParsers(context); - let cfgKey: CFGKey | undefined; - let savedCFG: CFG | undefined; function onNodeClick(offset: number): void { - // if (!savedCFG) return; moveCursorAndReveal(offset); focusEditor(); } @@ -316,7 +231,6 @@ export async function activate(context: vscode.ExtensionContext) { // TODO: Make this react to all the CFG settings. if (e.affectsConfiguration("functionGraphOverview")) { const settings = loadSettings(); - if (!savedCFG) return; const dot = graphToDot( savedCFG, false, @@ -334,7 +248,6 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.window.onDidChangeActiveColorTheme(() => { const settings = loadSettings(); - if (!savedCFG) return; const dot = graphToDot(savedCFG, false, undefined, settings.colorScheme); const svg = graphviz.dot(dot); From 9f2c4aec11b2a64fd7f5126da758fb00235a5667 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Wed, 15 Jan 2025 17:22:55 +0200 Subject: [PATCH 05/22] Cleanup & formatting --- .github/workflows/build.yaml | 2 + .vscodeignore | 1 - src/demo/vite.config.js | 9 +-- src/jetbrains/src/App.svelte | 22 +++--- src/vscode/extension.ts | 126 ++++++----------------------------- src/vscode/overview-view.ts | 74 +++++--------------- webview-content/index.html | 31 --------- webview-content/main.css | 20 ------ webview-content/main.js | 45 ------------- webview-content/reset.css | 30 --------- webview-content/vscode.css | 91 ------------------------- 11 files changed, 52 insertions(+), 399 deletions(-) delete mode 100644 webview-content/index.html delete mode 100644 webview-content/main.css delete mode 100644 webview-content/main.js delete mode 100644 webview-content/reset.css delete mode 100644 webview-content/vscode.css diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4a10469..ddd0012 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -35,6 +35,8 @@ jobs: - run: bun install --production + - run: bun build-jetbrains + - run: bun run package - name: Setup Environment diff --git a/.vscodeignore b/.vscodeignore index 92dfc2f..ef9df19 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -8,7 +8,6 @@ !CHANGELOG.md !LICENSE.txt !README.md -!webview-content/* !parsers/* !media/icon.png !media/svg/view-icon.svg \ No newline at end of file diff --git a/src/demo/vite.config.js b/src/demo/vite.config.js index 5691f36..ef0f34d 100644 --- a/src/demo/vite.config.js +++ b/src/demo/vite.config.js @@ -8,13 +8,6 @@ export default defineConfig({ copyPublicDir: true, outDir: "../../dist/demo", emptyOutDir: true, - rollupOptions: { - output: { - entryFileNames: "assets/[name].js", - chunkFileNames: "assets/[name].js", - assetFileNames: "assets/[name].[ext]", - }, - }, }, - base: "", + base: "/function-graph-overview/", }); diff --git a/src/jetbrains/src/App.svelte b/src/jetbrains/src/App.svelte index 8b45df1..4d11e7d 100644 --- a/src/jetbrains/src/App.svelte +++ b/src/jetbrains/src/App.svelte @@ -2,7 +2,11 @@ import { isDark } from "../../components/lightdark"; import { onDestroy } from "svelte"; import Jetbrains from "../../components/Jetbrains.svelte"; - import { isValidLanguage, type Language, newCFGBuilder } from "../../control-flow/cfg"; + import { + isValidLanguage, + type Language, + newCFGBuilder, + } from "../../control-flow/cfg"; import { deserializeColorList, type ColorList, @@ -23,8 +27,7 @@ let display: Jetbrains; - const vscode = acquireVsCodeApi?acquireVsCodeApi():undefined; - + const vscode = acquireVsCodeApi ? acquireVsCodeApi() : undefined; let codeAndOffset: { code: string; @@ -56,9 +59,9 @@ // Handle JetBrains, which registers a `navigateTo` global function if (window.navigateTo) { window.navigateTo(e.detail.offset.toString()); - }else { + } else { // Handle VSCode - console.log("Node clicked! Posting message", e.detail.offset) + console.log("Node clicked! Posting message", e.detail.offset); vscode?.postMessage({ event: "node-clicked", offset: e.detail.offset }); } } @@ -86,13 +89,12 @@ window.setFlatSwitch = (flag: boolean) => (flatSwitch = flag); window.setHighlight = (flag: boolean) => (highlight = flag); - function initVSCode() { if (!vscode) { // We're not running in VSCode return; } - console.log("Initializing VSCode API") + console.log("Initializing VSCode API"); // Handle messages sent from the extension to the webview window.addEventListener("message", (event) => { console.log("Received message", event.data); @@ -105,20 +107,20 @@ } }); - const onClick = (event)=> { + const onClick = (event) => { let target = event.target; while ( target.tagName !== "div" && target.tagName !== "svg" && !target.classList.contains("node") - ) { + ) { target = target.parentElement; } if (!target.classList.contains("node")) { return; } vscode.postMessage({ event: "node-clicked", node: target.id }); - } + }; window.addEventListener("click", onClick); } diff --git a/src/vscode/extension.ts b/src/vscode/extension.ts index 43061bc..075457c 100644 --- a/src/vscode/extension.ts +++ b/src/vscode/extension.ts @@ -1,8 +1,5 @@ -import { Graphviz } from "@hpcc-js/wasm-graphviz"; import * as vscode from "vscode"; -import type { - Language, -} from "../control-flow/cfg"; +import type { Language } from "../control-flow/cfg"; import { type ColorScheme, deserializeColorList, @@ -10,68 +7,23 @@ import { getLightColorList, listToScheme, } from "../control-flow/colors"; -import { graphToDot } from "../control-flow/render"; import { OverviewViewProvider } from "./overview-view"; -let graphviz: Graphviz; -interface SupportedLanguage { - /** - * The VSCode languageId to match on - */ - languageId: string; - /** - * The CFG language - */ - language: Language; -} // ADD-LANGUAGES-HERE -const supportedLanguages: SupportedLanguage[] = [ - { - languageId: "c", - language: "C" as Language, - }, - { - languageId: "cpp", - language: "C++" as Language, - }, - { - languageId: "go", - language: "Go" as Language, - }, - { - languageId: "python", - language: "Python" as Language, - }, - { - languageId: "typescript", - language: "TypeScript" as Language, - }, - { - languageId: "javascript", - language: "TypeScript" as Language, - }, - { - languageId: "typescriptreact", - language: "TSX" as Language, - }, - { - languageId: "javascriptreact", - language: "TSX" as Language, - }, -]; - -const supportedLanguageIds = new Set( - supportedLanguages.map((lang) => lang.languageId), -); -const idToLanguage = (languageId: string): Language | null => { - for (const lang of supportedLanguages) { - if (lang.languageId === languageId) { - return lang.language; - } - } - return null; +const languageMapping: { [key: string]: Language } = { + c: "C", + cpp: "C++", + go: "Go", + python: "Python", + typescript: "TypeScript", + javascript: "TypeScript", + typescriptreact: "TypeScript", + javascriptreact: "TypeScript", }; +const idToLanguage = (languageId: string): Language | undefined => { + return languageMapping[languageId]; +}; function getCurrentCode(): { code: string; @@ -85,13 +37,10 @@ function getCurrentCode(): { const document = editor.document; const languageId = document.languageId; - if (!supportedLanguageIds.has(languageId)) { - console.log(`Unsupported language id: ${languageId}`); - return null; - } const language = idToLanguage(languageId); if (!language) { + console.log(`Unsupported language id: ${languageId}`); return null; } @@ -145,8 +94,6 @@ function loadSettings(): Settings { }; } - - function focusEditor() { const editor = vscode.window.activeTextEditor; if (editor) { @@ -179,30 +126,14 @@ function moveCursorAndReveal(offset: number) { // This method is called when your extension is activated // Your extension is activated the very first time the command is executed export async function activate(context: vscode.ExtensionContext) { - graphviz = await Graphviz.load(); - - const helloWorldDark = `digraph G { - bgcolor="#1e1e1e" - node [color="#e0e0e0", fontcolor="#e0e0e0"] - edge [color="#e0e0e0"] - Hello -> World -}`; - const helloWorldLight = "digraph G { Hello -> World }"; - // We use the color theme for the initial graph, as it's a good way to avoid // shocking the user without being overly complicated with reading custom // color schemes. - const helloWorldSvg = graphviz.dot( - isThemeDark() ? helloWorldDark : helloWorldLight, - ); - const helloWorldBGColor = isThemeDark() ? "#1e1e1e" : "white"; + const _helloWorldBGColor = isThemeDark() ? "#1e1e1e" : "white"; - const provider = new OverviewViewProvider( - context.extensionUri, - helloWorldSvg, - helloWorldBGColor, - onNodeClick, - ); + // TODO: We should probably pass the initial config in here, so that the initial + // graph will be in the right color theme. + const provider = new OverviewViewProvider(context.extensionUri, onNodeClick); context.subscriptions.push( vscode.window.registerWebviewViewProvider( @@ -217,8 +148,6 @@ export async function activate(context: vscode.ExtensionContext) { 'Congratulations, your extension "function-graph-overview" is now active!', ); - - function onNodeClick(offset: number): void { moveCursorAndReveal(offset); focusEditor(); @@ -230,16 +159,8 @@ export async function activate(context: vscode.ExtensionContext) { // TODO: This currently only changes the color-scheme. // TODO: Make this react to all the CFG settings. if (e.affectsConfiguration("functionGraphOverview")) { - const settings = loadSettings(); - const dot = graphToDot( - savedCFG, - false, - undefined, - settings.colorScheme, - ); - const svg = graphviz.dot(dot); - - provider.setSVG(svg, settings.colorScheme["graph.background"]); + const _settings = loadSettings(); + // TODO: Trigger a redraw } }, ), @@ -247,11 +168,8 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.window.onDidChangeActiveColorTheme(() => { - const settings = loadSettings(); - const dot = graphToDot(savedCFG, false, undefined, settings.colorScheme); - const svg = graphviz.dot(dot); - - provider.setSVG(svg, settings.colorScheme["graph.background"]); + const _settings = loadSettings(); + // TODO: Trigger a redraw }), ); diff --git a/src/vscode/overview-view.ts b/src/vscode/overview-view.ts index 89fb5f2..c37dd26 100644 --- a/src/vscode/overview-view.ts +++ b/src/vscode/overview-view.ts @@ -1,35 +1,27 @@ import * as crypto from "node:crypto"; -import * as fs from "node:fs"; import * as vscode from "vscode"; import type { Language } from "../control-flow/cfg.ts"; export class OverviewViewProvider implements vscode.WebviewViewProvider { public static readonly viewType = "functionGraphOverview.overview"; - private readonly helloWorldSvg: string; - private readonly helloWorldBGColor: string; private readonly _nodeClickHandler: (offset: number) => void; private _view?: vscode.WebviewView; constructor( private readonly _extensionUri: vscode.Uri, - helloWorldSvg: string, - helloWorldBGColor: string, nodeClickHandler: (offset: number) => void, ) { - this.helloWorldSvg = helloWorldSvg; - this.helloWorldBGColor = helloWorldBGColor; this._nodeClickHandler = nodeClickHandler; } - public setSVG(svg: string, bgColor: string) { + public setCode(code: string, offset: number, language: Language) { if (this._view) { - this._view.webview.postMessage({ type: "svgImage", svg, bgColor }); - } - } - - public setCode(code:string, offset:number, language:Language) { - if (this._view) { - this._view.webview.postMessage({ type: "updateCode", code, offset, language }); + this._view.webview.postMessage({ + type: "updateCode", + code, + offset, + language, + }); } } @@ -60,59 +52,23 @@ export class OverviewViewProvider implements vscode.WebviewViewProvider { } private getUri(filename: string): vscode.Uri { - return vscode.Uri.joinPath(this._extensionUri, "dist","jetbrains", filename); + return vscode.Uri.joinPath( + this._extensionUri, + "dist", + "jetbrains", + filename, + ); } private getWebviewUri(webview: vscode.Webview, filename: string): vscode.Uri { return webview.asWebviewUri(this.getUri(filename)); } - - - private _getHtmlForWebview(webview: vscode.Webview): string { - // Get the local path to main script run in the webview, then convert it to a uri we can use in the webview. - const scriptUri = this.getWebviewUri(webview, "main.js"); - - // Do the same for the stylesheet. - const styleResetUri = this.getWebviewUri(webview, "reset.css"); - const styleVSCodeUri = this.getWebviewUri(webview, "vscode.css"); - const styleMainUri = this.getWebviewUri(webview, "main.css"); - - // Use a nonce to only allow a specific script to be run. - const nonce = getNonce(); - - const htmlPath = this.getUri("index.html").fsPath; - let html = fs.readFileSync(htmlPath).toString(); - - const replacements: [RegExp, string][] = [ - [/#{nonce}/g, nonce], - [/#{cspSource}/g, webview.cspSource], - [/#{styleResetUri}/g, styleResetUri.toString()], - [/#{styleVSCodeUri}/g, styleVSCodeUri.toString()], - [/#{styleMainUri}/g, styleMainUri.toString()], - [/#{scriptUri}/g, scriptUri.toString()], - [/#{helloWorldSvg}/g, this.helloWorldSvg], - [/#{helloWorldBGColor}/g, this.helloWorldBGColor], - ]; - - for (const [pattern, substitute] of replacements) { - html = html.replaceAll(pattern, substitute); - } - - // Make sure we did not forget any replacements - const unreplaced = html.match(/#{\w+}/g); - if (unreplaced) { - console.log("Unreplaced placeholder found!", unreplaced); - } - - return html; - } - private _getWebviewContent(webview: vscode.Webview) { // The CSS file from the React build output - const stylesUri = this.getWebviewUri(webview, "assets/index.css"); + const stylesUri = this.getWebviewUri(webview, "assets/index.css"); // The JS file from the React build output - const scriptUri = this.getWebviewUri(webview, "assets/index.js"); + const scriptUri = this.getWebviewUri(webview, "assets/index.js"); const nonce = getNonce(); console.log("CSP Source", webview.cspSource); diff --git a/webview-content/index.html b/webview-content/index.html deleted file mode 100644 index faa7721..0000000 --- a/webview-content/index.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - Overview - - - -
#{helloWorldSvg}
- - - - diff --git a/webview-content/main.css b/webview-content/main.css deleted file mode 100644 index ffc0498..0000000 --- a/webview-content/main.css +++ /dev/null @@ -1,20 +0,0 @@ -body { - height: 100dvh; - width: 100dvw; - padding: 0; - margin: 0; - overflow: hidden; - display: flex; - justify-content: center; - align-items: center; -} - -svg { - width: 100%; - height: 100%; -} - -#overview { - width: 100%; - height: 100%; -} diff --git a/webview-content/main.js b/webview-content/main.js deleted file mode 100644 index 2a23e95..0000000 --- a/webview-content/main.js +++ /dev/null @@ -1,45 +0,0 @@ -//@ts-check - -// This script will be run within the webview itself -// It cannot access the main VS Code APIs directly. -(() => { - const vscode = acquireVsCodeApi(); - - // Handle messages sent from the extension to the webview - window.addEventListener("message", (event) => { - const message = event.data; // The json data that the extension sent - switch (message.type) { - case "svgImage": { - displaySVG(message.svg, message.bgColor); - break; - } - } - }); - - window.addEventListener("click", onClick); - - /** - * - * @param {string} svgMarkup - */ - function displaySVG(svgMarkup, bgColor) { - const div = document.querySelector("#overview"); - if (div) div.innerHTML = svgMarkup; - document.body.style.backgroundColor = bgColor; - } - - function onClick(event) { - let target = event.target; - while ( - target.tagName !== "div" && - target.tagName !== "svg" && - !target.classList.contains("node") - ) { - target = target.parentElement; - } - if (!target.classList.contains("node")) { - return; - } - vscode.postMessage({ event: "node-clicked", node: target.id }); - } -})(); diff --git a/webview-content/reset.css b/webview-content/reset.css deleted file mode 100644 index 0e3b4cf..0000000 --- a/webview-content/reset.css +++ /dev/null @@ -1,30 +0,0 @@ -html { - box-sizing: border-box; - font-size: 13px; -} - -*, -*:before, -*:after { - box-sizing: inherit; -} - -body, -h1, -h2, -h3, -h4, -h5, -h6, -p, -ol, -ul { - margin: 0; - padding: 0; - font-weight: normal; -} - -img { - max-width: 100%; - height: auto; -} diff --git a/webview-content/vscode.css b/webview-content/vscode.css deleted file mode 100644 index 16493d8..0000000 --- a/webview-content/vscode.css +++ /dev/null @@ -1,91 +0,0 @@ -:root { - --container-paddding: 20px; - --input-padding-vertical: 6px; - --input-padding-horizontal: 4px; - --input-margin-vertical: 4px; - --input-margin-horizontal: 0; -} - -body { - padding: 0 var(--container-paddding); - color: var(--vscode-foreground); - font-size: var(--vscode-font-size); - font-weight: var(--vscode-font-weight); - font-family: var(--vscode-font-family); - background-color: var(--vscode-editor-background); -} - -ol, -ul { - padding-left: var(--container-paddding); -} - -body > *, -form > * { - margin-block-start: var(--input-margin-vertical); - margin-block-end: var(--input-margin-vertical); -} - -*:focus { - outline-color: var(--vscode-focusBorder) !important; -} - -a { - color: var(--vscode-textLink-foreground); -} - -a:hover, -a:active { - color: var(--vscode-textLink-activeForeground); -} - -code { - font-size: var(--vscode-editor-font-size); - font-family: var(--vscode-editor-font-family); -} - -button { - border: none; - padding: var(--input-padding-vertical) var(--input-padding-horizontal); - width: 100%; - text-align: center; - outline: 1px solid transparent; - outline-offset: 2px !important; - color: var(--vscode-button-foreground); - background: var(--vscode-button-background); -} - -button:hover { - cursor: pointer; - background: var(--vscode-button-hoverBackground); -} - -button:focus { - outline-color: var(--vscode-focusBorder); -} - -button.secondary { - color: var(--vscode-button-secondaryForeground); - background: var(--vscode-button-secondaryBackground); -} - -button.secondary:hover { - background: var(--vscode-button-secondaryHoverBackground); -} - -input:not([type="checkbox"]), -textarea { - display: block; - width: 100%; - border: none; - font-family: var(--vscode-font-family); - padding: var(--input-padding-vertical) var(--input-padding-horizontal); - color: var(--vscode-input-foreground); - outline-color: var(--vscode-input-border); - background-color: var(--vscode-input-background); -} - -input::placeholder, -textarea::placeholder { - color: var(--vscode-input-placeholderForeground); -} From 326c6cd79fd46983f1430c387422f0e9a1ba96f7 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Wed, 15 Jan 2025 17:25:05 +0200 Subject: [PATCH 06/22] Build! --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ddd0012..c0111b9 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -33,7 +33,7 @@ jobs: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - - run: bun install --production + - run: bun install - run: bun build-jetbrains From bf83f98f9c8806531d4e0cfa7368882cdf7b2fb9 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Wed, 15 Jan 2025 17:27:35 +0200 Subject: [PATCH 07/22] Only include the parsers once! --- .vscodeignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.vscodeignore b/.vscodeignore index ef9df19..ab73530 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -8,6 +8,5 @@ !CHANGELOG.md !LICENSE.txt !README.md -!parsers/* !media/icon.png !media/svg/view-icon.svg \ No newline at end of file From 4533c2c4dc65d5bef9901d822b0190a3a224fbdf Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Wed, 15 Jan 2025 18:50:15 +0200 Subject: [PATCH 08/22] Plan ahead! --- src/jetbrains/src/App.svelte | 41 +++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/jetbrains/src/App.svelte b/src/jetbrains/src/App.svelte index 4d11e7d..4eed2e1 100644 --- a/src/jetbrains/src/App.svelte +++ b/src/jetbrains/src/App.svelte @@ -1,11 +1,50 @@
diff --git a/src/vscode/extension.ts b/src/vscode/extension.ts index 075457c..740c62b 100644 --- a/src/vscode/extension.ts +++ b/src/vscode/extension.ts @@ -126,14 +126,13 @@ function moveCursorAndReveal(offset: number) { // This method is called when your extension is activated // Your extension is activated the very first time the command is executed export async function activate(context: vscode.ExtensionContext) { - // We use the color theme for the initial graph, as it's a good way to avoid - // shocking the user without being overly complicated with reading custom - // color schemes. - const _helloWorldBGColor = isThemeDark() ? "#1e1e1e" : "white"; - // TODO: We should probably pass the initial config in here, so that the initial // graph will be in the right color theme. - const provider = new OverviewViewProvider(context.extensionUri, onNodeClick); + const provider = new OverviewViewProvider( + context.extensionUri, + onNodeClick, + isThemeDark(), + ); context.subscriptions.push( vscode.window.registerWebviewViewProvider( diff --git a/src/vscode/overview-view.ts b/src/vscode/overview-view.ts index c37dd26..99cd7b3 100644 --- a/src/vscode/overview-view.ts +++ b/src/vscode/overview-view.ts @@ -10,6 +10,7 @@ export class OverviewViewProvider implements vscode.WebviewViewProvider { constructor( private readonly _extensionUri: vscode.Uri, nodeClickHandler: (offset: number) => void, + private readonly isDark: boolean, ) { this._nodeClickHandler = nodeClickHandler; } @@ -65,15 +66,10 @@ export class OverviewViewProvider implements vscode.WebviewViewProvider { } private _getWebviewContent(webview: vscode.Webview) { - // The CSS file from the React build output const stylesUri = this.getWebviewUri(webview, "assets/index.css"); - // The JS file from the React build output const scriptUri = this.getWebviewUri(webview, "assets/index.js"); const nonce = getNonce(); - console.log("CSP Source", webview.cspSource); - console.log("script uri", scriptUri); - // Tip: Install the es6-string-html VS Code extension to enable code highlighting below return /*html*/ ` @@ -82,9 +78,9 @@ export class OverviewViewProvider implements vscode.WebviewViewProvider { - Hello World + Function Graph Overview - +
From 57b60b68b37ec178f613e1295fc426e2f5fd55d2 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Thu, 16 Jan 2025 21:10:57 +0200 Subject: [PATCH 10/22] Type-safe message-passing for VSCode WebView --- src/jetbrains/src/App.svelte | 24 +++++++++++++++++----- src/vscode/extension.ts | 11 +++++++--- src/vscode/messages.ts | 40 ++++++++++++++++++++++++++++++++++++ src/vscode/overview-view.ts | 32 +++++++++++++---------------- 4 files changed, 81 insertions(+), 26 deletions(-) create mode 100644 src/vscode/messages.ts diff --git a/src/jetbrains/src/App.svelte b/src/jetbrains/src/App.svelte index fca3df0..0ca9d64 100644 --- a/src/jetbrains/src/App.svelte +++ b/src/jetbrains/src/App.svelte @@ -1,3 +1,13 @@ + +
Date: Fri, 17 Jan 2025 11:59:45 +0200 Subject: [PATCH 15/22] Cleanup --- src/components/Jetbrains.svelte | 4 -- src/jetbrains/src/App.svelte | 78 ++++++++++----------------------- src/vscode/overview-view.ts | 2 +- 3 files changed, 25 insertions(+), 59 deletions(-) diff --git a/src/components/Jetbrains.svelte b/src/components/Jetbrains.svelte index 4ee55ee..e8b74f1 100644 --- a/src/components/Jetbrains.svelte +++ b/src/components/Jetbrains.svelte @@ -151,10 +151,6 @@ offset: cfg.graph.getNodeAttribute(target.id, "startOffset"), }); } - - export function applyColors(colors: ColorList) { - colorList = colors; - } {#await initialize() then} diff --git a/src/jetbrains/src/App.svelte b/src/jetbrains/src/App.svelte index 81d91d8..d216482 100644 --- a/src/jetbrains/src/App.svelte +++ b/src/jetbrains/src/App.svelte @@ -10,9 +10,15 @@ declare global { interface Window { JetBrains?: { + /** + * Calls going from the WebView to the JetBrains extension + */ ToExtension?: { navigateTo: (offset: string) => void; }; + /** + * Functions the extension can use to call into the WebView + */ ToWebview?: { setColors: (colors: string) => void; setCode: (code: string, offset: number, language: string) => void; @@ -26,47 +32,6 @@
- Date: Fri, 17 Jan 2025 13:53:37 +0200 Subject: [PATCH 20/22] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 455f9f9..2bc1a62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how ### Fixed - `finally` blocks are now supported in TypeScript. +- Methods are now supported in TypeScript. ### Changed From b75e286dd57a7e00cdab5b6abe216744fc2c3e31 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Fri, 17 Jan 2025 14:03:16 +0200 Subject: [PATCH 21/22] Cleanup and a bit of docs --- src/vscode/messages.ts | 6 ++++++ src/vscode/overview-view.ts | 2 -- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vscode/messages.ts b/src/vscode/messages.ts index 466ebd9..441032f 100644 --- a/src/vscode/messages.ts +++ b/src/vscode/messages.ts @@ -1,3 +1,6 @@ +/** + * Defines the message types VSCode uses to communicate with the WebView. + */ import type { Language } from "../control-flow/cfg.ts"; import type { ColorList } from "../control-flow/colors.ts"; @@ -37,6 +40,9 @@ export type MessageHandlersOf = { [T in MessageTagOf]: (message: MessageMapOf[T]) => void; }; +/** + * Handles messages in a type-safe manner. + */ export class MessageHandler { constructor(private messageHandlers: MessageHandlersOf) {} diff --git a/src/vscode/overview-view.ts b/src/vscode/overview-view.ts index 4790577..25cacb0 100644 --- a/src/vscode/overview-view.ts +++ b/src/vscode/overview-view.ts @@ -50,8 +50,6 @@ export class OverviewViewProvider implements vscode.WebviewViewProvider { localResourceRoots: [this._extensionUri], }; - console.log("webview options", webviewView.webview.options); - webviewView.webview.html = this._getWebviewContent(webviewView.webview); webviewView.webview.onDidReceiveMessage((message: MessageToVscode) => this.messageHandler.handleMessage(message), From 1b517386aa2c360fc7c14a756a1a2fbb90e08d60 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Fri, 17 Jan 2025 14:04:38 +0200 Subject: [PATCH 22/22] Remove debug prints --- src/webview/src/App.svelte | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/webview/src/App.svelte b/src/webview/src/App.svelte index 53585d0..16a187a 100644 --- a/src/webview/src/App.svelte +++ b/src/webview/src/App.svelte @@ -126,7 +126,6 @@ } | null = null; function setCode(newCode: string, offset: number, language: string) { - console.log("SetCode", newCode, offset, language); if (isValidLanguage(language)) { codeAndOffset = { code: newCode, offset, language }; } @@ -137,10 +136,8 @@ // We're not running in VSCode return; } - console.log("Initializing VSCode API"); // Handle messages sent from the extension to the webview window.addEventListener("message", (event: { data: MessageToWebview }) => { - console.log("Received message", event.data); const message = event.data; // The json data that the extension sent switch (message.tag) { case "updateCode": { @@ -163,8 +160,6 @@ }); stateHandler.onNavigateTo((offset: number) => { - // Handle VSCode - console.log("Node clicked! Posting message", offset); vscode?.postMessage({ tag: "navigateTo", offset: offset }); }); } @@ -207,7 +202,6 @@ function navigateTo( e: CustomEvent<{ node: string; offset: number | null }>, ): void { - console.log("navigateTo", e); if (e.detail.offset === null) { // We don't know the offset, so we can't navigate to it. // TODO: Check if this can actually happen now.