diff --git a/.vscode/settings.json b/.vscode/settings.json index 2eb0321..5a75d99 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,14 +1,14 @@ { - "eslint.packageManager": "yarn", "search.exclude": { "**/node_modules": true, "**/lib": true, "**/coverage": true }, "editor.insertSpaces": true, + "files.trimTrailingWhitespace": true, "[typescript]": { "editor.tabSize": 4 }, "typescript.tsdk": "node_modules/typescript/lib", - "files.trimTrailingWhitespace": true, + "typescript.enablePromptUseWorkspaceTsdk": true, } \ No newline at end of file diff --git a/README.md b/README.md index b895a08..a308f56 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/eclipse/sprotty-vscode) +[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/eclipse/sprotty-vscode) # sprotty-vscode @@ -16,7 +16,7 @@ Also contains an example extension for a domain-specific language for statemachi ## Architecture -In VS Code, extensions can contribute new UI components using a webview. Webviews communicate with the extension using the [`postMessage`](https://code.visualstudio.com/api/extension-guides/webview#passing-messages-from-an-extension-to-a-webview) API. The [`WebviewPanelManager`](./packages/sprotty-vscode/src/webview-panel-manager.ts) uses this to send and receive Sprotty Actions to and from a [`WebviewEndpoint`](./packages/sprotty-vscode/src/webview-endpoint.ts). The latter runs a webpacked `bundle.js` that contains the Sprotty diagram code. +In VS Code, extensions can contribute new UI components using a webview. Webviews communicate with the extension using the [`vscode-messenger`](https://github.com/TypeFox/vscode-messenger) library. The [`WebviewPanelManager`](./packages/sprotty-vscode/src/webview-panel-manager.ts) uses this to send and receive Sprotty Actions to and from a [`WebviewEndpoint`](./packages/sprotty-vscode/src/webview-endpoint.ts). The latter runs a webpacked `bundle.js` that contains the Sprotty diagram code. ![Architecture Diagram](images/architecture.png) @@ -25,15 +25,19 @@ If your extension provides a language, you can include a [Sprotty-enhanced langu ## Contents The repo is structured as follows -- `example/`: an example using an Xtext-based Language Server and with Sprotty visualization. -- `sprotty-vscode-extension/`: library code for the VSCode extension. -- `sprotty-vscode-protocol/`: common protocol classes for the communication between the extension and the webview. -- `sprotty-vscode-webview/`: library code for the script that is run in the webview. +- `examples`: an example Sprotty visualization using a [Langium](https://langium.org/)-based Language Server. +- `packages/sprotty-vscode`: library code for the VSCode extension. +- `packages/sprotty-vscode-protocol`: common protocol classes for the communication between the extension and the webview. +- `packages/sprotty-vscode-webview`: library code for the script that is run in the webview. ## Development -Compile the library code and the examples +Compile the library code and the examples: ``` -examples/states-xtext/language-server/gradlew -p examples/states-xtext/language-server/ build && yarn +yarn ``` +If you also want to use the older Xtext-based example, you need to run this command before `yarn`: +``` +./examples/states-xtext/language-server/gradlew -p examples/states-xtext/language-server/ build +``` diff --git a/packages/sprotty-vscode/src/action-handler.ts b/packages/sprotty-vscode/src/action-handler.ts deleted file mode 100644 index 7ce3f1d..0000000 --- a/packages/sprotty-vscode/src/action-handler.ts +++ /dev/null @@ -1,31 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2020 TypeFox and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { Action } from 'sprotty-protocol'; - -/** - * Used to locally intercept and handle actions in the VS Code extension. - * @deprecated - */ -export interface ActionHandler { - readonly kind: string; - - /** - * @returns true when the action should be further progagated, e.g. - * to the language server - */ - handleAction(action: Action): Thenable -} diff --git a/packages/sprotty-vscode/src/index.ts b/packages/sprotty-vscode/src/index.ts index 69d329a..cf3856d 100644 --- a/packages/sprotty-vscode/src/index.ts +++ b/packages/sprotty-vscode/src/index.ts @@ -17,8 +17,6 @@ export * from './default-contributions'; export * from './sprotty-editor-provider'; export * from './sprotty-view-provider'; -export * from './sprotty-vscode-extension'; -export * from './sprotty-webview'; export * from './webview-endpoint'; export * from './webview-panel-manager'; export * from './webview-utils'; diff --git a/packages/sprotty-vscode/src/lsp/editing/index.ts b/packages/sprotty-vscode/src/lsp/editing/index.ts index 7940dfa..099c590 100644 --- a/packages/sprotty-vscode/src/lsp/editing/index.ts +++ b/packages/sprotty-vscode/src/lsp/editing/index.ts @@ -16,4 +16,3 @@ export * from './lsp-label-edit-action-handler'; export * from './workspace-edit-action-handler'; -export * from './sprotty-lsp-edit-vscode-extension'; diff --git a/packages/sprotty-vscode/src/lsp/editing/lsp-label-edit-action-handler.ts b/packages/sprotty-vscode/src/lsp/editing/lsp-label-edit-action-handler.ts index 413c3da..0f3e9c3 100644 --- a/packages/sprotty-vscode/src/lsp/editing/lsp-label-edit-action-handler.ts +++ b/packages/sprotty-vscode/src/lsp/editing/lsp-label-edit-action-handler.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2020 TypeFox and others. + * Copyright (c) 2020-2023 TypeFox and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,16 +14,12 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from 'sprotty-protocol'; import { LspLabelEditAction } from 'sprotty-vscode-protocol/lib/lsp/editing'; import * as vscode from 'vscode'; import { - CompletionItem, CompletionItemKind, CompletionList, CompletionRequest, LanguageClient, - PrepareRenameRequest, RenameParams, RenameRequest, TextDocumentPositionParams + CompletionItem, CompletionItemKind, CompletionRequest, PrepareRenameRequest, RenameParams, + RenameRequest, TextDocumentPositionParams } from 'vscode-languageclient/node'; -import { ActionHandler } from '../../action-handler'; -import { SprottyLspVscodeExtension } from '../sprotty-lsp-vscode-extension'; -import { SprottyWebview } from '../../sprotty-webview'; import { convertPosition, convertUri, convertWorkspaceEdit, convertRange } from '../lsp-utils'; import { LspWebviewEndpoint } from '../lsp-webview-endpoint'; @@ -91,93 +87,3 @@ async function renameElement(action: LspLabelEditAction, endpoint: LspWebviewEnd } } } - -/** - * @deprecated Use `addLspLabelEditActionHandler` instead. - */ -export class LspLabelEditActionHandler implements ActionHandler { - - readonly kind = LspLabelEditAction.KIND; - - constructor(readonly webview: SprottyWebview) { - if (!(webview.extension instanceof SprottyLspVscodeExtension)) - throw new Error('LspLabelEditActionHandler must be initialized with a SprottyLspWebview'); - } - - async handleAction(action: Action): Promise { - if (LspLabelEditAction.is(action)) { - switch (action.editKind) { - case 'xref': return this.chooseCrossReference(action); - case 'name': return this.renameElement(action); - } - } - return false; - } - - protected get languageClient(): LanguageClient { - return (this.webview.extension as SprottyLspVscodeExtension).languageClient; - } - - async chooseCrossReference(action: LspLabelEditAction): Promise { - const completions = await this.languageClient.sendRequest(CompletionRequest.type, { - textDocument: { uri: action.location.uri }, - position: convertPosition(action.location.range.start) - }); - if (completions) { - const completionItems = ((completions as any)["items"]) - ? (completions as CompletionList).items - : completions as CompletionItem[]; - - const quickPickItems = this.filterCompletionItems(completionItems) - .map(completionItem => { - return { - label: completionItem.textEdit!.newText, - value: completionItem - }; - }); - const pick = await vscode.window.showQuickPick(quickPickItems); - if (pick) { - const pickedCompletionItem = (pick as any).value as CompletionItem; - if (pickedCompletionItem.textEdit) { - const workspaceEdit = new vscode.WorkspaceEdit(); - const textEdit = vscode.TextEdit.replace(convertRange(action.location.range), pickedCompletionItem.textEdit.newText); - workspaceEdit.set(convertUri(action.location.uri), [ textEdit ]); - return vscode.workspace.applyEdit(workspaceEdit); - } - } - } - return false; - } - - protected filterCompletionItems(items: CompletionItem[]): CompletionItem[] { - return items.filter(item => item.kind === CompletionItemKind.Reference); - } - - async renameElement(action: LspLabelEditAction): Promise { - const canRename = await this.languageClient.sendRequest(PrepareRenameRequest.type, { - textDocument: { - uri: action.location.uri - }, - position: action.location.range.start - }); - if (canRename) { - const newName = await vscode.window.showInputBox({ - prompt: 'Enter new name', - placeHolder: 'new name', - value: action.initialText - }); - if (newName) { - const workspaceEdit = await this.languageClient.sendRequest(RenameRequest.type, { - textDocument: { - uri: action.location.uri - }, - position: action.location.range.start, - newName - }); - if (workspaceEdit) - return vscode.workspace.applyEdit(convertWorkspaceEdit(workspaceEdit)); - } - } - return false; - } -} diff --git a/packages/sprotty-vscode/src/lsp/editing/sprotty-lsp-edit-vscode-extension.ts b/packages/sprotty-vscode/src/lsp/editing/sprotty-lsp-edit-vscode-extension.ts deleted file mode 100644 index aebfd57..0000000 --- a/packages/sprotty-vscode/src/lsp/editing/sprotty-lsp-edit-vscode-extension.ts +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2020 TypeFox and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import * as vscode from 'vscode'; -import { SprottyLspVscodeExtension } from '../sprotty-lsp-vscode-extension'; -import { DeleteWithWorkspaceEditAction } from 'sprotty-vscode-protocol/lib/lsp/editing'; - -/** - * @deprecated Use `LspWebviewPanelManager` and `registerLspEditCommands` instead. - */ -export abstract class SprottyLspEditVscodeExtension extends SprottyLspVscodeExtension { - protected override registerCommands() { - super.registerCommands(); - this.context.subscriptions.push( - vscode.commands.registerCommand(this.extensionPrefix + '.diagram.delete', (...commandArgs: any) => { - const activeWebview = this.findActiveWebview(); - if (activeWebview) - activeWebview.dispatch({ - kind: DeleteWithWorkspaceEditAction.KIND - }); - })); - } -} diff --git a/packages/sprotty-vscode/src/lsp/editing/workspace-edit-action-handler.ts b/packages/sprotty-vscode/src/lsp/editing/workspace-edit-action-handler.ts index 3c4299b..6f94f36 100644 --- a/packages/sprotty-vscode/src/lsp/editing/workspace-edit-action-handler.ts +++ b/packages/sprotty-vscode/src/lsp/editing/workspace-edit-action-handler.ts @@ -14,10 +14,8 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { Action } from 'sprotty-protocol'; import { WorkspaceEditAction } from 'sprotty-vscode-protocol/lib/lsp/editing'; import * as vscode from 'vscode'; -import { ActionHandler } from '../../action-handler'; import { convertWorkspaceEdit } from '../lsp-utils'; import { LspWebviewEndpoint } from '../lsp-webview-endpoint'; @@ -27,16 +25,3 @@ export function addWorkspaceEditActionHandler(endpoint: LspWebviewEndpoint): voi }; endpoint.addActionHandler(WorkspaceEditAction.KIND, handler); }; - -/** - * @deprecated Use `addWorkspaceEditActionHandler` instead. - */ -export class WorkspaceEditActionHandler implements ActionHandler { - readonly kind = WorkspaceEditAction.KIND; - - async handleAction(action: Action): Promise { - if (WorkspaceEditAction.is(action)) - await vscode.workspace.applyEdit(convertWorkspaceEdit(action.workspaceEdit)); - return false; - } -} diff --git a/packages/sprotty-vscode/src/lsp/index.ts b/packages/sprotty-vscode/src/lsp/index.ts index 952e5ba..46e2df5 100644 --- a/packages/sprotty-vscode/src/lsp/index.ts +++ b/packages/sprotty-vscode/src/lsp/index.ts @@ -20,5 +20,3 @@ export * from './lsp-utils'; export * from './lsp-webview-endpoint'; export * from './lsp-webview-panel-manager'; export * from './protocol'; -export * from './sprotty-lsp-webview'; -export * from './sprotty-lsp-vscode-extension'; diff --git a/packages/sprotty-vscode/src/lsp/sprotty-lsp-vscode-extension.ts b/packages/sprotty-vscode/src/lsp/sprotty-lsp-vscode-extension.ts deleted file mode 100644 index 15fa182..0000000 --- a/packages/sprotty-vscode/src/lsp/sprotty-lsp-vscode-extension.ts +++ /dev/null @@ -1,80 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2020 TypeFox and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import * as vscode from 'vscode'; -import { Emitter, LanguageClient } from 'vscode-languageclient/node'; -import { acceptMessageType, didCloseMessageType, OpenInTextEditorMessage, openInTextEditorMessageType } from './protocol'; -import { ActionMessage } from 'sprotty-protocol'; -import { SprottyDiagramIdentifier } from 'sprotty-vscode-protocol'; -import { SprottyVscodeExtension } from '../sprotty-vscode-extension'; - -/** - * @deprecated Use `LspWebviewPanelManager` instead. - */ -export abstract class SprottyLspVscodeExtension extends SprottyVscodeExtension { - readonly languageClient: LanguageClient; - - protected acceptFromLanguageServerEmitter = new Emitter(); - - constructor(extensionPrefix: string, context: vscode.ExtensionContext) { - super(extensionPrefix, context); - this.languageClient = this.activateLanguageClient(context); - this.languageClient.onNotification(acceptMessageType, (message: ActionMessage) => this.acceptFromLanguageServerEmitter.fire(message)); - this.languageClient.onNotification(openInTextEditorMessageType, (message: OpenInTextEditorMessage) => this.openInTextEditor(message)); - } - - onAcceptFromLanguageServer(listener: (message: ActionMessage) => void): vscode.Disposable { - return this.acceptFromLanguageServerEmitter.event(listener); - } - - override didCloseWebview(identifier: SprottyDiagramIdentifier): void { - super.didCloseWebview(identifier); - try { - this.languageClient.sendNotification(didCloseMessageType, identifier.clientId); - } catch (err) { - // Ignore the error and proceed - } - } - - protected abstract activateLanguageClient(context: vscode.ExtensionContext): LanguageClient; - - deactivateLanguageClient(): Thenable { - if (!this.languageClient) { - return Promise.resolve(undefined); - } - return this.languageClient.stop(); - } - - protected openInTextEditor(message: OpenInTextEditorMessage): void { - const editor = vscode.window.visibleTextEditors.find(visibleEditor => visibleEditor.document.uri.toString() === message.location.uri); - if (editor) { - const start = this.toPosition(message.location.range.start); - const end = this.toPosition(message.location.range.end); - editor.selection = new vscode.Selection(start, end); - editor.revealRange(editor.selection, vscode.TextEditorRevealType.InCenter); - } else if (message.forceOpen) { - vscode.window.showTextDocument(vscode.Uri.parse(message.location.uri), { - selection: new vscode.Range(this.toPosition(message.location.range.start), - this.toPosition(message.location.range.end)), - viewColumn: vscode.ViewColumn.One - }); - } - } - - protected toPosition(p: { character: number, line: number }): vscode.Position { - return new vscode.Position(p.line, p.character); - } -} diff --git a/packages/sprotty-vscode/src/lsp/sprotty-lsp-webview.ts b/packages/sprotty-vscode/src/lsp/sprotty-lsp-webview.ts deleted file mode 100644 index 5524dbf..0000000 --- a/packages/sprotty-vscode/src/lsp/sprotty-lsp-webview.ts +++ /dev/null @@ -1,70 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2020 TypeFox and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { isActionMessage } from 'sprotty-protocol'; -import { Message, ResponseMessage } from 'vscode-jsonrpc/lib/common/messages'; -import { LanguageClient } from 'vscode-languageclient/node'; - -import { acceptMessageType } from './protocol'; -import { SprottyLspVscodeExtension } from './sprotty-lsp-vscode-extension'; -import { SprottyWebview, SprottyWebviewOptions } from '../sprotty-webview'; - - -/** - * @deprecated Use `LspWebviewEndpoint` in conjunction with `LspWebviewPanelManager` instead. - */ -export class SprottyLspWebview extends SprottyWebview { - - static override viewCount = 0; - - override readonly extension: SprottyLspVscodeExtension; - - constructor(protected override options: SprottyWebviewOptions) { - super(options); - if (!(options.extension instanceof SprottyLspVscodeExtension)) - throw new Error('SprottyLspWebview must be initialized with a SprottyLspVscodeExtension'); - } - - protected get languageClient(): LanguageClient { - return this.extension.languageClient; - } - - protected override async connect() { - super.connect(); - this.disposables.push(this.extension.onAcceptFromLanguageServer(message => this.sendToWebview(message))); - } - - protected override async receiveFromWebview(message: any): Promise { - const shouldPropagate = await super.receiveFromWebview(message); - if (shouldPropagate) { - if (isActionMessage(message)) { - this.languageClient.sendNotification(acceptMessageType, message); - } else if (Message.isRequest(message)) { - const result = (message.params) - ? await this.languageClient.sendRequest(message.method, message.params) - : await this.languageClient.sendRequest(message.method); - this.sendToWebview( { - jsonrpc: '2.0', - id: message.id, - result: result - }); - } else if (Message.isNotification(message)) { - this.languageClient.sendNotification(message.method, message.params); - } - } - return false; - } -} diff --git a/packages/sprotty-vscode/src/sprotty-vscode-extension.ts b/packages/sprotty-vscode/src/sprotty-vscode-extension.ts deleted file mode 100644 index 1e0fe0d..0000000 --- a/packages/sprotty-vscode/src/sprotty-vscode-extension.ts +++ /dev/null @@ -1,151 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2020 TypeFox and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import * as path from 'path'; -import * as vscode from 'vscode'; -import { Action } from 'sprotty-protocol'; -import { SprottyDiagramIdentifier } from 'sprotty-vscode-protocol'; -import { SprottyWebview } from './sprotty-webview'; -import { serializeUri } from './webview-utils'; -import { Messenger } from 'vscode-messenger'; - -/** - * @deprecated Use `WebviewPanelManager` instead. - */ -export abstract class SprottyVscodeExtension { - - protected readonly webviewMap = new Map(); - protected singleton?: SprottyWebview; - readonly messenger: Messenger; - - constructor(readonly extensionPrefix: string, readonly context: vscode.ExtensionContext) { - this.registerCommands(); - this.messenger = new Messenger(); - } - - protected registerCommands() { - this.context.subscriptions.push( - vscode.commands.registerCommand(this.extensionPrefix + '.diagram.open', async (...commandArgs: any[]) => { - const identifier = await this.createDiagramIdentifier(commandArgs); - if (identifier) { - const key = this.getKey(identifier); - let webView = this.singleton ?? this.webviewMap.get(key); - if (webView) { - webView.reloadContent(identifier); - webView.diagramPanel.reveal(webView.diagramPanel.viewColumn); - } else { - webView = this.createWebView(identifier); - if (webView.singleton) { - this.singleton = webView; - } else { - this.webviewMap.set(key, webView); - } - } - } - })); - this.context.subscriptions.push( - vscode.commands.registerCommand(this.extensionPrefix + '.diagram.fit', () => { - const activeWebview = this.findActiveWebview(); - if (activeWebview) - activeWebview.dispatch({ - kind: 'fit', - elementIds: [], - animate: true - } as Action); - })); - this.context.subscriptions.push( - vscode.commands.registerCommand(this.extensionPrefix + '.diagram.center', () => { - const activeWebview = this.findActiveWebview(); - if (activeWebview) - activeWebview.dispatch({ - kind: 'center', - elementIds: [], - animate: true - } as Action); - })); - this.context.subscriptions.push( - vscode.commands.registerCommand(this.extensionPrefix + '.diagram.export', () => { - const activeWebview = this.findActiveWebview(); - if (activeWebview) - activeWebview.dispatch({ - kind: 'requestExportSvg' - } as Action); - })); - } - - protected findActiveWebview(): SprottyWebview | undefined { - if (this.singleton && this.singleton.diagramPanel.active) { - return this.singleton; - } - for (const webview of this.webviewMap.values()) { - if (webview.diagramPanel.active) { - return webview; - } - } - return undefined; - } - - didCloseWebview(identifier: SprottyDiagramIdentifier): void { - this.webviewMap.delete(this.getKey(identifier)); - if (this.singleton) { - this.singleton = undefined; - } - } - - protected getKey(identifier: SprottyDiagramIdentifier) { - return JSON.stringify({ - diagramType: identifier.diagramType, - uri: identifier.uri - }); - } - - protected abstract createWebView(diagramIdentifier: SprottyDiagramIdentifier): SprottyWebview; - - protected async createDiagramIdentifier(commandArgs: any[]): Promise { - const uri = await this.getURI(commandArgs); - const diagramType = await this.getDiagramType(commandArgs); - if (!uri || !diagramType) - return undefined; - const clientId = diagramType + '_' + SprottyWebview.viewCount++; - return { - diagramType, - uri: serializeUri(uri), - clientId - }; - } - - protected abstract getDiagramType(commandArgs: any[]): Promise | string | undefined; - - getDiagramTypeForUri(uri: vscode.Uri): Promise | string | undefined { - return this.getDiagramType([uri]); - } - - protected async getURI(commandArgs: any[]): Promise { - if (commandArgs.length > 0) { - if (commandArgs[0] instanceof vscode.Uri) { - return commandArgs[0]; - } - } - if (vscode.window.activeTextEditor) - return vscode.window.activeTextEditor.document.uri; - return undefined; - } - - getExtensionFileUri(...segments: string[]): vscode.Uri { - return vscode.Uri - .file(path.join(this.context.extensionPath, ...segments)); - } -} diff --git a/packages/sprotty-vscode/src/sprotty-webview.ts b/packages/sprotty-vscode/src/sprotty-webview.ts deleted file mode 100644 index caa69a7..0000000 --- a/packages/sprotty-vscode/src/sprotty-webview.ts +++ /dev/null @@ -1,229 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2020 TypeFox and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { Action, hasOwnProperty, isActionMessage } from 'sprotty-protocol'; -import { ActionNotification, DiagramIdentifierNotification, SprottyDiagramIdentifier, WebviewReadyMessage, WebviewReadyNotification } from 'sprotty-vscode-protocol'; -import * as vscode from 'vscode'; -import { Messenger } from 'vscode-messenger'; -import { MessageParticipant } from 'vscode-messenger-common'; -import { ActionHandler } from './action-handler'; -import { SprottyVscodeExtension } from './sprotty-vscode-extension'; -import { serializeUri } from './webview-utils'; - -export interface SprottyWebviewOptions { - extension: SprottyVscodeExtension - messenger: Messenger - messageParticipant: MessageParticipant - identifier: SprottyDiagramIdentifier - localResourceRoots: vscode.Uri[] - scriptUri: vscode.Uri - singleton?: boolean -} - -/** - * @deprecated Use `WebviewEndpoint` in conjunction with `WebviewPanelManager` instead. - */ -export class SprottyWebview { - - static viewCount = 0; - - readonly extension: SprottyVscodeExtension; - readonly messenger: Messenger; - readonly messageParticipant: MessageParticipant; - readonly diagramIdentifier: SprottyDiagramIdentifier; - readonly localResourceRoots: vscode.Uri[]; - readonly scriptUri: vscode.Uri; - readonly diagramPanel: vscode.WebviewPanel; - readonly actionHandlers = new Map(); - - protected disposables: vscode.Disposable[] = []; - - private resolveWebviewReady: () => void; - private readonly webviewReady = new Promise((resolve) => this.resolveWebviewReady = resolve); - - constructor(protected options: SprottyWebviewOptions) { - this.extension = options.extension; - this.messenger = options.messenger; - this.messageParticipant = options.messageParticipant; - this.diagramIdentifier = options.identifier; - this.localResourceRoots = options.localResourceRoots; - this.scriptUri = options.scriptUri; - this.diagramPanel = this.createWebviewPanel(); - this.connect(); - } - - get singleton(): boolean { - return !!this.options.singleton; - } - - protected ready(): Promise { - return this.webviewReady; - } - - protected createTitle(): string { - if (this.diagramIdentifier.uri) - return this.diagramIdentifier.uri.substring(this.diagramIdentifier.uri.lastIndexOf('/') + 1); - if (this.diagramIdentifier.diagramType) - return this.diagramIdentifier.diagramType; - else - return 'Diagram'; - } - - protected createWebviewPanel(): vscode.WebviewPanel { - const title = this.createTitle(); - const diagramPanel = vscode.window.createWebviewPanel( - this.diagramIdentifier.diagramType || 'diagram', - title, - vscode.ViewColumn.Beside, - { - localResourceRoots: this.localResourceRoots, - enableScripts: true, - retainContextWhenHidden: true - }); - this.initializeWebview(diagramPanel.webview, title); - return diagramPanel; - } - - protected initializeWebview(webview: vscode.Webview, title?: string) { - webview.html = ` - - - - - - ${title} - - - -
- - - `; - } - - protected async connect() { - this.disposables.push(this.diagramPanel.onDidChangeViewState(event => { - this.setWebviewActiveContext(event.webviewPanel.active); - })); - this.disposables.push(this.diagramPanel.onDidDispose(() => { - this.extension.didCloseWebview(this.diagramIdentifier); - this.disposables.forEach(disposable => disposable.dispose()); - })); - if (this.singleton) { - this.disposables.push(vscode.window.onDidChangeActiveTextEditor(async editor => { - if (editor) { - const uri = editor.document.uri; - const diagramType = await this.extension.getDiagramTypeForUri(uri); - if (diagramType) { - this.reloadContent({ - diagramType, - uri: serializeUri(uri), - clientId: this.diagramIdentifier.clientId - }); - } - } - })); - } - this.messenger.onNotification(ActionNotification, - async message => { - this.receiveFromWebview(message); - }, - { sender: this.messageParticipant } - ); - this.messenger.onNotification(WebviewReadyNotification, - async message => { - this.receiveFromWebview(message); - }, - { sender: this.messageParticipant } - ); - await this.ready(); - } - - async reloadContent(newId: SprottyDiagramIdentifier): Promise { - if (newId.diagramType !== this.diagramIdentifier.diagramType || newId.uri !== this.diagramIdentifier.uri) { - this.diagramIdentifier.diagramType = newId.diagramType; - this.diagramIdentifier.uri = newId.uri; - this.sendDiagramIdentifier(); - this.diagramPanel.title = this.createTitle(); - } - } - - protected setWebviewActiveContext(isActive: boolean) { - vscode.commands.executeCommand('setContext', this.diagramIdentifier.diagramType + '-focused', isActive); - } - - protected async sendDiagramIdentifier() { - await this.ready(); - this.sendToWebview(this.diagramIdentifier); - } - - /** - * @return true if the message should be propagated, e.g. to a language server - */ - protected receiveFromWebview(message: any): Thenable { - if (isActionMessage(message)) - return this.accept(message.action); - else if (isWebviewReadyMessage(message)) { - this.resolveWebviewReady(); - this.sendDiagramIdentifier(); - return Promise.resolve(false); - } - return Promise.resolve(true); - } - - protected sendToWebview(message: any) { - if (isActionMessage(message)) { - const actionHandler = this.actionHandlers.get(message.action.kind); - if (actionHandler && !actionHandler.handleAction(message.action)) - return; - } - if (isActionMessage(message)) { - this.messenger.sendNotification(ActionNotification, this.messageParticipant, message); - } else if (isDiagramIdentifier(message)) { - this.messenger.sendNotification(DiagramIdentifierNotification, this.messageParticipant, this.diagramIdentifier); - } - } - - dispatch(action: Action) { - this.sendToWebview({ - clientId: this.diagramIdentifier.clientId, - action - }); - } - - accept(action: Action): Thenable { - const actionHandler = this.actionHandlers.get(action.kind); - if (actionHandler) - return actionHandler.handleAction(action); - return Promise.resolve(true); - } - - addActionHandler(actionHandlerConstructor: new(webview: SprottyWebview) => ActionHandler) { - const actionHandler = new actionHandlerConstructor(this); - this.actionHandlers.set(actionHandler.kind, actionHandler); - } -} - -export function isWebviewReadyMessage(object: unknown): object is WebviewReadyMessage { - return hasOwnProperty(object, 'readyMessage'); -} - -export function isDiagramIdentifier(object: unknown): object is SprottyDiagramIdentifier { - return hasOwnProperty(object, ['clientId', 'diagramType', 'uri']); -} diff --git a/packages/sprotty-vscode/src/webview-utils.ts b/packages/sprotty-vscode/src/webview-utils.ts index e498f93..baff412 100644 --- a/packages/sprotty-vscode/src/webview-utils.ts +++ b/packages/sprotty-vscode/src/webview-utils.ts @@ -28,7 +28,8 @@ export function serializeUri(uri: vscode.Uri): string { return uriString; } -export function createWebviewPanel(identifier: SprottyDiagramIdentifier, options: { localResourceRoots: vscode.Uri[], scriptUri: vscode.Uri }): vscode.WebviewPanel { +export function createWebviewPanel(identifier: SprottyDiagramIdentifier, + options: { localResourceRoots: vscode.Uri[], scriptUri: vscode.Uri, cssUri?: vscode.Uri }): vscode.WebviewPanel { const title = createWebviewTitle(identifier); const diagramPanel = vscode.window.createWebviewPanel( identifier.diagramType || 'diagram', @@ -39,7 +40,11 @@ export function createWebviewPanel(identifier: SprottyDiagramIdentifier, options enableScripts: true, retainContextWhenHidden: true }); - diagramPanel.webview.html = createWebviewHtml(identifier, diagramPanel, { scriptUri: options.scriptUri, title }); + diagramPanel.webview.html = createWebviewHtml(identifier, diagramPanel, { + scriptUri: options.scriptUri, + cssUri: options.cssUri, + title + }); return diagramPanel; } @@ -54,17 +59,20 @@ export function createWebviewTitle(identifier: SprottyDiagramIdentifier): string } } -export function createWebviewHtml(identifier: SprottyDiagramIdentifier, container: WebviewContainer, options: { scriptUri: vscode.Uri, title?: string }): string { +export function createWebviewHtml(identifier: SprottyDiagramIdentifier, container: WebviewContainer, + options: { scriptUri: vscode.Uri, cssUri?: vscode.Uri, title?: string }): string { + const transformUri = (uri: vscode.Uri) => container.webview.asWebviewUri(uri).toString(); return ` - ${options.title} + ${options.title ? `${options.title}` : ''} + ${options.cssUri ? `` : ''}
- + `; }