Skip to content

Commit aa5a201

Browse files
author
James Pogran
authored
Merge pull request #426 from glennsarti/gh-422-update-webview
(GH-422) Move from preivewHTML to Web ViewAPI
2 parents 3778693 + 6707729 commit aa5a201

File tree

1 file changed

+146
-134
lines changed

1 file changed

+146
-134
lines changed

src/feature/NodeGraphFeature.ts

Lines changed: 146 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -13,172 +13,184 @@ import * as viz from 'viz.js';
1313

1414
const 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

101143
export 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

Comments
 (0)