Skip to content

Commit

Permalink
introduce a command for the code map
Browse files Browse the repository at this point in the history
fix the issues with duplicate editors being opened
optimize it to do less rendering
  • Loading branch information
Trass3r committed Aug 9, 2022
1 parent 9520bda commit 55adf3b
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 31 deletions.
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@
"highContrast": "#99999999"
}
}
],
"commands": [
{
"command": "debug-utils.showCodeMap",
"title": "Build a Code Map during debugging",
"when": "inDebugMode"
}
]
}
}
88 changes: 58 additions & 30 deletions src/codemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,42 @@ import * as gv from "ts-graphviz";
export class CodeMapProvider implements vs.DebugAdapterTracker, vs.TextDocumentContentProvider {
dotDocument?: vs.TextDocument;
graph: gv.Digraph;
active = false;

constructor() {
this.graph = gv.digraph('Call Graph', { splines: true });
}

/** @override */
provideTextDocumentContent(uri: vs.Uri, token: vs.CancellationToken): vs.ProviderResult<string> {
// here we update the source .dot document
// TODO: actually check uri
return gv.toDot(this.graph);
}

// https://code.visualstudio.com/api/extension-guides/virtual-documents#update-virtual-documents
onDidChangeEmitter = new vs.EventEmitter<vs.Uri>();
onDidChange = this.onDidChangeEmitter.event; // without this the emitter silently doesn't work

async onWillStartSession() {
this.dotDocument = await vs.workspace.openTextDocument(vs.Uri.parse('dot:1.dot', true));
async activate() : Promise<void> {
this.graph = gv.digraph('Call Graph', { splines: true }); // reset the graph

// used for debugging
vs.window.showTextDocument(this.dotDocument, { preview: false });
this.dotDocument = await vs.workspace.openTextDocument(vs.Uri.parse('dot:callgraph.dot', true));
this.active = true;

// save the current editor
const activeEditor = vs.window.activeTextEditor;
// show the `dot` source
vs.window.showTextDocument(this.dotDocument, vs.ViewColumn.Beside, true);

const args = {
document: this.dotDocument,
callback: (webpanel: any) => {
// The callback function receives the newly created webPanel.
// Overload webPanel.handleMessage(message) to receive message events like onClick and onDblClick
//console.log(JSON.stringify(webpanel, undefined, 2));
callback: (panel: any /* PreviewPanel */) => {
// we have to switch back to the original editor group to prevent issues
const webpanel: vs.WebviewPanel = panel.panel;
const disposable = webpanel.onDidChangeViewState(e => {
if (activeEditor)
vs.window.showTextDocument(activeEditor.document, activeEditor.viewColumn, false);
disposable.dispose();
});
// handle user closing the graphviz preview
webpanel.onDidDispose(e => {
this.active = false;
// there's no way to close a document, only this
// FIXME: if the editor is not in the active view column, this opens a new one and closes it
vs.window.showTextDocument(this.dotDocument!, vs.ViewColumn.Active, false)
.then(() => {
return vs.commands.executeCommand('workbench.action.closeActiveEditor');
});
this.graph.clear();
this.onDidChangeEmitter.fire(this.dotDocument!.uri);
this.dotDocument = undefined; // FIXME: does not delete the document
});
},
allowMultiplePanels: false,
title: 'Call Graph',
Expand All @@ -43,11 +50,24 @@ export class CodeMapProvider implements vs.DebugAdapterTracker, vs.TextDocumentC
vs.commands.executeCommand("graphviz-interactive-preview.preview.beside", args);
}

onWillStopSession() {
this.dotDocument = undefined;
this.graph.clear();
/** @override TextDocumentContentProvider */
provideTextDocumentContent(uri: vs.Uri, token: vs.CancellationToken): vs.ProviderResult<string> {
// here we update the source .dot document
if (uri.path != 'callgraph.dot')
return;
return gv.toDot(this.graph);
}

// https://code.visualstudio.com/api/extension-guides/virtual-documents#update-virtual-documents
onDidChangeEmitter = new vs.EventEmitter<vs.Uri>();
onDidChange = this.onDidChangeEmitter.event; // without this the emitter silently doesn't work

/** @override DebugAdapterTracker */
// onWillStartSession() {}

/** @override DebugAdapterTracker */
// onWillStopSession() {}

private getOrCreateNode(name: string) {
return this.graph.getNode(name) ?? this.graph.createNode(name, { shape: "box" });
}
Expand All @@ -64,11 +84,17 @@ export class CodeMapProvider implements vs.DebugAdapterTracker, vs.TextDocumentC
if (!r.success || r.body.stackFrames.length < 1)
return;

let lastNode = this.getOrCreateNode(CodeMapProvider.wordwrap(r.body.stackFrames[0].name, 64));
// prevent re-rendering if we're still in the same function
if (lastNode.attributes.get("color"))
return;

// mark the current function with a red border
for (const f of this.graph.nodes)
f.attributes.delete("color");

let lastNode = this.getOrCreateNode(CodeMapProvider.wordwrap(r.body.stackFrames[0].name, 64));
lastNode.attributes.set("color", "red");

// walk up the stack and create nodes/edges
for (let i = 1; i < r.body.stackFrames.length; ++i) {
const nodeName = CodeMapProvider.wordwrap(r.body.stackFrames[i].name, 64);
const node = this.getOrCreateNode(nodeName);
Expand All @@ -82,9 +108,11 @@ export class CodeMapProvider implements vs.DebugAdapterTracker, vs.TextDocumentC
this.onDidChangeEmitter.fire(this.dotDocument!.uri);
}

/** @override */
/** @override DebugAdapterTracker */
onDidSendMessage(msg: dap.ProtocolMessage) {
console.log(`< ${typeof msg} ${JSON.stringify(msg, undefined, 2)}`);
if (!this.active)
return;

if (msg.type !== "response" || (msg as dap.Response).command !== "stackTrace")
return;

Expand Down
9 changes: 8 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,17 @@ export function activate(context: vs.ExtensionContext) {
});
context.subscriptions.push(disposable);

// when the command is run a debug session is already active
// then it'd be too late to register the tracker, so do it eagerly
const codemap = new CodeMapProvider();
disposable = vs.commands.registerCommand("debug-utils.showCodeMap", () => {
codemap.activate();
});
context.subscriptions.push(disposable);

disposable = vs.debug.registerDebugAdapterTrackerFactory("*", {
createDebugAdapterTracker(_session: vs.DebugSession) {
return codemap;
return codemap; // null is also possible
}
});
context.subscriptions.push(disposable);
Expand Down

0 comments on commit 55adf3b

Please sign in to comment.