1
+ import * as vs from 'vscode' ;
2
+ import { DebugProtocol as dap } from 'vscode-debugprotocol' ;
3
+ import * as gv from "ts-graphviz" ;
4
+
5
+ export class CodeMapProvider implements vs . DebugAdapterTracker , vs . TextDocumentContentProvider {
6
+ dotDocument ?: vs . TextDocument ;
7
+ graph : gv . Digraph ;
8
+
9
+ constructor ( ) {
10
+ this . graph = gv . digraph ( 'Call Graph' , { splines : true } ) ;
11
+ }
12
+
13
+ provideTextDocumentContent ( _uri : vs . Uri , _token : vs . CancellationToken ) : vs . ProviderResult < string > {
14
+ return gv . toDot ( this . graph ) ;
15
+ }
16
+
17
+ onDidChangeEmitter = new vs . EventEmitter < vs . Uri > ( ) ;
18
+
19
+ async onWillStartSession ( ) {
20
+ this . dotDocument = await vs . workspace . openTextDocument ( vs . Uri . parse ( 'dot:1.dot' , true ) ) ;
21
+ this . graph = gv . digraph ( 'Call Graph' , { splines : true } ) ; // reset the graph
22
+
23
+ // used for debugging
24
+ vs . window . showTextDocument ( this . dotDocument , { preview : false } ) ;
25
+
26
+ const args = {
27
+ document : this . dotDocument ,
28
+ callback : ( webpanel : any ) => {
29
+ // The callback function receives the newly created webPanel.
30
+ // Overload webPanel.handleMessage(message) to receive message events like onClick and onDblClick
31
+ //console.log(JSON.stringify(webpanel, undefined, 2));
32
+ }
33
+ } ;
34
+
35
+ vs . commands . executeCommand ( "graphviz-interactive-preview.preview.beside" , args ) ;
36
+ }
37
+
38
+ onWillStopSession ( ) {
39
+ this . dotDocument = undefined ;
40
+ this . graph . clear ( ) ;
41
+ }
42
+
43
+ private getOrCreateNode ( name : string ) {
44
+ return this . graph . getNode ( name ) ?? this . graph . createNode ( name , { shape : "box" } ) ;
45
+ }
46
+
47
+ private static wordwrap ( str : string , width : number , brk : string = '\n' , cut : boolean = false ) {
48
+ if ( ! str )
49
+ return str ;
50
+
51
+ const regex = '.{1,' + width + '}(\s|$)' + ( cut ? '|.{' + width + '}|.+$' : '|\S+?(\s|$)' ) ;
52
+ return str . match ( RegExp ( regex , 'g' ) ) ! . join ( brk ) ;
53
+ }
54
+
55
+ private async onStackTraceResponse ( r : dap . StackTraceResponse ) {
56
+ if ( ! r . success || r . body . stackFrames . length < 1 )
57
+ return ;
58
+
59
+ for ( const f of this . graph . nodes )
60
+ f . attributes . delete ( "color" ) ;
61
+
62
+ let lastNode = this . getOrCreateNode ( CodeMapProvider . wordwrap ( r . body . stackFrames [ 0 ] . name , 64 ) ) ;
63
+ lastNode . attributes . set ( "color" , "red" ) ;
64
+ for ( let i = 1 ; i < r . body . stackFrames . length ; ++ i ) {
65
+ const nodeName = CodeMapProvider . wordwrap ( r . body . stackFrames [ i ] . name , 64 ) ;
66
+ const node = this . getOrCreateNode ( nodeName ) ;
67
+ if ( ! this . graph . edges . find ( e => {
68
+ return ( e . targets [ 0 ] as gv . INode ) . id === nodeName &&
69
+ ( e . targets [ 1 ] as gv . INode ) . id === lastNode . id ;
70
+ } ) )
71
+ this . graph . createEdge ( [ node , lastNode ] ) ;
72
+ lastNode = node ;
73
+ }
74
+ this . onDidChangeEmitter . fire ( this . dotDocument ! . uri ) ;
75
+ }
76
+
77
+ // @override
78
+ onDidSendMessage ( msg : dap . ProtocolMessage ) {
79
+ console . log ( `< ${ typeof msg } ${ JSON . stringify ( msg , undefined , 2 ) } ` ) ;
80
+ if ( msg . type !== "response" || ( msg as dap . Response ) . command !== "stackTrace" )
81
+ return ;
82
+
83
+ this . onStackTraceResponse ( msg as dap . StackTraceResponse ) ;
84
+ }
85
+ }
0 commit comments