Skip to content

Commit 9d1c700

Browse files
Clean up live preview VS Code event handlers (#1284)
1 parent 5044a3d commit 9d1c700

File tree

2 files changed

+78
-46
lines changed

2 files changed

+78
-46
lines changed

src/documentation/DocumentationManager.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ export class DocumentationManager implements vscode.Disposable {
4242
return false;
4343
}
4444

45-
this.previewEditor = new DocumentationPreviewEditor(this.extension, this.context);
45+
this.previewEditor = await DocumentationPreviewEditor.create(
46+
this.extension,
47+
this.context
48+
);
4649
const subscriptions: vscode.Disposable[] = [
4750
this.previewEditor.onDidUpdateContent(content => {
4851
this.editorUpdatedContentEmitter.fire(content);

src/documentation/DocumentationPreviewEditor.ts

Lines changed: 74 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -22,69 +22,71 @@ import { ConvertDocumentationRequest } from "../sourcekit-lsp/extensions/Convert
2222
export enum PreviewEditorConstant {
2323
VIEW_TYPE = "swift.previewDocumentationEditor",
2424
TITLE = "Preview Swift Documentation",
25+
UNSUPPORTED_EDITOR_ERROR_MESSAGE = "The active text editor does not support Swift Documentation Live Preview",
2526
}
2627

2728
export class DocumentationPreviewEditor implements vscode.Disposable {
28-
private readonly webviewPanel: vscode.WebviewPanel;
29-
private subscriptions: vscode.Disposable[] = [];
30-
31-
private disposeEmitter = new vscode.EventEmitter<void>();
32-
private renderEmitter = new vscode.EventEmitter<void>();
33-
private updateContentEmitter = new vscode.EventEmitter<WebviewContent>();
34-
35-
constructor(
36-
private readonly extension: vscode.ExtensionContext,
37-
private readonly context: WorkspaceContext
38-
) {
39-
const swiftDoccRenderPath = this.extension.asAbsolutePath(
29+
static async create(
30+
extension: vscode.ExtensionContext,
31+
context: WorkspaceContext
32+
): Promise<DocumentationPreviewEditor> {
33+
const swiftDoccRenderPath = extension.asAbsolutePath(
4034
path.join("assets", "swift-docc-render")
4135
);
42-
// Create and hook up events for the WebviewPanel
43-
this.webviewPanel = vscode.window.createWebviewPanel(
36+
const webviewPanel = vscode.window.createWebviewPanel(
4437
PreviewEditorConstant.VIEW_TYPE,
4538
PreviewEditorConstant.TITLE,
4639
{ viewColumn: vscode.ViewColumn.Beside, preserveFocus: true },
4740
{
4841
enableScripts: true,
4942
localResourceRoots: [
5043
vscode.Uri.file(
51-
this.extension.asAbsolutePath(path.join("assets", "documentation-webview"))
44+
extension.asAbsolutePath(path.join("assets", "documentation-webview"))
5245
),
5346
vscode.Uri.file(swiftDoccRenderPath),
5447
...context.folders.map(f => f.folder),
5548
],
5649
}
5750
);
58-
const webviewBaseURI = this.webviewPanel.webview.asWebviewUri(
51+
const webviewBaseURI = webviewPanel.webview.asWebviewUri(
5952
vscode.Uri.file(swiftDoccRenderPath)
6053
);
61-
const scriptURI = this.webviewPanel.webview.asWebviewUri(
54+
const scriptURI = webviewPanel.webview.asWebviewUri(
6255
vscode.Uri.file(
63-
this.extension.asAbsolutePath(
64-
path.join("assets", "documentation-webview", "index.js")
65-
)
56+
extension.asAbsolutePath(path.join("assets", "documentation-webview", "index.js"))
6657
)
6758
);
68-
fs.readFile(path.join(swiftDoccRenderPath, "index.html"), "utf-8").then(
69-
documentationHTML => {
70-
documentationHTML = documentationHTML
71-
.replaceAll("{{BASE_PATH}}", webviewBaseURI.toString())
72-
.replace("</body>", `<script src="${scriptURI.toString()}"></script></body>`);
73-
this.webviewPanel.webview.html = documentationHTML;
74-
this.subscriptions.push(
75-
this.webviewPanel.webview.onDidReceiveMessage(this.receiveMessage.bind(this)),
76-
vscode.window.onDidChangeActiveTextEditor(editor => {
77-
this.convertDocumentation(editor);
78-
}),
79-
vscode.window.onDidChangeTextEditorSelection(event => {
80-
this.convertDocumentation(event.textEditor);
81-
}),
82-
this.webviewPanel.onDidDispose(this.dispose.bind(this))
83-
);
84-
// Reveal the editor, but don't change the focus of the active text editor
85-
this.webviewPanel.reveal(undefined, true);
86-
}
59+
let doccRenderHTML = await fs.readFile(
60+
path.join(swiftDoccRenderPath, "index.html"),
61+
"utf-8"
62+
);
63+
doccRenderHTML = doccRenderHTML
64+
.replaceAll("{{BASE_PATH}}", webviewBaseURI.toString())
65+
.replace("</body>", `<script src="${scriptURI.toString()}"></script></body>`);
66+
webviewPanel.webview.html = doccRenderHTML;
67+
return new DocumentationPreviewEditor(context, webviewPanel);
68+
}
69+
70+
private activeTextEditor?: vscode.TextEditor;
71+
private subscriptions: vscode.Disposable[] = [];
72+
73+
private disposeEmitter = new vscode.EventEmitter<void>();
74+
private renderEmitter = new vscode.EventEmitter<void>();
75+
private updateContentEmitter = new vscode.EventEmitter<WebviewContent>();
76+
77+
private constructor(
78+
private readonly context: WorkspaceContext,
79+
private readonly webviewPanel: vscode.WebviewPanel
80+
) {
81+
this.activeTextEditor = vscode.window.activeTextEditor;
82+
this.subscriptions.push(
83+
this.webviewPanel.webview.onDidReceiveMessage(this.receiveMessage, this),
84+
vscode.window.onDidChangeActiveTextEditor(this.handleActiveTextEditorChange, this),
85+
vscode.workspace.onDidChangeTextDocument(this.handleDocumentChange, this),
86+
this.webviewPanel.onDidDispose(this.dispose, this)
8787
);
88+
// Reveal the editor, but don't change the focus of the active text editor
89+
webviewPanel.reveal(undefined, true);
8890
}
8991

9092
/** An event that is fired when the Documentation Preview Editor is disposed */
@@ -117,18 +119,45 @@ export class DocumentationPreviewEditor implements vscode.Disposable {
117119
private receiveMessage(message: WebviewMessage) {
118120
switch (message.type) {
119121
case "loaded":
120-
this.convertDocumentation(vscode.window.activeTextEditor);
122+
if (!this.activeTextEditor) {
123+
break;
124+
}
125+
this.convertDocumentation(this.activeTextEditor);
121126
break;
122127
case "rendered":
123128
this.renderEmitter.fire();
124129
break;
125130
}
126131
}
127132

128-
private async convertDocumentation(editor: vscode.TextEditor | undefined): Promise<void> {
129-
const document = editor?.document;
130-
if (!document || document.uri.scheme !== "file") {
131-
return undefined;
133+
private handleActiveTextEditorChange(activeTextEditor: vscode.TextEditor | undefined) {
134+
if (this.activeTextEditor === activeTextEditor || activeTextEditor === undefined) {
135+
return;
136+
}
137+
this.activeTextEditor = activeTextEditor;
138+
this.convertDocumentation(activeTextEditor);
139+
}
140+
141+
private handleDocumentChange(event: vscode.TextDocumentChangeEvent) {
142+
if (this.activeTextEditor?.document === event.document) {
143+
this.convertDocumentation(this.activeTextEditor);
144+
}
145+
}
146+
147+
private async convertDocumentation(textEditor: vscode.TextEditor): Promise<void> {
148+
const document = textEditor.document;
149+
if (
150+
document.uri.scheme !== "file" ||
151+
!["markdown", "tutorial", "swift"].includes(document.languageId)
152+
) {
153+
this.postMessage({
154+
type: "update-content",
155+
content: {
156+
type: "error",
157+
errorMessage: PreviewEditorConstant.UNSUPPORTED_EDITOR_ERROR_MESSAGE,
158+
},
159+
});
160+
return;
132161
}
133162

134163
const response = await this.context.languageClientManager.useLanguageClient(
@@ -137,7 +166,7 @@ export class DocumentationPreviewEditor implements vscode.Disposable {
137166
textDocument: {
138167
uri: document.uri.toString(),
139168
},
140-
position: editor.selection.start,
169+
position: textEditor.selection.start,
141170
});
142171
}
143172
);

0 commit comments

Comments
 (0)