Skip to content

Commit 50d71d1

Browse files
abentkampjoneugster
authored andcommitted
feat: [lean4web] abstract the implementation of the infoview as a webview
1 parent 9b2d75d commit 50d71d1

3 files changed

Lines changed: 117 additions & 73 deletions

File tree

vscode-lean4/src/extension.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { checkAll, SetupDiagnostics } from './diagnostics/setupDiagnostics'
99
import { PreconditionCheckResult, SetupNotificationOptions } from './diagnostics/setupNotifs'
1010
import { AlwaysEnabledFeatures, Exports, Lean4EnabledFeatures } from './exports'
1111
import { InfoProvider } from './infoview'
12+
import { VSCodeInfoWebviewFactory } from './infowebview'
1213
import { LeanClient } from './leanclient'
1314
import { LoogleView } from './loogleview'
1415
import { ManualView } from './manualview'
@@ -195,7 +196,7 @@ async function activateLean4Features(
195196
watchService.lakeFileChanged(packageUri => installer.handleLakeFileChanged(packageUri))
196197
context.subscriptions.push(watchService)
197198

198-
const infoProvider = new InfoProvider(clientProvider, context)
199+
const infoProvider = new InfoProvider(clientProvider, { language: 'lean4' }, context, new VSCodeInfoWebviewFactory(context))
199200
context.subscriptions.push(infoProvider)
200201

201202
context.subscriptions.push(new LeanTaskGutter(clientProvider, context))

vscode-lean4/src/infoview.ts

Lines changed: 18 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@ import {
99
ServerStoppedReason,
1010
TextInsertKind,
1111
} from '@leanprover/infoview-api'
12-
import { join } from 'path'
1312
import {
1413
commands,
1514
Diagnostic,
1615
Disposable,
1716
env,
17+
Event,
1818
ExtensionContext,
1919
Position,
2020
Range,
2121
Selection,
2222
TextEditor,
2323
TextEditorRevealType,
2424
Uri,
25-
WebviewPanel,
25+
ViewColumn,
2626
window,
2727
workspace,
2828
} from 'vscode'
@@ -40,8 +40,6 @@ import {
4040
getInfoViewShowGoalNames,
4141
getInfoViewShowTooltipOnHover,
4242
getInfoViewStyle,
43-
minIfProd,
44-
prodOrDev,
4543
} from './config'
4644
import { LeanClient } from './leanclient'
4745
import { Rpc } from './rpc'
@@ -53,6 +51,19 @@ import { logger } from './utils/logger'
5351
import { displayNotification } from './utils/notifs'
5452
import { viewColumnOfActiveTextEditor, viewColumnOfInfoView } from './utils/viewColumn'
5553

54+
export interface InfoWebview {
55+
readonly api: InfoviewApi
56+
readonly rpc: Rpc
57+
readonly visible: boolean
58+
dispose(): any
59+
reveal(viewColumn?: ViewColumn, preserveFocus?: boolean): void
60+
onDidDispose: Event<void>
61+
}
62+
63+
export interface InfoWebviewFactory {
64+
make(editorApi: EditorApi, stylesheet: string, column: number): InfoWebview
65+
}
66+
5667
const keepAlivePeriodMs = 10000
5768

5869
async function rpcConnect(client: LeanClient, uri: ls.DocumentUri): Promise<string> {
@@ -90,7 +101,7 @@ class RpcSessionAtPos implements Disposable {
90101

91102
export class InfoProvider implements Disposable {
92103
/** Instance of the panel, if it is open. Otherwise `undefined`. */
93-
private webviewPanel?: WebviewPanel & { rpc: Rpc; api: InfoviewApi }
104+
private webviewPanel?: InfoWebview
94105
private subscriptions: Disposable[] = []
95106
private clientSubscriptions: Disposable[] = []
96107

@@ -320,6 +331,7 @@ export class InfoProvider implements Disposable {
320331
constructor(
321332
private clientProvider: LeanClientProvider,
322333
private context: ExtensionContext,
334+
private infoWebviewFactory: InfoWebviewFactory,
323335
) {
324336
this.updateStylesheet()
325337

@@ -582,48 +594,13 @@ export class InfoProvider implements Disposable {
582594
if (this.webviewPanel) {
583595
this.webviewPanel.reveal(undefined, true)
584596
} else {
585-
const webviewPanel = window.createWebviewPanel(
586-
'lean4_infoview',
587-
'Lean Infoview',
588-
{ viewColumn: viewColumnOfInfoView(), preserveFocus: true },
589-
{
590-
enableFindWidget: true,
591-
retainContextWhenHidden: true,
592-
enableScripts: true,
593-
enableCommandUris: true,
594-
},
595-
) as WebviewPanel & { rpc: Rpc; api: InfoviewApi }
596-
597-
// Note that an extension can send data to its webviews using webview.postMessage().
598-
// This method sends any JSON serializable data to the webview. The message is received
599-
// inside the webview through the standard message event.
600-
// The receiving of these messages is done inside webview\index.ts where it
601-
// calls window.addEventListener('message',...
602-
webviewPanel.rpc = new Rpc(m => {
603-
try {
604-
void webviewPanel.webview.postMessage(m)
605-
} catch (e) {
606-
// ignore any disposed object exceptions
607-
}
608-
})
609-
webviewPanel.rpc.register(this.editorApi)
610-
611-
// Similarly, we can received data from the webview by listening to onDidReceiveMessage.
612-
webviewPanel.webview.onDidReceiveMessage(m => {
613-
try {
614-
webviewPanel.rpc.messageReceived(m)
615-
} catch {
616-
// ignore any disposed object exceptions
617-
}
618-
})
619-
webviewPanel.api = webviewPanel.rpc.getApi()
597+
const webviewPanel = this.infoWebviewFactory.make(this.editorApi, this.stylesheet)
620598
webviewPanel.onDidDispose(() => {
621599
this.webviewPanel = undefined
622600
this.clearNotificationHandlers()
623601
this.clearRpcSessions(null) // should be after `webviewPanel = undefined`
624602
})
625603
this.webviewPanel = webviewPanel
626-
webviewPanel.webview.html = this.initialHtml()
627604

628605
const client = this.clientProvider.findClient(leanEditor.documentExtUri)
629606
await this.initInfoView(leanEditor, client)
@@ -819,35 +796,4 @@ export class InfoProvider implements Disposable {
819796
// ensure the text document has the keyboard focus.
820797
await window.showTextDocument(editor.document, { viewColumn: editor.viewColumn, preserveFocus: false })
821798
}
822-
823-
private getLocalPath(path: string): string | undefined {
824-
if (this.webviewPanel) {
825-
return this.webviewPanel.webview.asWebviewUri(Uri.file(join(this.context.extensionPath, path))).toString()
826-
}
827-
return undefined
828-
}
829-
830-
private initialHtml() {
831-
const libPostfix = `.${prodOrDev}${minIfProd}.js`
832-
return `
833-
<!DOCTYPE html>
834-
<html>
835-
<head>
836-
<meta charset="UTF-8" />
837-
<meta http-equiv="Content-type" content="text/html;charset=utf-8">
838-
<title>Infoview</title>
839-
<style>${this.stylesheet}</style>
840-
<link rel="stylesheet" href="${this.getLocalPath('dist/lean4-infoview/index.css')}">
841-
</head>
842-
<body>
843-
<div id="react_root"></div>
844-
<script
845-
data-importmap-leanprover-infoview="${this.getLocalPath(`dist/lean4-infoview/index${libPostfix}`)}"
846-
data-importmap-react="${this.getLocalPath(`dist/lean4-infoview/react${libPostfix}`)}"
847-
data-importmap-react-jsx-runtime="${this.getLocalPath(`dist/lean4-infoview/react-jsx-runtime${libPostfix}`)}"
848-
data-importmap-react-dom="${this.getLocalPath(`dist/lean4-infoview/react-dom${libPostfix}`)}"
849-
src="${this.getLocalPath('dist/webview.js')}"></script>
850-
</body>
851-
</html>`
852-
}
853799
}

vscode-lean4/src/infowebview.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { EditorApi, InfoviewApi } from '@leanprover/infoview-api'
2+
import { join } from 'path'
3+
import { ExtensionContext, Uri, ViewColumn, WebviewPanel, window } from 'vscode'
4+
import { minIfProd, prodOrDev } from './config'
5+
import { InfoWebviewFactory } from './infoview'
6+
import { Rpc } from './rpc'
7+
import { viewColumnOfInfoView } from './utils/viewColumn'
8+
9+
10+
export class VSCodeInfoWebviewFactory implements InfoWebviewFactory {
11+
constructor(private context: ExtensionContext) {}
12+
13+
make(editorApi: EditorApi, stylesheet: string) {
14+
const webviewPanel = window.createWebviewPanel(
15+
'lean4_infoview',
16+
'Lean Infoview',
17+
{ viewColumn: viewColumnOfInfoView(), preserveFocus: true },
18+
{
19+
enableFindWidget: true,
20+
retainContextWhenHidden: true,
21+
enableScripts: true,
22+
enableCommandUris: true,
23+
},
24+
)
25+
26+
// Note that an extension can send data to its webviews using webview.postMessage().
27+
// This method sends any JSON serializable data to the webview. The message is received
28+
// inside the webview through the standard message event.
29+
// The receiving of these messages is done inside webview\index.ts where it
30+
// calls window.addEventListener('message',...
31+
const rpc = new Rpc(m => {
32+
try {
33+
void webviewPanel.webview.postMessage(m)
34+
} catch (e) {
35+
// ignore any disposed object exceptions
36+
}
37+
})
38+
rpc.register(editorApi)
39+
40+
// Similarly, we can received data from the webview by listening to onDidReceiveMessage.
41+
webviewPanel.webview.onDidReceiveMessage(m => {
42+
try {
43+
rpc.messageReceived(m)
44+
} catch {
45+
// ignore any disposed object exceptions
46+
}
47+
})
48+
const api = rpc.getApi<InfoviewApi>()
49+
webviewPanel.webview.html = this.initialHtml(webviewPanel, stylesheet)
50+
51+
return {
52+
api,
53+
rpc,
54+
get visible() {
55+
return webviewPanel.visible
56+
},
57+
dispose: () => {
58+
webviewPanel.dispose()
59+
},
60+
reveal: (viewColumn?: ViewColumn, preserveFocus?: boolean) => {
61+
webviewPanel.reveal(viewColumn, preserveFocus)
62+
},
63+
onDidDispose: webviewPanel.onDidDispose,
64+
}
65+
}
66+
67+
private getLocalPath(path: string, webviewPanel: WebviewPanel): string | undefined {
68+
if (webviewPanel) {
69+
return webviewPanel.webview.asWebviewUri(Uri.file(join(this.context.extensionPath, path))).toString()
70+
}
71+
return undefined
72+
}
73+
74+
private initialHtml(webviewPanel: WebviewPanel, stylesheet: string) {
75+
const libPostfix = `.${prodOrDev}${minIfProd}.js`
76+
return `
77+
<!DOCTYPE html>
78+
<html>
79+
<head>
80+
<meta charset="UTF-8" />
81+
<meta http-equiv="Content-type" content="text/html;charset=utf-8">
82+
<title>Infoview</title>
83+
<style>${stylesheet}</style>
84+
<link rel="stylesheet" href="${this.getLocalPath('dist/lean4-infoview/index.css', webviewPanel)}">
85+
</head>
86+
<body>
87+
<div id="react_root"></div>
88+
<script
89+
data-importmap-leanprover-infoview="${this.getLocalPath(`dist/lean4-infoview/index${libPostfix}`, webviewPanel)}"
90+
data-importmap-react="${this.getLocalPath(`dist/lean4-infoview/react${libPostfix}`, webviewPanel)}"
91+
data-importmap-react-jsx-runtime="${this.getLocalPath(`dist/lean4-infoview/react-jsx-runtime${libPostfix}`, webviewPanel)}"
92+
data-importmap-react-dom="${this.getLocalPath(`dist/lean4-infoview/react-dom${libPostfix}`, webviewPanel)}"
93+
src="${this.getLocalPath('dist/webview.js', webviewPanel)}"></script>
94+
</body>
95+
</html>`
96+
}
97+
}

0 commit comments

Comments
 (0)