Skip to content

Commit 55adf3b

Browse files
committed
introduce a command for the code map
fix the issues with duplicate editors being opened optimize it to do less rendering
1 parent 9520bda commit 55adf3b

File tree

3 files changed

+73
-31
lines changed

3 files changed

+73
-31
lines changed

package.json

+7
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@
8484
"highContrast": "#99999999"
8585
}
8686
}
87+
],
88+
"commands": [
89+
{
90+
"command": "debug-utils.showCodeMap",
91+
"title": "Build a Code Map during debugging",
92+
"when": "inDebugMode"
93+
}
8794
]
8895
}
8996
}

src/codemap.ts

+58-30
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,42 @@ import * as gv from "ts-graphviz";
66
export class CodeMapProvider implements vs.DebugAdapterTracker, vs.TextDocumentContentProvider {
77
dotDocument?: vs.TextDocument;
88
graph: gv.Digraph;
9+
active = false;
910

10-
constructor() {
11-
this.graph = gv.digraph('Call Graph', { splines: true });
12-
}
13-
14-
/** @override */
15-
provideTextDocumentContent(uri: vs.Uri, token: vs.CancellationToken): vs.ProviderResult<string> {
16-
// here we update the source .dot document
17-
// TODO: actually check uri
18-
return gv.toDot(this.graph);
19-
}
20-
21-
// https://code.visualstudio.com/api/extension-guides/virtual-documents#update-virtual-documents
22-
onDidChangeEmitter = new vs.EventEmitter<vs.Uri>();
23-
onDidChange = this.onDidChangeEmitter.event; // without this the emitter silently doesn't work
24-
25-
async onWillStartSession() {
26-
this.dotDocument = await vs.workspace.openTextDocument(vs.Uri.parse('dot:1.dot', true));
11+
async activate() : Promise<void> {
2712
this.graph = gv.digraph('Call Graph', { splines: true }); // reset the graph
2813

29-
// used for debugging
30-
vs.window.showTextDocument(this.dotDocument, { preview: false });
14+
this.dotDocument = await vs.workspace.openTextDocument(vs.Uri.parse('dot:callgraph.dot', true));
15+
this.active = true;
16+
17+
// save the current editor
18+
const activeEditor = vs.window.activeTextEditor;
19+
// show the `dot` source
20+
vs.window.showTextDocument(this.dotDocument, vs.ViewColumn.Beside, true);
3121

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

46-
onWillStopSession() {
47-
this.dotDocument = undefined;
48-
this.graph.clear();
53+
/** @override TextDocumentContentProvider */
54+
provideTextDocumentContent(uri: vs.Uri, token: vs.CancellationToken): vs.ProviderResult<string> {
55+
// here we update the source .dot document
56+
if (uri.path != 'callgraph.dot')
57+
return;
58+
return gv.toDot(this.graph);
4959
}
5060

61+
// https://code.visualstudio.com/api/extension-guides/virtual-documents#update-virtual-documents
62+
onDidChangeEmitter = new vs.EventEmitter<vs.Uri>();
63+
onDidChange = this.onDidChangeEmitter.event; // without this the emitter silently doesn't work
64+
65+
/** @override DebugAdapterTracker */
66+
// onWillStartSession() {}
67+
68+
/** @override DebugAdapterTracker */
69+
// onWillStopSession() {}
70+
5171
private getOrCreateNode(name: string) {
5272
return this.graph.getNode(name) ?? this.graph.createNode(name, { shape: "box" });
5373
}
@@ -64,11 +84,17 @@ export class CodeMapProvider implements vs.DebugAdapterTracker, vs.TextDocumentC
6484
if (!r.success || r.body.stackFrames.length < 1)
6585
return;
6686

87+
let lastNode = this.getOrCreateNode(CodeMapProvider.wordwrap(r.body.stackFrames[0].name, 64));
88+
// prevent re-rendering if we're still in the same function
89+
if (lastNode.attributes.get("color"))
90+
return;
91+
92+
// mark the current function with a red border
6793
for (const f of this.graph.nodes)
6894
f.attributes.delete("color");
69-
70-
let lastNode = this.getOrCreateNode(CodeMapProvider.wordwrap(r.body.stackFrames[0].name, 64));
7195
lastNode.attributes.set("color", "red");
96+
97+
// walk up the stack and create nodes/edges
7298
for (let i = 1; i < r.body.stackFrames.length; ++i) {
7399
const nodeName = CodeMapProvider.wordwrap(r.body.stackFrames[i].name, 64);
74100
const node = this.getOrCreateNode(nodeName);
@@ -82,9 +108,11 @@ export class CodeMapProvider implements vs.DebugAdapterTracker, vs.TextDocumentC
82108
this.onDidChangeEmitter.fire(this.dotDocument!.uri);
83109
}
84110

85-
/** @override */
111+
/** @override DebugAdapterTracker */
86112
onDidSendMessage(msg: dap.ProtocolMessage) {
87-
console.log(`< ${typeof msg} ${JSON.stringify(msg, undefined, 2)}`);
113+
if (!this.active)
114+
return;
115+
88116
if (msg.type !== "response" || (msg as dap.Response).command !== "stackTrace")
89117
return;
90118

src/extension.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,17 @@ export function activate(context: vs.ExtensionContext) {
1212
});
1313
context.subscriptions.push(disposable);
1414

15+
// when the command is run a debug session is already active
16+
// then it'd be too late to register the tracker, so do it eagerly
1517
const codemap = new CodeMapProvider();
18+
disposable = vs.commands.registerCommand("debug-utils.showCodeMap", () => {
19+
codemap.activate();
20+
});
21+
context.subscriptions.push(disposable);
22+
1623
disposable = vs.debug.registerDebugAdapterTrackerFactory("*", {
1724
createDebugAdapterTracker(_session: vs.DebugSession) {
18-
return codemap;
25+
return codemap; // null is also possible
1926
}
2027
});
2128
context.subscriptions.push(disposable);

0 commit comments

Comments
 (0)