@@ -13,172 +13,184 @@ import * as viz from 'viz.js';
1313
1414const PuppetNodeGraphToTheSideCommandId : string = 'extension.puppetShowNodeGraphToSide' ;
1515
16- class NodeGraphContentProvider implements vscode . TextDocumentContentProvider {
17- private onDidChangeEvent = new vscode . EventEmitter < vscode . Uri > ( ) ;
18- private waiting : boolean = false ;
16+ class NodeGraphWebViewProvider implements vscode . Disposable {
1917 private connectionManager : IConnectionManager = undefined ;
18+ private docUri : vscode . Uri = undefined ;
19+ private webPanel : vscode . WebviewPanel = undefined ;
20+ private parentFeature : NodeGraphFeature = undefined ;
2021 private shownLanguageServerNotAvailable = false ;
2122
22- constructor ( connectionManager :IConnectionManager ) {
23+ constructor (
24+ documentUri :vscode . Uri ,
25+ connectionManager :IConnectionManager ,
26+ parent : NodeGraphFeature )
27+ {
28+ this . docUri = documentUri ;
2329 this . connectionManager = connectionManager ;
30+ this . parentFeature = parent ;
2431 }
2532
26- public provideTextDocumentContent ( uri : vscode . Uri ) : Thenable < string > {
27- const sourceUri = vscode . Uri . parse ( uri . query ) ;
28-
29- return vscode . workspace . openTextDocument ( sourceUri ) . then ( document => {
30- const initialData = {
31- previewUri : uri . toString ( ) ,
32- source : sourceUri . toString ( )
33- } ;
33+ public isSameUri ( value : vscode . Uri ) : boolean {
34+ return value . toString ( ) === this . docUri . toString ( ) ;
35+ }
3436
35- if ( ( this . connectionManager . status !== ConnectionStatus . RunningLoaded ) && ( this . connectionManager . status !== ConnectionStatus . RunningLoading ) ) {
36- if ( this . shownLanguageServerNotAvailable ) {
37- vscode . window . showInformationMessage ( "The Puppet Node Graph Preview is not available as the Editor Service is not ready" ) ;
38- this . shownLanguageServerNotAvailable = true ;
39- }
40- return "The Puppet Node Graph Preview is not available as the Editor Service is not ready" ;
41- }
37+ public show ( ) : void {
38+ if ( this . webPanel !== undefined ) { return ; }
39+ this . webPanel = vscode . window . createWebviewPanel (
40+ 'nodeGraph' , // Identifies the type of the webview. Used internally
41+ `Node Graph '${ path . basename ( this . docUri . fsPath ) } '` , // Title of the panel displayed to the user
42+ vscode . ViewColumn . Beside , // Editor column to show the new webview panel in.
43+ { enableScripts : true }
44+ ) ;
4245
43- // Use the language server to render the document
44- return this . connectionManager . languageClient
45- . sendRequest ( CompileNodeGraphRequest . type , sourceUri )
46- . then (
47- ( compileResult ) => {
48-
49- var svgContent = '' ;
50- if ( compileResult . dotContent !== null ) {
51- var styling = `
52- bgcolor = "transparent"
53- color = "white"
54- rankdir = "TB"
55- node [ shape="box" penwidth="2" color="#e0e0e0" style="rounded,filled" fontname="Courier New" fillcolor=black, fontcolor="white"]
56- edge [ style="bold" color="#f0f0f0" penwith="2" ]
57-
58- label = ""` ;
59-
60- var graphContent = compileResult . dotContent ;
61- if ( graphContent === undefined ) { graphContent = '' ; }
62- // vis.jz sees backslashes as escape characters, however they are not in the DOT language. Instead
63- // we should escape any backslash coming from a valid DOT file in preparation to be rendered
64- graphContent = graphContent . replace ( / \\ / g, "\\\\" ) ;
65- graphContent = graphContent . replace ( `label = "vscode"` , styling ) ;
66-
67- svgContent = viz ( graphContent , "svg" ) ;
68- }
69-
70- var errorContent = `<div style='font-size: 1.5em'>${ compileResult . error } </div>` ;
71- if ( ( compileResult . error === undefined ) || ( compileResult . error === null ) ) { errorContent = '' ; }
72-
73- if ( reporter ) {
74- reporter . sendTelemetryEvent ( PuppetNodeGraphToTheSideCommandId ) ;
75- }
76-
77- return `
78- ${ errorContent }
79- <div id="graphviz_svg_div">
80- ${ svgContent }
81- </div>` ;
82- } ) ;
46+ this . webPanel . onDidDispose ( ( ) => {
47+ this . parentFeature . onProviderWebPanelDisposed ( this ) ;
8348 } ) ;
49+
50+ this . updateAsync ( ) ;
8451 }
8552
86- get onDidChange ( ) : vscode . Event < vscode . Uri > {
87- return this . onDidChangeEvent . event ;
53+ public async updateAsync ( ) : Promise < void > {
54+ this . webPanel . webview . html = await this . getHTMLContent ( ) ;
8855 }
8956
90- public update ( uri : vscode . Uri ) {
91- if ( ! this . waiting ) {
92- this . waiting = true ;
93- setTimeout ( ( ) => {
94- this . waiting = false ;
95- this . onDidChangeEvent . fire ( uri ) ;
96- } , 300 ) ;
57+ public async getHTMLContent ( ) : Promise < string > {
58+ if ( ( this . connectionManager . status !== ConnectionStatus . RunningLoaded ) && ( this . connectionManager . status !== ConnectionStatus . RunningLoading ) ) {
59+ if ( this . shownLanguageServerNotAvailable ) {
60+ vscode . window . showInformationMessage ( "The Puppet Node Graph Preview is not available as the Editor Service is not ready" ) ;
61+ this . shownLanguageServerNotAvailable = true ;
62+ }
63+ return "The Puppet Node Graph Preview is not available as the Editor Service is not ready" ;
64+ }
65+
66+ // Use the language server to render the document
67+ const requestData = {
68+ external : this . docUri . toString ( )
69+ } ;
70+ return this . connectionManager . languageClient
71+ . sendRequest ( CompileNodeGraphRequest . type , requestData )
72+ . then (
73+ ( compileResult ) => {
74+
75+ var svgContent = '' ;
76+ if ( compileResult . dotContent !== null ) {
77+ var styling = `
78+ bgcolor = "transparent"
79+ color = "white"
80+ rankdir = "TB"
81+ node [ shape="box" penwidth="2" color="#e0e0e0" style="rounded,filled" fontname="Courier New" fillcolor=black, fontcolor="white"]
82+ edge [ style="bold" color="#f0f0f0" penwith="2" ]
83+
84+ label = ""` ;
85+
86+ var graphContent = compileResult . dotContent ;
87+ if ( graphContent === undefined ) { graphContent = '' ; }
88+ // vis.jz sees backslashes as escape characters, however they are not in the DOT language. Instead
89+ // we should escape any backslash coming from a valid DOT file in preparation to be rendered
90+ graphContent = graphContent . replace ( / \\ / g, "\\\\" ) ;
91+ graphContent = graphContent . replace ( `label = "editorservices"` , styling ) ;
92+
93+ svgContent = viz ( graphContent , "svg" ) ;
94+ }
95+
96+ var errorContent = `<div style='font-size: 1.5em'>${ compileResult . error } </div>` ;
97+ if ( ( compileResult . error === undefined ) || ( compileResult . error === null ) ) { errorContent = '' ; }
98+
99+ if ( reporter ) {
100+ reporter . sendTelemetryEvent ( PuppetNodeGraphToTheSideCommandId ) ;
101+ }
102+
103+ const html : string = `<!DOCTYPE html>
104+ <html lang="en">
105+ <head>
106+ <meta charset="UTF-8">
107+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
108+ <style>
109+ g.node path {
110+ fill: var(--vscode-button-background);
111+ stroke: var(--vscode-button-hoverBackground);
112+ }
113+ g.node text {
114+ fill: var(--vscode-button-foreground);
97115 }
116+ g.edge path {
117+ fill: none;
118+ stroke: var(--vscode-foreground);
119+ }
120+ g.edge polygon {
121+ fill: var(--vscode-foreground);
122+ stroke: var(--vscode-foreground);
123+ }
124+ </style>
125+ </head>
126+ <body>
127+ ${ errorContent }
128+ <div id="graphviz_svg_div">
129+ ${ svgContent }
130+ </div>
131+ </body></html>` ;
132+
133+ return html ;
134+ } ) ;
135+ }
136+
137+ public dispose ( ) : any {
138+ this . webPanel . dispose ( ) ;
139+ return undefined ;
98140 }
99141}
100142
101143export class NodeGraphFeature implements IFeature {
102- private provider : NodeGraphContentProvider ;
144+ private acceptedLangId : string = undefined ;
145+ private providers : NodeGraphWebViewProvider [ ] = undefined ;
146+ private connectionManager : IConnectionManager = undefined ;
147+
148+ public onProviderWebPanelDisposed ( provider : NodeGraphWebViewProvider ) : void {
149+ // If the panel gets disposed then the user closed the tab.
150+ // Remove the provider object and dispose of it.
151+ const index = this . providers . indexOf ( provider , 0 ) ;
152+ if ( index > - 1 ) {
153+ this . providers . splice ( index , 1 ) ;
154+ provider . dispose ( ) ;
155+ }
156+ }
103157
104158 constructor (
105159 langID : string ,
106160 connectionManager : IConnectionManager ,
107161 logger : ILogger ,
108162 context : vscode . ExtensionContext
109163 ) {
164+ this . acceptedLangId = langID ;
165+ this . providers = [ ] ;
166+ this . connectionManager = connectionManager ;
167+
110168 context . subscriptions . push ( vscode . commands . registerCommand ( PuppetNodeGraphToTheSideCommandId ,
111- uri => this . showNodeGraph ( uri , true ) )
112- ) ;
169+ ( ) => {
170+ if ( ! vscode . window . activeTextEditor ) { return ; }
171+ if ( vscode . window . activeTextEditor . document . languageId !== this . acceptedLangId ) { return ; }
172+
173+ let resource = vscode . window . activeTextEditor . document . uri ;
174+ let provider = new NodeGraphWebViewProvider ( resource , this . connectionManager , this ) ;
175+ this . providers . push ( provider ) ;
176+ provider . show ( ) ;
177+ }
178+ ) ) ;
113179 logger . debug ( "Registered " + PuppetNodeGraphToTheSideCommandId + " command" ) ;
114180
115- this . provider = new NodeGraphContentProvider ( connectionManager ) ;
116- vscode . workspace . registerTextDocumentContentProvider ( langID , this . provider ) ;
117- logger . debug ( "Registered Node Graph Text Document provider" ) ;
118-
181+ // Subscribe to save events and fire updates
119182 context . subscriptions . push ( vscode . workspace . onDidSaveTextDocument ( document => {
120- if ( this . isNodeGraphFile ( document ) ) {
121- const uri = this . getNodeGraphUri ( document . uri ) ;
122- this . provider . update ( uri ) ;
123- }
183+ this . providers . forEach ( ( item ) => {
184+ if ( item . isSameUri ( document . uri ) ) { item . updateAsync ( ) ; }
185+ } ) ;
124186 } ) ) ;
125187 logger . debug ( "Registered onDidSaveTextDocument for node graph event handler" ) ;
126188 }
127189
128- private isNodeGraphFile ( document : vscode . TextDocument ) {
129- return document . languageId === 'puppet'
130- && document . uri . scheme !== 'puppet' ; // prevent processing of own documents
131- }
132-
133- private getNodeGraphUri ( uri : vscode . Uri ) {
134- if ( uri . scheme === 'puppet' ) {
135- return uri ;
136- }
137-
138- return uri . with ( {
139- scheme : 'puppet' ,
140- path : uri . fsPath + '.rendered' ,
141- query : uri . toString ( )
142- } ) ;
143- }
144-
145- private getViewColumn ( sideBySide : boolean ) : vscode . ViewColumn | undefined {
146- const active = vscode . window . activeTextEditor ;
147- if ( ! active ) {
148- return vscode . ViewColumn . One ;
149- }
150-
151- if ( ! sideBySide ) {
152- return active . viewColumn ;
153- }
154-
155- switch ( active . viewColumn ) {
156- case vscode . ViewColumn . One :
157- return vscode . ViewColumn . Two ;
158- case vscode . ViewColumn . Two :
159- return vscode . ViewColumn . Three ;
160- }
161-
162- return active . viewColumn ;
163- }
164-
165- private showNodeGraph ( uri ?: vscode . Uri , sideBySide : boolean = false ) {
166- let resource = uri ;
167- if ( ! ( resource instanceof vscode . Uri ) ) {
168- if ( vscode . window . activeTextEditor ) {
169- // we are relaxed and don't check for puppet files
170- // TODO: Should we? Probably
171- resource = vscode . window . activeTextEditor . document . uri ;
172- }
173- }
174-
175- const thenable = vscode . commands . executeCommand ( 'vscode.previewHtml' ,
176- this . getNodeGraphUri ( resource ) ,
177- this . getViewColumn ( sideBySide ) ,
178- `Node Graph '${ path . basename ( resource . fsPath ) } '` ) ;
179-
180- return thenable ;
190+ public dispose ( ) : any {
191+ // Dispose of any providers and then clear any references to them
192+ this . providers . forEach ( ( item ) => { item . dispose ( ) ; } ) ;
193+ this . providers = [ ] ;
194+ return undefined ;
181195 }
182-
183- public dispose ( ) : any { return undefined ; }
184196}
0 commit comments