@@ -6,35 +6,42 @@ import * as gv from "ts-graphviz";
66export 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
0 commit comments