Skip to content

Commit 25239e4

Browse files
committed
(GH-422) Move from previewHTML to WebView API
Previously the Node Graph Feature use the previewHTML API within VSCode, however this being actively deprecated by the VSCode team. This commit changes the Node Graph preview to use the new WebView API [1] by creating a static HTML rendered. Later commits will then call the Language Server for the manifest content and generate the required HTML. Note that the NodeGraphFeature must track all created WebView Panels as they do not auto dispose. Also if a user closes a WebView Panel it disposes of the panel object which means the parent NodeGraphProvider object must also be disposed of. This commit uses a simple array to track all created providers and hooks into lifecycle events to properly manage the objects. [1] https://code.visualstudio.com/docs/extensions/webview
1 parent f236077 commit 25239e4

File tree

1 file changed

+72
-141
lines changed

1 file changed

+72
-141
lines changed

src/feature/NodeGraphFeature.ts

Lines changed: 72 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -6,179 +6,110 @@ import * as path from 'path';
66
import { IFeature } from "../feature";
77
import { ILogger } from "../logging";
88
import { IConnectionManager } from '../connection';
9-
import { ConnectionStatus } from '../interfaces';
10-
import { CompileNodeGraphRequest } from '../messages';
11-
import { reporter } from '../telemetry/telemetry';
12-
import * as viz from 'viz.js';
139

1410
const PuppetNodeGraphToTheSideCommandId: string = 'extension.puppetShowNodeGraphToSide';
1511

16-
class NodeGraphContentProvider implements vscode.TextDocumentContentProvider {
17-
private onDidChangeEvent = new vscode.EventEmitter<vscode.Uri>();
18-
private waiting: boolean = false;
12+
class NodeGraphWebViewProvider implements vscode.Disposable {
1913
private connectionManager: IConnectionManager = undefined;
20-
private shownLanguageServerNotAvailable = false;
14+
private docUri: vscode.Uri = undefined;
15+
private webPanel: vscode.WebviewPanel = undefined;
16+
private parentFeature: NodeGraphFeature = undefined;
2117

22-
constructor(connectionManager:IConnectionManager) {
18+
constructor(
19+
documentUri:vscode.Uri,
20+
connectionManager:IConnectionManager,
21+
parent: NodeGraphFeature)
22+
{
23+
this.docUri = documentUri;
2324
this.connectionManager = connectionManager;
25+
this.parentFeature = parent;
2426
}
2527

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-
};
34-
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-
}
28+
public isSameUri(value: vscode.Uri): boolean {
29+
return value.toString() === this.docUri.toString();
30+
}
4231

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-
});
32+
public show(): void {
33+
if (this.webPanel !== undefined) { return; }
34+
this.webPanel = vscode.window.createWebviewPanel(
35+
'nodeGraph', // Identifies the type of the webview. Used internally
36+
`Node Graph '${path.basename(this.docUri.fsPath)}'`, // Title of the panel displayed to the user
37+
vscode.ViewColumn.Beside, // Editor column to show the new webview panel in.
38+
{ }
39+
);
40+
41+
this.webPanel.onDidDispose( () => {
42+
this.parentFeature.onProviderWebPanelDisposed(this);
8343
});
44+
45+
this.update();
8446
}
8547

86-
get onDidChange(): vscode.Event<vscode.Uri> {
87-
return this.onDidChangeEvent.event;
48+
public update(): void {
49+
this.webPanel.webview.html = this.getHTMLContent();
8850
}
8951

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);
97-
}
52+
public getHTMLContent(): string {
53+
return '<html><body>Node Graph Preview - ' + (new Date().toUTCString()) + '</body></html>';
54+
}
55+
56+
public dispose(): any {
57+
this.webPanel.dispose();
58+
return undefined;
9859
}
9960
}
10061

10162
export class NodeGraphFeature implements IFeature {
102-
private provider: NodeGraphContentProvider;
63+
private acceptedLangId: string = undefined;
64+
private providers: NodeGraphWebViewProvider[] = undefined;
65+
private connectionManager: IConnectionManager = undefined;
66+
67+
public onProviderWebPanelDisposed(provider: NodeGraphWebViewProvider): void {
68+
// If the panel gets disposed then the user closed the tab.
69+
// Remove the provider object and dispose of it.
70+
const index = this.providers.indexOf(provider, 0);
71+
if (index > -1) {
72+
this.providers.splice(index, 1);
73+
provider.dispose();
74+
}
75+
}
10376

10477
constructor(
10578
langID: string,
10679
connectionManager: IConnectionManager,
10780
logger: ILogger,
10881
context: vscode.ExtensionContext
10982
) {
83+
this.acceptedLangId = langID;
84+
this.providers = [];
85+
this.connectionManager = connectionManager;
86+
11087
context.subscriptions.push(vscode.commands.registerCommand(PuppetNodeGraphToTheSideCommandId,
111-
uri => this.showNodeGraph(uri, true))
112-
);
88+
() => {
89+
if (!vscode.window.activeTextEditor) { return; }
90+
if (vscode.window.activeTextEditor.document.languageId !== this.acceptedLangId) { return; }
91+
92+
let resource = vscode.window.activeTextEditor.document.uri;
93+
let provider = new NodeGraphWebViewProvider(resource, this.connectionManager, this);
94+
this.providers.push(provider);
95+
provider.show();
96+
}
97+
));
11398
logger.debug("Registered " + PuppetNodeGraphToTheSideCommandId + " command");
11499

115-
this.provider = new NodeGraphContentProvider(connectionManager);
116-
vscode.workspace.registerTextDocumentContentProvider(langID, this.provider);
117-
logger.debug("Registered Node Graph Text Document provider");
118-
100+
// Subscribe to save events and fire updates
119101
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-
}
102+
this.providers.forEach( (item) => {
103+
if (item.isSameUri(document.uri)) { item.update(); }
104+
});
124105
}));
125106
logger.debug("Registered onDidSaveTextDocument for node graph event handler");
126107
}
127108

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;
109+
public dispose(): any {
110+
// Dispose of any providers and then clear any references to them
111+
this.providers.forEach( (item) => { item.dispose(); });
112+
this.providers = [];
113+
return undefined;
181114
}
182-
183-
public dispose(): any { return undefined; }
184115
}

0 commit comments

Comments
 (0)