diff --git a/example/workflow/extension/package.json b/example/workflow/extension/package.json
index ff69ffe..b7d136b 100644
--- a/example/workflow/extension/package.json
+++ b/example/workflow/extension/package.json
@@ -41,60 +41,128 @@
],
"commands": [
{
- "command": "workflow.diagram.fit",
+ "command": "workflow.fit",
"title": "Fit to Screen",
"category": "Workflow Diagram",
- "enablement": "glsp-workflow-diagram-focused"
+ "enablement": "activeCustomEditorId == 'workflow.glspDiagram'"
},
{
- "command": "workflow.diagram.center",
+ "command": "workflow.center",
"title": "Center selection",
"category": "Workflow Diagram",
- "enablement": "glsp-workflow-diagram-focused"
+ "enablement": "activeCustomEditorId == 'workflow.glspDiagram'"
},
{
- "command": "workflow.diagram.layout",
+ "command": "workflow.layout",
"title": "Layout diagram",
"category": "Workflow Diagram",
- "enablement": "glsp-workflow-diagram-focused"
+ "enablement": "activeCustomEditorId == 'workflow.glspDiagram'"
},
{
"command": "workflow.goToNextNode",
"title": "Go to next Node",
"category": "Workflow Navigation",
- "enablement": "glsp-workflow-diagram-focused"
+ "enablement": "activeCustomEditorId == 'workflow.glspDiagram' && workflow.editorSelectedElementsAmount == 1"
},
{
"command": "workflow.goToPreviousNode",
"title": "Go to previous Node",
"category": "Workflow Navigation",
- "enablement": "glsp-workflow-diagram-focused"
+ "enablement": "activeCustomEditorId == 'workflow.glspDiagram' && workflow.editorSelectedElementsAmount == 1"
},
{
"command": "workflow.showDocumentation",
"title": "Show documentation...",
"category": "Workflow Diagram",
- "enablement": "glsp-workflow-diagram-focused"
+ "enablement": "activeCustomEditorId == 'workflow.glspDiagram' && workflow.editorSelectedElementsAmount == 1"
+ },
+ {
+ "command": "workflow.exportAsSVG",
+ "title": "Export as SVG",
+ "category": "Workflow Diagram",
+ "enablement": "activeCustomEditorId == 'workflow.glspDiagram'"
}
],
+ "submenus": [
+ {
+ "id": "workflow.editor.title",
+ "label": "Diagram"
+ }
+ ],
+ "menus": {
+ "editor/title": [
+ {
+ "submenu": "workflow.editor.title",
+ "group": "bookmarks"
+ },
+ {
+ "command": "workflow.goToNextNode",
+ "group": "navigation",
+ "when": "activeCustomEditorId == 'workflow.glspDiagram' && workflow.editorSelectedElementsAmount == 1"
+ },
+ {
+ "command": "workflow.goToPreviousNode",
+ "group": "navigation",
+ "when": "activeCustomEditorId == 'workflow.glspDiagram' && workflow.editorSelectedElementsAmount == 1"
+ },
+ {
+ "command": "workflow.showDocumentation",
+ "group": "navigation",
+ "when": "activeCustomEditorId == 'workflow.glspDiagram' && workflow.editorSelectedElementsAmount == 1"
+ }
+ ],
+ "workflow.editor.title": [
+ {
+ "command": "workflow.fit",
+ "group": "navigation",
+ "when": "activeCustomEditorId == 'workflow.glspDiagram'"
+ },
+ {
+ "command": "workflow.center",
+ "group": "navigation",
+ "when": "activeCustomEditorId == 'workflow.glspDiagram'"
+ },
+ {
+ "command": "workflow.layout",
+ "group": "navigation",
+ "when": "activeCustomEditorId == 'workflow.glspDiagram'"
+ },
+ {
+ "command": "workflow.exportAsSVG",
+ "when": "activeCustomEditorId == 'workflow.glspDiagram'"
+ }
+ ]
+ },
"keybindings": [
{
"key": "alt+f",
"mac": "alt+f",
- "command": "workflow.diagram.fit",
- "when": "glsp-workflow-diagram-focused"
+ "command": "workflow.fit",
+ "when": "activeCustomEditorId == 'workflow.glspDiagram'"
},
{
"key": "alt+c",
"mac": "alt+c",
- "command": "workflow.diagram.center",
- "when": "glsp-workflow-diagram-focused"
+ "command": "workflow.center",
+ "when": "activeCustomEditorId == 'workflow.glspDiagram'"
},
{
"key": "alt+l",
"mac": "alt+l",
- "command": "workflow.diagram.layout",
- "when": "glsp-workflow-diagram-focused"
+ "command": "workflow.layout",
+ "when": "activeCustomEditorId == 'workflow.glspDiagram'"
+ },
+ {
+ "key": "Ctrl+4",
+ "mac": "cmd+4",
+ "command": "workflow.goToNextNode",
+ "when": "activeCustomEditorId == 'workflow.glspDiagram' && workflow.editorSelectedElementsAmount == 1"
+ },
+ {
+ "key": "Ctrl+3",
+ "mac": "cmd+3",
+ "command": "workflow.goToPreviousNode",
+ "when": "activeCustomEditorId == 'workflow.glspDiagram' && workflow.editorSelectedElementsAmount == 1"
}
]
},
@@ -109,15 +177,15 @@
],
"main": "./lib/workflow-extension",
"devDependencies": {
+ "@eclipse-glsp/vscode-integration": "0.9.0",
"@types/node": "^8.0.0",
"path": "^0.12.7",
"rimraf": "^2.6.3",
- "@eclipse-glsp/vscode-integration": "0.9.0",
"ts-loader": "^6.2.1",
"ts-node": "^9.1.1",
- "workflow-glsp-webview": "0.9.0",
"typescript": "^3.1.3",
- "vscode": "^1.1.21"
+ "vscode": "^1.1.21",
+ "workflow-glsp-webview": "0.9.0"
},
"scripts": {
"prepare": "npx vscode-install && yarn clean && yarn build && yarn lint",
diff --git a/example/workflow/extension/src/workflow-editor-provider.ts b/example/workflow/extension/src/workflow-editor-provider.ts
new file mode 100644
index 0000000..d2104e1
--- /dev/null
+++ b/example/workflow/extension/src/workflow-editor-provider.ts
@@ -0,0 +1,64 @@
+/********************************************************************************
+ * Copyright (c) 2021 EclipseSource 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.01
+ ********************************************************************************/
+
+import * as vscode from 'vscode';
+import * as path from 'path';
+
+import { GlspVscodeConnector } from '@eclipse-glsp/vscode-integration';
+import { GlspEditorProvider } from '@eclipse-glsp/vscode-integration/lib/quickstart-components';
+
+export default class WorkflowEditorProvider extends GlspEditorProvider {
+ diagramType = 'workflow-diagram';
+
+ constructor(
+ protected readonly extensionContext: vscode.ExtensionContext,
+ protected readonly glspVscodeConnector: GlspVscodeConnector
+ ) {
+ super(glspVscodeConnector);
+ }
+
+ setUpWebview(_document: vscode.CustomDocument, webviewPanel: vscode.WebviewPanel, _token: vscode.CancellationToken, clientId: string): void {
+ const localResourceRootsUri = vscode.Uri.file(
+ path.join(this.extensionContext.extensionPath, './pack')
+ );
+
+ const webviewScriptSourceUri = vscode.Uri.file(
+ path.join(this.extensionContext.extensionPath, './pack/webview.js')
+ );
+
+ webviewPanel.webview.options = {
+ localResourceRoots: [localResourceRootsUri],
+ enableScripts: true
+ };
+
+ webviewPanel.webview.html = `
+
+
+
+
+
+
+
+
+
+
+
+ `;
+ }
+}
diff --git a/example/workflow/extension/src/workflow-extension.ts b/example/workflow/extension/src/workflow-extension.ts
index 4845105..9925433 100644
--- a/example/workflow/extension/src/workflow-extension.ts
+++ b/example/workflow/extension/src/workflow-extension.ts
@@ -13,37 +13,99 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { GlspDiagramEditorContext, NavigateAction } from '@eclipse-glsp/vscode-integration';
-import { join, resolve } from 'path';
+
import * as vscode from 'vscode';
+import * as process from 'process';
+import * as path from 'path';
+
+import {
+ GlspVscodeConnector,
+ NavigateAction,
+ LayoutOperation,
+ FitToScreenAction,
+ CenterAction,
+ RequestExportSvgAction
+} from '@eclipse-glsp/vscode-integration';
+
+import {
+ GlspServerLauncher,
+ SocketGlspVscodeServer
+} from '@eclipse-glsp/vscode-integration/lib/quickstart-components';
+
+import WorkflowEditorProvider from './workflow-editor-provider';
+
+const DEFAULT_SERVER_PORT = '5007';
-import { WorkflowGlspDiagramEditorContext } from './workflow-glsp-diagram-editor-context';
+export async function activate(context: vscode.ExtensionContext): Promise {
+ // Start server process using quickstart component
+ if (process.env.GLSP_SERVER_DEBUG !== 'true') {
+ const serverProcess = new GlspServerLauncher({
+ jarPath: path.join(__dirname, '../server/org.eclipse.glsp.example.workflow-0.9.0-SNAPSHOT-glsp.jar'),
+ serverPort: JSON.parse(process.env.GLSP_SERVER_PORT || DEFAULT_SERVER_PORT),
+ additionalArgs: ['--fileLog', 'true', '--logDir', path.join(__dirname, '../server')],
+ logging: true
+ });
+ context.subscriptions.push(serverProcess);
+ await serverProcess.start();
+ }
+
+ // Wrap server with quickstart component
+ const workflowServer = new SocketGlspVscodeServer({
+ clientId: 'glsp.workflow',
+ clientName: 'workflow',
+ serverPort: JSON.parse(process.env.GLSP_SERVER_PORT || DEFAULT_SERVER_PORT)
+ });
-export const SERVER_DIR = join(__dirname, '..', 'server');
-export const JAR_FILE = resolve(join(SERVER_DIR, 'org.eclipse.glsp.example.workflow-0.9.0-SNAPSHOT-glsp.jar'));
+ // Initialize GLSP-VSCode connector with server wrapper
+ const glspVscodeConnector = new GlspVscodeConnector({
+ server: workflowServer,
+ logging: true
+ });
-let editorContext: GlspDiagramEditorContext;
+ const customEditorProvider = vscode.window.registerCustomEditorProvider(
+ 'workflow.glspDiagram',
+ new WorkflowEditorProvider(context, glspVscodeConnector),
+ {
+ webviewOptions: { retainContextWhenHidden: true },
+ supportsMultipleEditorsPerDocument: false
+ }
+ );
-export function activate(context: vscode.ExtensionContext): void {
- editorContext = new WorkflowGlspDiagramEditorContext(context);
+ context.subscriptions.push(workflowServer, glspVscodeConnector, customEditorProvider);
+ workflowServer.start();
+ // Keep track of selected elements
+ let selectedElements: string[] = [];
context.subscriptions.push(
+ glspVscodeConnector.onSelectionUpdate(n => {
+ selectedElements = n;
+ vscode.commands.executeCommand('setContext', 'workflow.editorSelectedElementsAmount', n.length);
+ })
+ );
+
+ // Register various commands
+ context.subscriptions.push(
+ vscode.commands.registerCommand('workflow.fit', () => {
+ glspVscodeConnector.sendActionToActiveClient(new FitToScreenAction(selectedElements));
+ }),
+ vscode.commands.registerCommand('workflow.center', () => {
+ glspVscodeConnector.sendActionToActiveClient(new CenterAction(selectedElements));
+ }),
+ vscode.commands.registerCommand('workflow.layout', () => {
+ glspVscodeConnector.sendActionToActiveClient(new LayoutOperation());
+ }),
vscode.commands.registerCommand('workflow.goToNextNode', () => {
- editorContext.dispatchActionToWebview(new NavigateAction('next'));
+ glspVscodeConnector.sendActionToActiveClient(new NavigateAction('next'));
}),
vscode.commands.registerCommand('workflow.goToPreviousNode', () => {
- editorContext.dispatchActionToWebview(new NavigateAction('previous'));
+ glspVscodeConnector.sendActionToActiveClient(new NavigateAction('previous'));
}),
vscode.commands.registerCommand('workflow.showDocumentation', () => {
- editorContext.dispatchActionToWebview(new NavigateAction('documentation'));
+ glspVscodeConnector.sendActionToActiveClient(new NavigateAction('documentation'));
+ }),
+ vscode.commands.registerCommand('workflow.exportAsSVG', () => {
+ glspVscodeConnector.sendActionToActiveClient(new RequestExportSvgAction());
})
);
}
-export function deactivate(): Thenable {
- if (!editorContext) {
- return Promise.resolve(undefined);
- }
- return editorContext.deactivateGLSPClient();
-}
-
diff --git a/example/workflow/extension/src/workflow-glsp-diagram-editor-context.ts b/example/workflow/extension/src/workflow-glsp-diagram-editor-context.ts
deleted file mode 100644
index faa77b4..0000000
--- a/example/workflow/extension/src/workflow-glsp-diagram-editor-context.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-/********************************************************************************
- * Copyright (c) 2021 EclipseSource 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 {
- GlspDiagramEditorContext,
- GLSPEnvVariable,
- GLSPJavaServerArgs,
- GLSPWebView,
- JavaSocketServerConnectionProvider
-} from '@eclipse-glsp/vscode-integration';
-import { ServerConnectionProvider } from '@eclipse-glsp/vscode-integration/lib/server-connection-provider';
-import { join, resolve } from 'path';
-import { SprottyDiagramIdentifier } from 'sprotty-vscode-protocol';
-import * as vscode from 'vscode';
-
-export const DEFAULT_PORT = 5007;
-export const PORT_ARG_KEY = 'WF_GLSP';
-export const SERVER_DIR = join(__dirname, '..', 'server');
-export const JAR_FILE = resolve(join(SERVER_DIR, 'org.eclipse.glsp.example.workflow-0.9.0-SNAPSHOT-glsp.jar'));
-
-export class WorkflowGlspDiagramEditorContext extends GlspDiagramEditorContext {
- readonly id = 'glsp.workflow';
- readonly diagramType = 'workflow-diagram';
-
- get extensionPrefix(): string {
- return 'workflow';
- }
-
- protected getConnectionProvider(): ServerConnectionProvider {
- const launchOptions = {
- jarPath: JAR_FILE,
- serverPort: GLSPEnvVariable.getServerPort() || DEFAULT_PORT,
- isRunning: GLSPEnvVariable.isServerDebug(),
- noConsoleLog: true,
- additionalArgs: GLSPJavaServerArgs.enableFileLogging(SERVER_DIR)
- };
- return new JavaSocketServerConnectionProvider(launchOptions);
- }
-
- createWebview(webviewPanel: vscode.WebviewPanel, identifier: SprottyDiagramIdentifier): GLSPWebView {
- const webview = new GLSPWebView({
- editorContext: this,
- identifier,
- localResourceRoots: [
- this.getExtensionFileUri('pack')
- ],
- scriptUri: this.getExtensionFileUri('pack', 'webview.js'),
- webviewPanel
- });
- return webview;
- }
-}
diff --git a/example/workflow/scripts/download.ts b/example/workflow/scripts/download.ts
index 1f892f4..8a21e1f 100644
--- a/example/workflow/scripts/download.ts
+++ b/example/workflow/scripts/download.ts
@@ -15,6 +15,7 @@
********************************************************************************/
import download from 'mvn-artifact-download';
import { join } from 'path';
+import { existsSync } from 'fs';
const downloadDir = join(__dirname, '../extension/server');
const mavenRepository = 'https://oss.sonatype.org/content/repositories/snapshots/';
@@ -23,8 +24,13 @@ const artifactId = 'org.eclipse.glsp.example.workflow';
const version = '0.9.0';
const classifier = 'glsp';
-console.log('Downloading latest version of the Workflow Example Java Server from the maven repository...');
-download({ groupId, artifactId, version, classifier, isSnapShot: true }, downloadDir, mavenRepository)
- .then(() => console.log('Download completed. Start the server using this command: \njava -jar org.eclipse.glsp.example.workflow-'
- + version + '-SNAPSHOT-glsp.jar org.eclipse.glsp.example.workflow.launch.ExampleServerLauncher\n\n'))
- .catch(err => console.error(err));
+if (!existsSync(`${__dirname}/../extension/server/${artifactId}-${version}-SNAPSHOT-${classifier}.jar`)) {
+ console.log('Downloading latest version of the Workflow Example Java Server from the maven repository...');
+ download({ groupId, artifactId, version, classifier, isSnapShot: true }, downloadDir, mavenRepository)
+ .then(() => console.log('Download completed. Start the server using this command: \njava -jar org.eclipse.glsp.example.workflow-'
+ + version + '-SNAPSHOT-glsp.jar org.eclipse.glsp.example.workflow.launch.ExampleServerLauncher\n\n'))
+ .catch(err => console.error(err));
+} else {
+ console.log('Server jar already exists. Skipping download.');
+}
+
diff --git a/packages/vscode-integration-webview/src/glsp-starter.ts b/packages/vscode-integration-webview/src/glsp-starter.ts
index 6dd1a55..da81e13 100644
--- a/packages/vscode-integration-webview/src/glsp-starter.ts
+++ b/packages/vscode-integration-webview/src/glsp-starter.ts
@@ -13,8 +13,18 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { DiagramServer, NavigateToExternalTargetAction, TYPES } from '@eclipse-glsp/client';
import { Container } from 'inversify';
+import {
+ DiagramServer,
+ NavigateToExternalTargetAction,
+ ExportSvgAction,
+ SelectAction,
+ TYPES
+} from '@eclipse-glsp/client';
+import {
+ RequestClipboardDataAction,
+ SetClipboardDataAction
+} from '@eclipse-glsp/client/lib/features/copy-paste/copy-paste-actions';
import {
SprottyDiagramIdentifier,
SprottyStarter,
@@ -52,6 +62,12 @@ export abstract class GLSPStarter extends SprottyStarter {
* All kinds of actions that should (also) be delegated to and handled by the vscode extension
*/
protected get extensionActionKinds(): string[] {
- return [NavigateToExternalTargetAction.KIND];
+ return [
+ NavigateToExternalTargetAction.KIND,
+ RequestClipboardDataAction.KIND,
+ SetClipboardDataAction.KIND,
+ SelectAction.KIND,
+ ExportSvgAction.KIND
+ ];
}
}
diff --git a/packages/vscode-integration-webview/src/glsp-vscode-diagramserver.ts b/packages/vscode-integration-webview/src/glsp-vscode-diagramserver.ts
index 6b43c33..03b3f44 100644
--- a/packages/vscode-integration-webview/src/glsp-vscode-diagramserver.ts
+++ b/packages/vscode-integration-webview/src/glsp-vscode-diagramserver.ts
@@ -23,7 +23,8 @@ import {
isDeleteElementOperation,
isSetEditModeAction,
registerDefaultGLSPServerActions,
- SetEditModeAction
+ SetEditModeAction,
+ ICopyPasteHandler
} from '@eclipse-glsp/client';
import { SelectionService } from '@eclipse-glsp/client/lib/features/select/selection-service';
import { inject } from 'inversify';
@@ -34,6 +35,7 @@ export const localDispatchProperty = '__localDispatch';
export class GLSPVscodeDiagramServer extends VscodeDiagramServer {
@inject(GLSP_TYPES.SelectionService) protected selectionService: SelectionService;
+ @inject(GLSP_TYPES.ICopyPasteHandler) protected copyPasteHandler: ICopyPasteHandler;
initialize(registry: ActionHandlerRegistry): void {
registerDefaultGLSPServerActions(registry, this);
@@ -43,6 +45,18 @@ export class GLSPVscodeDiagramServer extends VscodeDiagramServer {
this.messageReceived(message.data);
}
});
+
+ window.addEventListener('copy', (e: ClipboardEvent) => {
+ this.copyPasteHandler.handleCopy(e);
+ });
+
+ window.addEventListener('cut', (e: ClipboardEvent) => {
+ this.copyPasteHandler.handleCut(e);
+ });
+
+ window.addEventListener('paste', (e: ClipboardEvent) => {
+ this.copyPasteHandler.handlePaste(e);
+ });
}
handleLocally(action: Action): boolean {
diff --git a/packages/vscode-integration/README.md b/packages/vscode-integration/README.md
index 389e7f6..c9c0e73 100644
--- a/packages/vscode-integration/README.md
+++ b/packages/vscode-integration/README.md
@@ -1,16 +1,571 @@
# Eclipse GLSP VSCode Integration
+This package contains the glue code for integrating [GLSP](https://www.eclipse.org/glsp/)
+diagrams in VS Code. This library enables the implementation of GLSP Diagram editors
+for VS Code base on the [Custom Editor API](https://code.visualstudio.com/api/extension-guides/custom-editors).
-This package contains the glue code for integrating [GLSP](https://www.eclipse.org/glsp/) diagrams in VS Code. This libaray enables
-the implementation of GLSP Diagram editors for VS Code base on the [Custom Editor API](https://code.visualstudio.com/api/extension-guides/custom-editors).
-
-
-### Where to find the sources?
-
+## Where to find the sources?
In addition to this repository, the related source code can be found here:
- https://github.com/eclipse-glsp/glsp-server
- https://github.com/eclipse-glsp/glsp-client
-## More information
+## Getting started
+This section will show you how to get your first GLPS extension up and running using
+the *GLSP VSCode Integration*.
+
+### Example Extension
+You can find a complete example extension that uses this package
+[here](https://github.com/eclipse-glsp/glsp-vscode-integration/tree/master/example/workflow).
+It makes heavy use of default implementations like the default GLSP server, the
+default GLSP client and the default GLSP Sprotty client.
+
+### Example Code
+There are a few steps that are absolutely necessary for the GLSP VSCode Integration
+to work. They are outlined in this section.
+
+#### Extension
+First you need to set up your extension starter code. This is done by setting the
+`"main"` field of your `package.json` to the entry point of your extension and exporting
+an `activate()` function from that file. For more information on how to set up your
+VSCode extension please visit https://code.visualstudio.com/api.
+
+Code Example
+
+```typescript
+import * as vscode from 'vscode';
+
+export async function activate(context: vscode.ExtensionContext): Promise {
+ // Your extension code here.
+}
+```
+
+
+#### Server
+Next we will start a GLSP server from within the activate function. If you have
+already have the server running through some other process you can skip this step.
+
+If you are using the default GLSP server implementation provided at https://github.com/eclipse-glsp/glsp-server,
+you can use the `GlspServerLauncher` [quickstart component](#Quickstart-Components)
+to start the server with very little code:
+
+Code Example
+
+```typescript
+import { GlspServerLauncher } from '@eclipse-glsp/vscode-integration/lib/quickstart-components';
+
+export async function activate(context: vscode.ExtensionContext): Promise {
+ const serverProcess = new GlspServerLauncher({
+ jarPath: '/your/path/to/server.jar',
+ serverPort: 5007
+ });
+ context.subscriptions.push(serverProcess);
+ await serverProcess.start();
+}
+```
+
+
+#### Server Interface and GLSP VSCode Connector
+A connector class is needed to provide an interface for the *GLSP VSCode integration*
+to communicate with the server. If you are using the default GLSP server which communicates
+over JSON-RPC, you can make use of the `SocketGlspVscodeServer` [quickstart component](#Quickstart-Components)
+to implement the needed interface with very little boilerplate code.
+
+If we have a server component providing the needed interface we can create an instance
+of the `GlspVscodeConnector` and pass the server component. The `GlspVscodeConnector`
+lies at the core of this package and provides all the needed functionality.
+
+Code Example
+
+```typescript
+import { GlspVscodeConnector } from '@eclipse-glsp/vscode-integration';
+import { SocketGlspVscodeServer } from '@eclipse-glsp/vscode-integration/lib/quickstart-components';
+
+export async function activate(context: vscode.ExtensionContext): Promise {
+ // (Server startup code from above here...)
+
+ const workflowServer = new SocketGlspVscodeServer({
+ clientId: 'some.client.id',
+ clientName: 'SomeClientName',
+ serverPort: 5007
+ });
+
+ const glspVscodeConnector = new GlspVscodeConnector({ server: workflowServer });
+
+ context.subscriptions.push(workflowServer, glspVscodeConnector)
+}
+```
+
+
+#### Custom Editor Provider
+In order to have a custom editor in VSCode a component implementing the `vscode.CustomEditorProvider`
+needs to be registered from within the extension (more information on custom editors
+[here](https://code.visualstudio.com/api/extension-guides/custom-editors)).
+
+The GLSP VSCode integration package gives you free reign over how you implement
+your `CustomEditorProvider`, however a few function calls at certain places are
+needed for the integration to work properly:
+
+- The `onDidChangeCustomDocument` of your `CustomEditorProvider` should always fire
+ at least when `GlspVscodeConnector.onDidChangeCustomDocument` fires.
+- `GlspVscodeConnector.saveDocument(document)` should be called when `CustomEditorProvider.saveCustomDocument`
+ is called.
+- `GlspVscodeConnector.saveDocument(document, destination)` should be called when
+ `CustomEditorProvider.saveCustomDocumentAs` is called.
+- `GlspVscodeConnector.revertDocument()` should be called when `CustomEditorProvider.revertCustomDocument`
+ is called.
+
+You can use the `GlspEditorProvider` [quickstart component](#Quickstart-Components)
+to set up such an editor provider without much boilerplate code.
+
+If you chose to create a `CustomEditorProvider` yourself, the `resolveCustomEditor`
+function of the `CustomEditorProvider` act as an excellent place to register your
+GLSP clients. You can do this with the `GlspVscodeConnector.registerClient(client)`
+function. You are free to choose on how your clients implement the needed interface,
+however if you need inspiration on how to do it with the default GLSP components,
+you can take a look at the example
+[here](https://github.com/eclipse-glsp/glsp-vscode-integration/tree/master/example/workflow).
+
+Code Example
+
+```typescript
+import MyCustomEditorProvider from './my-custom-editor-provider.ts';
+
+export async function activate(context: vscode.ExtensionContext): Promise {
+ // (Server startup code from above here...)
+ // (GlspVscodeConnector code from above here...)
+
+ const customEditorProvider = vscode.window.registerCustomEditorProvider(
+ 'your.custom.editor',
+ new MyCustomEditorProvider(glspVscodeConnector),
+ {
+ webviewOptions: { retainContextWhenHidden: true },
+ supportsMultipleEditorsPerDocument: false
+ }
+ );
+
+ context.subscriptions.push(customEditorProvider);
+}
+```
+
+```typescript
+// my-custom-editor-provider.ts
+export default class WorkflowEditorProvider implements vscode.CustomEditorProvider {
+
+ onDidChangeCustomDocument: vscode.Event>;
+
+ constructor(
+ private readonly glspVscodeConnector: GlspVscodeConnector
+ ) {
+ this.onDidChangeCustomDocument = glspVscodeConnector.onDidChangeCustomDocument; // necessary
+ }
+
+ // (Any other methods needed for the vscode.CustomEditorProvider interface here.)
+
+ saveCustomDocument(document: vscode.CustomDocument, cancellation: vscode.CancellationToken): Thenable {
+ return this.glspVscodeConnector.saveDocument(document); // necessary
+ }
+
+ saveCustomDocumentAs(document: vscode.CustomDocument, destination: vscode.Uri, cancellation: vscode.CancellationToken): Thenable {
+ return this.glspVscodeConnector.saveDocument(document, destination); // necessary
+ }
+
+ revertCustomDocument(document: vscode.CustomDocument, cancellation: vscode.CancellationToken): Thenable {
+ return this.glspVscodeConnector.revertDocument(document, 'your.diagram.type'); // necessary
+ }
+
+ resolveCustomEditor(document:
+ vscode.CustomDocument,
+ webviewPanel: vscode.WebviewPanel,
+ token: vscode.CancellationToken
+ ): void | Thenable {
+
+ const onSendToClientEmitter = new vscode.EventEmitter();
+ const onClientMessage = new vscode.EventEmitter();
+
+ // (Your code to send event content to webview here using onSendToClientEmitter.)
+ // (Your code to emit messages from client with onClientSendEmitter.)
+
+ this.glspVscodeConnector.registerClient({
+ clientId: 'your.glsp.client.id.here', // Should be different each time resolve custom Editor is called - must be equal to the ids the client will send in its messages
+ document: document,
+ webviewPanel: webviewPanel,
+ onClientMessage: onClientSendEmitter.event,
+ onSendToClientEmitter: onSendToClientEmitter
+ });
+
+ webviewPanel.webview.html = `(Your webview HTML here)`;
+ }
+}
+```
+
+
+#### Final touches
+All that's left to do is a final call to start the server and the extension
+should be up and running.
+
+Code Example
+
+```typescript
+
+export async function activate(context: vscode.ExtensionContext): Promise {
+ // (Server startup code from above here...)
+ // (GlspVscodeConnector code from above here...)
+ // (CustomEditorProvider code from above here...)
+
+ workflowServer.start();
+}
+```
+
-For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). If you have questions, contact us on our [spectrum chat](https://spectrum.chat/glsp/) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/).
+## API
+This package exports a number of members, the most important one being the `GlspVscodeConnector`-Class.
+
+### GlspVscodeConnector
+This is the core of the VSCode integration and provides various functionality. It
+primarily intercepts certain GLSP Actions sent from the clients or server to trigger
+VSCode specific contributions. This currently includes:
+
+- File dirty state
+- File "Save" and "Save as..."
+- File reverting
+- Diagnostics or "markers" and "validations"
+- External target navigation
+- Exporting as SVG (with dialog window)
+- Providing element selection context to extensions
+
+#### Options
+The `GlspVscodeConnector` takes one constructor argument - an object containing its configuration.
+
+```typescript
+export interface GlspVscodeConnectorOptions {
+
+ /**
+ * The GLSP server (or its wrapper) that the VSCode integration should use.
+ */
+ server: GlspVscodeServer;
+
+ /**
+ * Wether the GLSP-VSCode integration should log various events. This is useful
+ * if you want to find out what events the VSCode integration is receiving from
+ * and sending to the server and clients.
+ *
+ * Defaults to `false`.
+ */
+ logging?: boolean;
+
+ /**
+ * Optional property to intercept (and/or modify) messages sent from the client
+ * to the VSCode integration via `GlspVscodeClient.onClientSend`.
+ *
+ * @param message Contains the original message sent by the client.
+ * @param callback A callback to control how messages are handled further.
+ */
+ onBeforeReceiveMessageFromClient?: (message: unknown, callback: InterceptorCallback) => void;
+
+ /**
+ * Optional property to intercept (and/or modify) messages sent from the server
+ * to the VSCode integration via `GlspVscodeServer.onServerSend`.
+ *
+ * @param message Contains the original message sent by the client.
+ * @param callback A callback to control how messages are handled further.
+ */
+ onBeforeReceiveMessageFromServer?(message: unknown, callback: InterceptorCallback): void;
+
+ /**
+ * Optional property to intercept (and/or modify) messages sent from the VSCode
+ * integration to the server via the `GlspVscodeServer.onServerReceiveEmitter`.
+ *
+ * The returned value from this function is the message that will be propagated
+ * to the server. It can be modified to anything. Returning `undefined` will
+ * cancel the propagation.
+ *
+ * @param originalMessage The original message received by the VSCode integration
+ * from the client.
+ * @param processedMessage If the VSCode integration modified the received message
+ * in any way, this parameter will contain the modified message. If the VSCode
+ * integration did not modify the message, this parameter will be identical to
+ * `originalMessage`.
+ * @param messageChanged This parameter will indicate wether the VSCode integration
+ * modified the incoming message. In other words: Whether `originalMessage`
+ * and `processedMessage` are different.
+ * @returns A message that will be propagated to the server. If the message
+ * is `undefined` the propagation will be cancelled.
+ */
+ onBeforePropagateMessageToServer?(
+ originalMessage: unknown, processedMessage: unknown, messageChanged: boolean
+ ): unknown | undefined;
+
+ /**
+ * Optional property to intercept (and/or modify) messages sent from the VSCode
+ * integration to a client via the `GlspVscodeClient.onClientReceiveEmitter`.
+ *
+ * The returned value from this function is the message that will be propagated
+ * to the client. It can be modified to anything. Returning `undefined` will
+ * cancel the propagation.
+ *
+ * @param originalMessage The original message received by the VSCode integration
+ * from the server.
+ * @param processedMessage If the VSCode integration modified the received message
+ * in any way, this parameter will contain the modified message. If the VSCode
+ * integration did not modify the message, this parameter will be identical to
+ * `originalMessage`.
+ * @param messageChanged This parameter will indicate wether the VSCode integration
+ * modified the incoming message. In other words: Whether `originalMessage`
+ * and `processedMessage` are different.
+ * @returns A message that will be propagated to the client. If the message
+ * is `undefined` the propagation will be cancelled.
+ */
+ onBeforePropagateMessageToClient?(
+ originalMessage: unknown, processedMessage: unknown, messageChanged: boolean
+ ): unknown | undefined;
+}
+```
+
+#### Methods and Fields
+
+```typescript
+interface GlspVscodeConnector extends vscode.Disposable {
+
+ /**
+ * A subscribable event which fires with an array containing the IDs of all
+ * selected elements when the selection of the editor changes.
+ */
+ onSelectionUpdate: vscode.Event;
+
+ /**
+ * A subscribable event which fires when a document changed. The event body
+ * will contain that document. Use this event for the onDidChangeCustomDocument
+ * on your implementation of the `CustomEditorProvider`.
+ */
+ onDidChangeCustomDocument: vscode.Event>;
+
+ /**
+ * Register a client on the GLSP-VSCode connector. All communication will subsequently
+ * run through the VSCode integration. Clients do not need to be unregistered
+ * as they are automatically disposed of when the panel they belong to is closed.
+ *
+ * @param client The client to register.
+ */
+ registerClient(client: GlspVscodeClient): void;
+
+ /**
+ * Send an action to the client/panel that is currently focused. If no registered
+ * panel is focused, the message will not be sent.
+ *
+ * @param action The action to send to the active client.
+ */
+ sendActionToActiveClient(action: Action): void;
+
+ /**
+ * Saves a document. Make sure to call this function in the `saveCustomDocument`
+ * and `saveCustomDocumentAs` functions of your `CustomEditorProvider` implementation.
+ *
+ * @param document The document to save.
+ * @param destination Optional parameter. When this parameter is provided the
+ * file will instead be saved at this location.
+ * @returns A promise that resolves when the file has been successfully saved.
+ */
+ async saveDocument(document: D, destination?: vscode.Uri): Promise;
+
+ /**
+ * Reverts a document. Make sure to call this function in the `revertCustomDocument`
+ * functions of your `CustomEditorProvider` implementation.
+ *
+ * @param document Document to revert.
+ * @param diagramType Diagram type as it is configured on the server.
+ * @returns A promise that resolves when the file has been successfully reverted.
+ */
+ async revertDocument(document: D, diagramType: string): Promise;
+}
+```
+
+### GlspVscodeServer
+
+```typescript
+/**
+ * The server or server wrapper used by the VSCode integration needs to implement
+ * this interface.
+ */
+export interface GlspVscodeServer {
+
+ /**
+ * An event emitter used by the VSCode extension to send messages to the server.
+ *
+ * You should subscribe to the event attached to this emitter to receive messages
+ * from the client/VSCode integration and pass them to the server.
+ *
+ * Use the properties `onBeforeReceiveMessageFromClient` and `onBeforePropagateMessageToServer`
+ * of the GlspVscodeConnector in order to control what messages are propagated
+ * and processed.
+ */
+ readonly onSendToServerEmitter: vscode.EventEmitter;
+
+ /**
+ * An event the VSCode integration uses to receive messages from the server.
+ * The messages are then propagated to the client or processed by the VSCode
+ * integration to provide functionality.
+ *
+ * Fire this event with the message the server wants to send to the client.
+ *
+ * Use the properties `onBeforeReceiveMessageFromServer` and `onBeforePropagateMessageToClient`
+ * of the GlspVscodeConnector in order to control what messages are propagated
+ * and processed.
+ */
+ readonly onServerMessage: vscode.Event;
+}
+```
+
+### GlspVscodeClient
+
+```typescript
+/**
+ * Any clients registered on the GLSP VSCode integration need to implement this
+ * interface.
+ */
+export interface GlspVscodeClient {
+
+ /**
+ * A unique identifier for the client/panel with which the client will be registered
+ * on the server.
+ */
+ readonly clientId: string;
+
+ /**
+ * The webview belonging to the client.
+ */
+ readonly webviewPanel: vscode.WebviewPanel;
+
+ /**
+ * The document object belonging to the client;
+ */
+ readonly document: D;
+
+ /**
+ * This event emitter is used by the VSCode integration to pass messages/actions
+ * to the client. These messages can come from the server or the VSCode integration
+ * itself.
+ *
+ * You should subscribe to the attached event and pass contents of the event
+ * to the webview.
+ *
+ * Use the properties `onBeforeReceiveMessageFromServer` and `onBeforePropagateMessageToClient`
+ * of the GlspVscodeConnector in order to control what messages are propagated
+ * and processed.
+ */
+ readonly onSendToClientEmitter: vscode.EventEmitter;
+
+ /**
+ * The VSCode integration will subscribe to this event to listen to messages
+ * from the client.
+ *
+ * Fire this event with the message the client wants to send to the server.
+ *
+ * Use the properties `onBeforeReceiveMessageFromClient` and `onBeforePropagateMessageToServer`
+ * of the GlspVscodeConnector in order to control what messages are propagated
+ * and processed.
+ */
+ readonly onClientMessage: vscode.Event;
+}
+```
+
+### Quickstart Components
+This package also exposes components which can be taken advantage of if you are using the default GLSP components.
+
+They can be imported using
+
+```ts
+import * as QuickstartComponents from '@eclipse-glsp/vscode-integration/lib/quickstart-components';
+```
+
+#### GlspServerLauncher
+A small class used to start a default implementation GLSP server.
+
+```ts
+interface GlspServerLauncher extends vscode.Disposable {
+ constructor(options: JavaSocketServerLauncherOptions);
+
+ /**
+ * Starts up the server.
+ */
+ async start(): Promise;
+
+ /**
+ * Stops the server.
+ */
+ stop(): void;
+}
+
+interface JavaSocketServerLauncherOptions {
+ /** Path to the location of the jar file that should be launched as process */
+ readonly jarPath: string;
+ /** Port on which the server should listen for new client connections */
+ readonly serverPort: number;
+ /** Set to `true` if server stdout and stderr should be printed in extension host console. Default: `false` */
+ readonly logging?: boolean;
+ /** Additional arguments that should be passed when starting the server process. */
+ readonly additionalArgs?: string[];
+}
+```
+
+#### SocketGlspVscodeServer
+A can component that provides the right interface for the GLSP VSCode integration
+to be used as server and which can connect to a default implementation GLSP server.
+
+```ts
+interface SocketGlspVscodeServerOptions {
+ /** Port of the running server. */
+ readonly serverPort: number;
+ /** Client ID to register the jsonRPC client with on the server. */
+ readonly clientId: string;
+ /** Name to register the client with on the server. */
+ readonly clientName: string;
+}
+
+interface SocketGlspVscodeServer extends GlspVscodeServer, vscode.Disposable {
+
+ constructor(private readonly options: SocketGlspVscodeServerOptions);
+
+ /**
+ * Starts up the JSON-RPC client and connects it to a running server.
+ */
+ async start(): Promise;
+
+ /**
+ * Stops the client. It cannot be restarted.
+ */
+ async stop(): Promise;
+}
+```
+
+#### GlspEditorProvider
+An extensible base class to create a CustomEditorProvider that takes care of diagram
+initialization and custom document events.
+
+Webview setup needs to be implemented.
+
+```ts
+export abstract class GlspEditorProvider implements vscode.CustomEditorProvider {
+ /**
+ * The diagram type identifier the diagram server is responsible for.
+ */
+ abstract diagramType: string;
+
+ constructor(protected readonly glspVscodeConnector: GlspVscodeConnector);
+
+ /**
+ * Used to set up the webview within the webview panel.
+ */
+ abstract setUpWebview(
+ document: vscode.CustomDocument,
+ webviewPanel: vscode.WebviewPanel,
+ token: vscode.CancellationToken,
+ clientId: string
+ ): void;
+}
+```
+
+## More information
+For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp)
+and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/). If you have questions,
+contact us on our [spectrum chat](https://spectrum.chat/glsp/) and have a look at our
+[communication and support options](https://www.eclipse.org/glsp/contact/).
diff --git a/packages/vscode-integration/package.json b/packages/vscode-integration/package.json
index fb2a46d..4c10f1a 100644
--- a/packages/vscode-integration/package.json
+++ b/packages/vscode-integration/package.json
@@ -35,7 +35,6 @@
},
"dependencies": {
"@eclipse-glsp/protocol": "next",
- "sprotty-vscode-protocol": "0.0.5",
"vscode-jsonrpc": "^4.0.0"
},
"devDependencies": {
diff --git a/packages/vscode-integration/src/action/action-handler.ts b/packages/vscode-integration/src/action/action-handler.ts
deleted file mode 100644
index 91a008a..0000000
--- a/packages/vscode-integration/src/action/action-handler.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/********************************************************************************
- * Copyright (c) 2021 EclipseSource 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-vscode-protocol';
-
-/**
- * Used to locally intercept and handle actions in the VS Code extension.
- */
-export interface ExtensionActionHandler {
-
- /**
- * List of action names that the action handler will intercept.
- */
- readonly kinds: string[];
-
- /**
- * @returns true when the action should be further progagated to the glsp server or the
- * webview
- */
- handleAction(action: Action): Thenable;
-}
diff --git a/packages/vscode-integration/src/action/external-navigation.ts b/packages/vscode-integration/src/action/external-navigation.ts
deleted file mode 100644
index 23965df..0000000
--- a/packages/vscode-integration/src/action/external-navigation.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-/********************************************************************************
- * Copyright (c) 2021 EclipseSource 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 { ExtensionActionHandler } from './action-handler';
-import { Action } from 'sprotty-vscode-protocol';
-
-interface Args { [key: string]: string | number | boolean }
-
-interface NavigationTarget {
- uri: string;
- label?: string;
- args?: Args;
-}
-
-export class NavigateToExternalTargetAction implements Action {
- static readonly KIND = 'navigateToExternalTarget';
- readonly kind = NavigateToExternalTargetAction.KIND;
- constructor(readonly target: NavigationTarget) { }
- static is(action?: Action): action is NavigateToExternalTargetAction {
- return action !== undefined && (action.kind === NavigateToExternalTargetAction.KIND)
- && (action as NavigateToExternalTargetAction).target !== undefined;
- }
-}
-
-export class NavigateToExternalTargetHandler implements ExtensionActionHandler {
- static SHOW_OPTIONS = 'jsonOpenerOptions';
-
- kinds = [NavigateToExternalTargetAction.KIND];
-
- async handleAction(action: Action): Promise {
- if (NavigateToExternalTargetAction.is(action)) {
- const { uri, args } = action.target;
- let showOptions = { ...args };
-
- // Give server the possibility to provide options through the `showOptions` field by providing a
- // stringified version of the `TextDocumentShowOptions`
- // See: https://code.visualstudio.com/api/references/vscode-api#TextDocumentShowOptions
- const showOptionsField = args?.[NavigateToExternalTargetHandler.SHOW_OPTIONS];
- if (showOptionsField) {
- showOptions = { ...args, ...(JSON.parse(showOptionsField.toString())) };
- }
-
- vscode.window.showTextDocument(vscode.Uri.parse(uri), showOptions);
- }
-
- return false;
- }
-}
diff --git a/packages/vscode-integration/src/action/action-dispatcher.ts b/packages/vscode-integration/src/actions/action.ts
similarity index 57%
rename from packages/vscode-integration/src/action/action-dispatcher.ts
rename to packages/vscode-integration/src/actions/action.ts
index cb1cdff..dfeb728 100644
--- a/packages/vscode-integration/src/action/action-dispatcher.ts
+++ b/packages/vscode-integration/src/actions/action.ts
@@ -13,18 +13,23 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { Action } from 'sprotty-vscode-protocol';
-import { GLSPWebViewRegistry } from 'src/glsp-webview';
+/* eslint-disable no-null/no-null */
-export interface ExtensionActionDispatcher {
- dispatch(action: Action): void;
+export interface Action {
+ readonly kind: string;
}
-export namespace ExtensionActionDispatcher {
- export function dispatch(registry: GLSPWebViewRegistry, action: Action): void {
- const activeWebview = registry.getActiveWebview();
- if (activeWebview) {
- activeWebview.dispatch(action);
- }
- }
+export interface ActionMessage {
+ clientId: string;
+ action: A;
+}
+
+export function isAction(object: any): object is Action {
+ return typeof object === 'object' && object !== null && 'kind' in object && typeof object['kind'] === 'string';
+}
+
+export function isActionMessage(object: any): object is ActionMessage {
+ return typeof object === 'object' && object !== null &&
+ 'clientId' in object && typeof object['clientId'] === 'string' &&
+ 'action' in object && isAction(object.action);
}
diff --git a/packages/vscode-integration/src/utils/glsp-java-server-args.ts b/packages/vscode-integration/src/actions/export.ts
similarity index 54%
rename from packages/vscode-integration/src/utils/glsp-java-server-args.ts
rename to packages/vscode-integration/src/actions/export.ts
index 7cbaed4..7cc469e 100644
--- a/packages/vscode-integration/src/utils/glsp-java-server-args.ts
+++ b/packages/vscode-integration/src/actions/export.ts
@@ -14,19 +14,22 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-export namespace GLSPJavaServerArgs {
- /**
- * Utility function to create the additional launch args for a GLSP Java Server
- * to enable file logging.
- * @param logDir Path to the directy where the log files should be stored
- * @param disableConsolelogging Flag to indicate wether default console logging should be disabled
- */
- export function enableFileLogging(logDir: string, disableConsolelogging = true): string[] {
- const additionalArgs = ['--fileLog', 'true', '--logDir', logDir];
- if (disableConsolelogging) {
- additionalArgs.push('--consoleLog');
- additionalArgs.push('false');
- }
- return additionalArgs;
+import { Action } from './action';
+
+export class ExportSvgAction implements Action {
+ static readonly KIND = 'exportSvg';
+ constructor(public readonly svg: string, public readonly kind = ExportSvgAction.KIND) { }
+
+ static is(action?: Action): action is ExportSvgAction {
+ return action !== undefined && action.kind === ExportSvgAction.KIND && 'svg' in action;
+ }
+}
+
+export class RequestExportSvgAction implements Action {
+ static readonly KIND = 'requestExportSvg';
+ constructor(public readonly kind = RequestExportSvgAction.KIND) { }
+
+ static is(action?: Action): action is RequestExportSvgAction {
+ return action !== undefined && action.kind === RequestExportSvgAction.KIND;
}
}
diff --git a/packages/vscode-integration/src/utils/glsp-env-var.ts b/packages/vscode-integration/src/actions/external-navigation.ts
similarity index 55%
rename from packages/vscode-integration/src/utils/glsp-env-var.ts
rename to packages/vscode-integration/src/actions/external-navigation.ts
index 469fed5..a67abe6 100644
--- a/packages/vscode-integration/src/utils/glsp-env-var.ts
+++ b/packages/vscode-integration/src/actions/external-navigation.ts
@@ -13,20 +13,23 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-export namespace GLSPEnvVariable {
- export const SERVER_DEBUG = 'GLSP_SERVER_DEBUG';
- export const SERVER_PORT = 'GLSP_SERVER_PORT';
- export function isServerDebug(): boolean {
- const envVar = process.env[SERVER_DEBUG];
- return envVar !== undefined && JSON.parse(envVar);
- }
+import { Action } from './action';
+
+interface Args { [key: string]: string | number | boolean }
+
+interface NavigationTarget {
+ uri: string;
+ label?: string;
+ args?: Args;
+}
- export function getServerPort(): number | undefined {
- const envVar = process.env[SERVER_PORT];
- if (envVar) {
- return JSON.parse(envVar);
- }
- return;
+export class NavigateToExternalTargetAction implements Action {
+ static readonly KIND = 'navigateToExternalTarget';
+ readonly kind = NavigateToExternalTargetAction.KIND;
+ constructor(readonly target: NavigationTarget) { }
+ static is(action?: Action): action is NavigateToExternalTargetAction {
+ return action !== undefined && (action.kind === NavigateToExternalTargetAction.KIND)
+ && (action as NavigateToExternalTargetAction).target !== undefined;
}
}
diff --git a/packages/vscode-integration/src/action/index.ts b/packages/vscode-integration/src/actions/index.ts
similarity index 87%
rename from packages/vscode-integration/src/action/index.ts
rename to packages/vscode-integration/src/actions/index.ts
index 2df55ef..9c68392 100644
--- a/packages/vscode-integration/src/action/index.ts
+++ b/packages/vscode-integration/src/actions/index.ts
@@ -1,5 +1,5 @@
/********************************************************************************
- * Copyright (c) 2021 EclipseSource and others.
+ * Copyright (c) 2021 EclipseSource 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,10 +14,11 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-export * from './action-handler';
-export * from './action-dispatcher';
export * from './action';
-export * from './operation';
-export * from './navigation';
+export * from './export';
+export * from './save-state';
export * from './external-navigation';
export * from './markers';
+export * from './navigation';
+export * from './operation';
+export * from './selection';
diff --git a/packages/vscode-integration/src/action/markers.ts b/packages/vscode-integration/src/actions/markers.ts
similarity index 93%
rename from packages/vscode-integration/src/action/markers.ts
rename to packages/vscode-integration/src/actions/markers.ts
index 6305974..8fafd48 100644
--- a/packages/vscode-integration/src/action/markers.ts
+++ b/packages/vscode-integration/src/actions/markers.ts
@@ -1,5 +1,5 @@
/********************************************************************************
- * Copyright (c) 2021 EclipseSource and others.
+ * Copyright (c) 2021 EclipseSource 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
@@ -13,7 +13,8 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { Action } from 'sprotty-vscode-protocol';
+
+import { Action } from './action';
export interface Marker {
readonly label: string;
diff --git a/packages/vscode-integration/src/actions/navigation.ts b/packages/vscode-integration/src/actions/navigation.ts
new file mode 100644
index 0000000..07b371e
--- /dev/null
+++ b/packages/vscode-integration/src/actions/navigation.ts
@@ -0,0 +1,60 @@
+/********************************************************************************
+ * Copyright (c) 2021 EclipseSource 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 './action';
+
+interface Args {
+ [key: string]: string | number | boolean;
+}
+
+export class NavigateAction implements Action {
+ static readonly KIND = 'navigate';
+ readonly kind = NavigateAction.KIND;
+ constructor(readonly targetTypeId: string, readonly args?: Args) { }
+
+ static is(action?: Action): action is NavigateAction {
+ return action !== undefined && action.kind === NavigateAction.KIND && 'targetTypeId' in action;
+ }
+}
+
+export class FitToScreenAction implements Action {
+ static readonly KIND = 'fit';
+ constructor(public readonly elementIds: string[],
+ public readonly padding?: number,
+ public readonly maxZoom?: number,
+ public readonly animate: boolean = true,
+ public readonly kind = FitToScreenAction.KIND) {
+ }
+
+ static is(action?: Action): action is FitToScreenAction {
+ return action !== undefined && action.kind === FitToScreenAction.KIND
+ && 'elementIds' in action && 'animate' in action;
+ }
+}
+
+export class CenterAction implements Action {
+ static readonly KIND = 'center';
+ constructor(public readonly elementIds: string[],
+ public readonly animate: boolean = true,
+ public readonly retainZoom: boolean = false,
+ public readonly kind = CenterAction.KIND) {
+ }
+
+ static is(action?: Action): action is CenterAction {
+ return action !== undefined && action.kind === CenterAction.KIND
+ && 'elementIds' in action && 'animate' in action && 'retainZoom' in action;
+ }
+}
diff --git a/packages/vscode-integration/src/action/operation.ts b/packages/vscode-integration/src/actions/operation.ts
similarity index 95%
rename from packages/vscode-integration/src/action/operation.ts
rename to packages/vscode-integration/src/actions/operation.ts
index 3ff54f7..dab41b0 100644
--- a/packages/vscode-integration/src/action/operation.ts
+++ b/packages/vscode-integration/src/actions/operation.ts
@@ -13,9 +13,10 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { Action } from 'sprotty-vscode-protocol';
-export type Operation = Action;
+import { Action } from './action';
+
+type Operation = Action;
export class UndoOperation implements Operation {
static readonly KIND = 'glspUndo';
diff --git a/packages/vscode-integration/src/action/action.ts b/packages/vscode-integration/src/actions/save-state.ts
similarity index 64%
rename from packages/vscode-integration/src/action/action.ts
rename to packages/vscode-integration/src/actions/save-state.ts
index 0f5e5e1..da62847 100644
--- a/packages/vscode-integration/src/action/action.ts
+++ b/packages/vscode-integration/src/actions/save-state.ts
@@ -1,5 +1,5 @@
/********************************************************************************
- * Copyright (c) 2021 EclipseSource and others.
+ * Copyright (c) 2021 EclipseSource 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
@@ -13,45 +13,17 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { Action } from 'sprotty-vscode-protocol';
+
+import { Action } from './action';
export type JsonPrimitive = string | number | boolean;
+
export class RequestModelAction implements Action {
static readonly KIND = 'requestModel';
readonly kind = RequestModelAction.KIND;
constructor(public readonly options?: { [key: string]: JsonPrimitive },
public readonly requestId = '') { }
-
-}
-
-export class FitToScreenAction implements Action {
- static readonly KIND = 'fit';
- constructor(public readonly elementIds: string[],
- public readonly padding?: number,
- public readonly maxZoom?: number,
- public readonly animate: boolean = true,
- public readonly kind = FitToScreenAction.KIND) {
- }
-
- static is(action?: Action): action is FitToScreenAction {
- return action !== undefined && action.kind === FitToScreenAction.KIND
- && 'elementIds' in action && 'animate' in action;
- }
-}
-
-export class CenterAction implements Action {
- static readonly KIND = 'center';
- constructor(public readonly elementIds: string[],
- public readonly animate: boolean = true,
- public readonly retainZoom: boolean = false,
- public readonly kind = CenterAction.KIND) {
- }
-
- static is(action?: Action): action is CenterAction {
- return action !== undefined && action.kind === CenterAction.KIND
- && 'elementIds' in action && 'animate' in action && 'retainZoom' in action;
- }
}
export class SaveModelAction implements Action {
diff --git a/packages/vscode-integration/src/action/navigation.ts b/packages/vscode-integration/src/actions/selection.ts
similarity index 60%
rename from packages/vscode-integration/src/action/navigation.ts
rename to packages/vscode-integration/src/actions/selection.ts
index 9f7f0c0..58d42ae 100644
--- a/packages/vscode-integration/src/action/navigation.ts
+++ b/packages/vscode-integration/src/actions/selection.ts
@@ -13,18 +13,21 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { Action } from 'sprotty-vscode-protocol';
-interface Args {
- [key: string]: string | number | boolean;
-}
+import { Action } from './action';
+
+export class SelectAction implements Action {
+ static readonly KIND = 'elementSelected';
-export class NavigateAction implements Action {
- static readonly KIND = 'navigate';
- readonly kind = NavigateAction.KIND;
- constructor(readonly targetTypeId: string, readonly args?: Args) { }
+ constructor(
+ public readonly selectedElementsIDs: string[] = [],
+ public readonly deselectedElementsIDs: string[] = [],
+ public readonly kind = SelectAction.KIND
+ ) { }
- static is(action?: Action): action is NavigateAction {
- return action !== undefined && action.kind === NavigateAction.KIND && 'targetTypeId' in action;
+ static is(action?: Action): action is SelectAction {
+ return action !== undefined && action.kind === SelectAction.KIND
+ && 'selectedElementsIDs' in action
+ && 'deselectedElementsIDs' in action;
}
}
diff --git a/packages/vscode-integration/src/glsp-commands.ts b/packages/vscode-integration/src/glsp-commands.ts
deleted file mode 100644
index 0b0b8dc..0000000
--- a/packages/vscode-integration/src/glsp-commands.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-/********************************************************************************
- * Copyright (c) 2021 EclipseSource 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-vscode-protocol';
-import * as vscode from 'vscode';
-
-import { ExtensionActionDispatcher } from './action';
-import { GLSPWebViewRegistry } from './glsp-webview';
-
-export interface GLSPCommandOptions {
- readonly command: string;
- readonly extensionPrefix: string;
- readonly registry: GLSPWebViewRegistry;
- readonly action: Action;
- readonly context: vscode.ExtensionContext;
-}
-export namespace GLSPCommand {
- export const FIT_TO_SCREEN = 'diagram.fit';
- export const CENTER = 'diagram.center';
- export const LAYOUT = 'diagram.layout';
-
- export function commandId(extensionPrefix: string, commandKey: string): string {
- return `${extensionPrefix}.${commandKey}`;
- }
-
- export function registerActionCommand(options: GLSPCommandOptions): void {
- options.context.subscriptions.push(
- vscode.commands.registerCommand(commandId(options.extensionPrefix, options.command),
- () => ExtensionActionDispatcher.dispatch(options.registry, options.action))
- );
- }
-
-}
-
diff --git a/packages/vscode-integration/src/glsp-diagram-document.ts b/packages/vscode-integration/src/glsp-diagram-document.ts
deleted file mode 100644
index 7d7c93c..0000000
--- a/packages/vscode-integration/src/glsp-diagram-document.ts
+++ /dev/null
@@ -1,144 +0,0 @@
-/********************************************************************************
- * Copyright (c) 2021 EclipseSource 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, SprottyDiagramIdentifier } from 'sprotty-vscode-protocol';
-import * as vscode from 'vscode';
-
-import {
- DirtyStateChangeReason,
- ExtensionActionDispatcher,
- RedoOperation,
- RequestModelAction,
- SaveModelAction,
- SetDirtyStateAction,
- UndoOperation,
- SetMarkersAction
-} from './action';
-import { ExtensionActionHandler } from './action/action-handler';
-import { waitForEventWithTimeout } from './utils';
-import { Disposable } from './utils/disposable';
-
-export interface DiagramEditEvent {
- undo(): void;
- redo(): void;
-}
-export class GlspDiagramDocument extends Disposable implements vscode.CustomDocument, ExtensionActionHandler {
- static async create(uri: vscode.Uri, _backupId?: string): Promise {
- return new GlspDiagramDocument(uri);
- }
-
- protected readonly _onDidChange: vscode.EventEmitter;
- protected readonly _onDidSave: vscode.EventEmitter;
- protected actionDispatcher?: ExtensionActionDispatcher;
- protected diagramIdentifier: SprottyDiagramIdentifier;
-
- private readonly diagnostics: vscode.DiagnosticCollection;
-
- kinds = [
- SetDirtyStateAction.KIND,
- SetMarkersAction.KIND
- ];
-
- private constructor(readonly uri: vscode.Uri) {
- super();
- this._onDidChange = this.addDisposable(new vscode.EventEmitter());
- this._onDidSave = this.addDisposable(new vscode.EventEmitter());
- this.diagnostics = this.addDisposable(vscode.languages.createDiagnosticCollection());
- }
-
- initialize(diagramIdentifier: SprottyDiagramIdentifier, actionDispatcher: ExtensionActionDispatcher): void {
- this.diagramIdentifier = diagramIdentifier;
- this.actionDispatcher = actionDispatcher;
- }
-
- get onDidChange(): vscode.Event {
- return this._onDidChange.event;
- }
-
- get onDidSave(): vscode.Event {
- return this._onDidSave.event;
- }
-
- protected dispatchAction(action: Action): void {
- if (!this.actionDispatcher) {
- throw new Error(`Cannot dispatch action "${action.kind}" for GlspDocument with uri: "${this.uri}".
- No action dispatcher has been set`);
- }
- this.actionDispatcher.dispatch(action);
- }
-
- async backup(destination: vscode.Uri, _cancellation: vscode.CancellationToken): Promise {
- // No need to implement a custom backup. The server holds the current model state anyways.
- return {
- id: destination.toString(),
- delete: () => {/** */ }
- };
- }
-
- async save(_cancellation: vscode.CancellationToken): Promise {
- return this.dispatchAction(new SaveModelAction());
- }
-
- async saveAs(destination: vscode.Uri, cancellation: vscode.CancellationToken): Promise {
- this.dispatchAction(new SaveModelAction(destination.path));
- return waitForEventWithTimeout(this.onDidSave, 2000, 'onDidSave');
- }
-
- async revert(cancellation: vscode.CancellationToken): Promise {
- this.dispatchAction(new RequestModelAction({
- sourceUri: this.uri.toString(),
- diagramType: this.diagramIdentifier.diagramType
- }));
- }
-
- async handleAction(action: Action): Promise {
- if (SetDirtyStateAction.is(action)) {
- const reason = action.reason || '';
- if (reason === DirtyStateChangeReason.SAVE) {
- this._onDidSave.fire();
- } else if (reason === DirtyStateChangeReason.OPERATION && action.isDirty) {
- this._onDidChange.fire({
- undo: () => {
- this.dispatchAction(new UndoOperation());
- },
- redo: () => {
- this.dispatchAction(new RedoOperation());
- }
- });
- }
- }
-
- if (SetMarkersAction.is(action)) {
- const SEVERITY_MAP = {
- 'info': 2,
- 'warning': 1,
- 'error': 0
- };
-
- const updatedDiagnostics = action.markers.map(marker => new vscode.Diagnostic(
- new vscode.Range(0, 0, 0, 0), // Must have be defined as such - no workarounds
- marker.description,
- SEVERITY_MAP[marker.kind]
- ));
-
- this.diagnostics.set(this.uri, updatedDiagnostics);
-
- return true; // needs to be sent to webview
- }
-
- return false;
- }
-}
-
diff --git a/packages/vscode-integration/src/glsp-diagram-editor-context.ts b/packages/vscode-integration/src/glsp-diagram-editor-context.ts
deleted file mode 100644
index eae8b2f..0000000
--- a/packages/vscode-integration/src/glsp-diagram-editor-context.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-/********************************************************************************
- * Copyright (c) 2021 EclipseSource 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 { ActionMessageHandler, ApplicationIdProvider, BaseJsonrpcGLSPClient, GLSPClient } from '@eclipse-glsp/protocol';
-import * as net from 'net';
-import * as path from 'path';
-import { Action, ActionMessage, SprottyDiagramIdentifier } from 'sprotty-vscode-protocol';
-import * as vscode from 'vscode';
-import {
- createMessageConnection,
- Emitter,
- MessageConnection,
- SocketMessageReader,
- SocketMessageWriter
-} from 'vscode-jsonrpc';
-
-import { CenterAction, FitToScreenAction, LayoutOperation, ExtensionActionDispatcher, NavigateToExternalTargetHandler } from './action';
-import { GLSPCommand } from './glsp-commands';
-import { GlspDiagramEditorProvider } from './glsp-diagram-editor-provider';
-import { GLSPWebView } from './glsp-webview';
-import { ServerConnectionProvider } from './server-connection-provider';
-import { Disposable } from './utils/disposable';
-
-export abstract class GlspDiagramEditorContext extends Disposable {
- protected _glspClient: BaseJsonrpcGLSPClient;
- protected onReady: Promise = Promise.resolve();
-
- protected onMessageFromGLSPServerEmitter = new Emitter();
-
- public abstract readonly id: string;
- public abstract readonly diagramType: string;
- public abstract readonly extensionPrefix: string;
-
- private editorProvider: GlspDiagramEditorProvider;
-
- constructor(readonly context: vscode.ExtensionContext) {
- super();
- this.addDisposable(this.registerEditorProvider());
- this.initializeGLSPClient();
- this.registerCommands();
- }
-
- protected abstract getConnectionProvider(): ServerConnectionProvider;
-
- protected registerEditorProvider(): vscode.Disposable {
- this.editorProvider = new GlspDiagramEditorProvider(this.context, this);
- const viewType = `${this.extensionPrefix}.${GlspDiagramEditorProvider.VIEW_TYPE}`;
- return vscode.window.registerCustomEditorProvider(viewType,
- this.editorProvider
- , {
- webviewOptions: { retainContextWhenHidden: true },
- supportsMultipleEditorsPerDocument: false
- });
- }
-
- protected initializeGLSPClient(): void {
- this._glspClient = new BaseJsonrpcGLSPClient({
- id: this.id,
- name: this.extensionPrefix,
- connectionProvider: () => this.getConnectionProvider().createConnection()
- });
- this.onReady = this._glspClient.start().then(() => {
- this._glspClient.initializeServer({ applicationId: ApplicationIdProvider.get() });
- this._glspClient.onActionMessage(message => this.onMessageFromGLSPServerEmitter.fire(message));
- });
- }
-
- abstract createWebview(webviewPanel: vscode.WebviewPanel, identifier: SprottyDiagramIdentifier): GLSPWebView;
-
- registerActionHandlers(webview: GLSPWebView): void {
- webview.addActionHandler(new NavigateToExternalTargetHandler());
- }
-
- getExtensionFileUri(...segments: string[]): vscode.Uri {
- return vscode.Uri
- .file(path.join(this.context.extensionPath, ...segments));
- }
-
- onMessageFromGLSPServer(listener: ActionMessageHandler): vscode.Disposable {
- return this.onMessageFromGLSPServerEmitter.event(listener);
- }
-
- deactivateGLSPClient(): Thenable {
- this.dispose();
- return Promise.resolve(undefined);
- }
-
- async glspClient(): Promise {
- await this.onReady;
- return this._glspClient;
- }
-
- protected registerCommands(): void {
- this.registerActionCommand(GLSPCommand.FIT_TO_SCREEN, new FitToScreenAction([]));
- this.registerActionCommand(GLSPCommand.CENTER, new CenterAction([]));
- this.registerActionCommand(GLSPCommand.LAYOUT, new LayoutOperation());
- }
-
- /**
- * Register a command that dispatches a new action when triggered.
- * @param command Command id without extension prefix.
- * @param action The action that should be dispatched.
- */
- protected registerActionCommand(command: string, action: Action): void {
- GLSPCommand.registerActionCommand({
- command,
- action,
- context: this.context,
- registry: this.editorProvider.webviewRegistry,
- extensionPrefix: this.extensionPrefix
- });
- }
-
- async dispatchActionToWebview(action: Action): Promise {
- ExtensionActionDispatcher.dispatch(this.editorProvider.webviewRegistry, action);
- }
-
-}
-
-export function createSocketConnection(outSocket: net.Socket, inSocket: net.Socket): MessageConnection {
- const reader = new SocketMessageReader(inSocket);
- const writer = new SocketMessageWriter(outSocket);
- return createMessageConnection(reader, writer);
-}
diff --git a/packages/vscode-integration/src/glsp-diagram-editor-provider.ts b/packages/vscode-integration/src/glsp-diagram-editor-provider.ts
deleted file mode 100644
index 2620734..0000000
--- a/packages/vscode-integration/src/glsp-diagram-editor-provider.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-/********************************************************************************
- * Copyright (c) 2021 EclipseSource 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 { SprottyDiagramIdentifier } from 'sprotty-vscode-protocol';
-import * as vscode from 'vscode';
-
-import { GlspDiagramDocument } from './glsp-diagram-document';
-import { GlspDiagramEditorContext } from './glsp-diagram-editor-context';
-import { GLSPWebView, GLSPWebViewRegistry } from './glsp-webview';
-import { disposeAll } from './utils/disposable';
-
-export class GlspDiagramEditorProvider implements vscode.CustomEditorProvider {
- public static VIEW_TYPE = 'glspDiagram';
- readonly webviewRegistry: GLSPWebViewRegistry;
- private _onDidChangeCustomDocument: vscode.EventEmitter>;
-
- constructor(protected readonly context: vscode.ExtensionContext,
- protected readonly editorContext: GlspDiagramEditorContext) {
- this.webviewRegistry = new GLSPWebViewRegistry();
- this._onDidChangeCustomDocument = new vscode.EventEmitter>();
- }
-
- get onDidChangeCustomDocument(): vscode.Event> {
- return this._onDidChangeCustomDocument.event;
- }
-
- saveCustomDocument(document: GlspDiagramDocument, cancellation: vscode.CancellationToken): Thenable {
- return document.save(cancellation);
- }
-
- saveCustomDocumentAs(document: GlspDiagramDocument, destination: vscode.Uri, cancellation: vscode.CancellationToken): Thenable {
- return document.saveAs(destination, cancellation);
- }
- revertCustomDocument(document: GlspDiagramDocument, cancellation: vscode.CancellationToken): Thenable {
- return document.revert(cancellation);
- }
-
- backupCustomDocument(document: GlspDiagramDocument, context: vscode.CustomDocumentBackupContext, cancellation: vscode.CancellationToken):
- Thenable {
- return document.backup(context.destination, cancellation);
- }
-
- async openCustomDocument(uri: vscode.Uri, openContext: vscode.CustomDocumentOpenContext, token: vscode.CancellationToken): Promise {
- const document = await GlspDiagramDocument.create(uri, openContext.backupId);
-
- const listeners: vscode.Disposable[] = [];
- listeners.push(document.onDidChange(e => {
- this._onDidChangeCustomDocument.fire({
- document,
- ...e
- });
-
- }));
- document.onDidDispose(() => disposeAll(listeners));
- return document;
- }
-
- resolveCustomEditor(document: GlspDiagramDocument, webviewPanel: vscode.WebviewPanel, token: vscode.CancellationToken): void | Thenable {
- const identifier = this.createDiagramIdentifier(document);
- const webview = this.editorContext.createWebview(webviewPanel, identifier);
- this.webviewRegistry.add(document.uri, webview);
- webview.addActionHandler(document);
- this.editorContext.registerActionHandlers(webview);
- document.initialize(identifier, webview);
-
- return webview.connect();
- }
-
- protected createDiagramIdentifier(document: GlspDiagramDocument): SprottyDiagramIdentifier {
- const diagramType = this.editorContext.diagramType;
- const clientId = diagramType + '_' + GLSPWebView.viewCount++;
- return {
- diagramType,
- uri: serializeUri(document.uri),
- clientId
- };
- }
-
-}
-
-export function serializeUri(uri: vscode.Uri): string {
- let uriString = uri.toString();
- const match = uriString.match(/file:\/\/\/([a-z])%3A/i);
- if (match) {
- uriString = 'file:///' + match[1] + ':' + uriString.substring(match[0].length);
- }
- return uriString;
-}
diff --git a/packages/vscode-integration/src/glsp-vscode-connector.ts b/packages/vscode-integration/src/glsp-vscode-connector.ts
new file mode 100644
index 0000000..0ac82cb
--- /dev/null
+++ b/packages/vscode-integration/src/glsp-vscode-connector.ts
@@ -0,0 +1,461 @@
+/********************************************************************************
+ * Copyright (c) 2021 EclipseSource 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 * as fs from 'fs';
+
+import {
+ isActionMessage,
+ SaveModelAction,
+ RequestModelAction,
+ SetDirtyStateAction,
+ DirtyStateChangeReason,
+ UndoOperation,
+ RedoOperation,
+ SetMarkersAction,
+ NavigateToExternalTargetAction,
+ SelectAction,
+ ExportSvgAction,
+ Action,
+ ActionMessage
+} from './actions';
+
+import { GlspVscodeConnectorOptions, GlspVscodeClient } from './types';
+
+export enum MessageOrigin {
+ CLIENT,
+ SERVER
+}
+
+export interface MessageProcessingResult {
+ processedMessage: unknown;
+ messageChanged: boolean;
+}
+
+/**
+ * The `GlspVscodeConnector` acts as the bridge between GLSP-Clients and the GLSP-Server
+ * and is at the core of the Glsp-VSCode integration.
+ *
+ * It works by being providing a server that implements the `GlspVscodeServer`
+ * interface and registering clients using the `GlspVscodeConnector.registerClient`
+ * function. Messages sent between the clients and the server are then intercepted
+ * by the connector to provide functionality based on the content of the messages.
+ *
+ * Messages can be intercepted using the interceptor properties in the options
+ * argument.
+ *
+ * Selection updates can be listened to using the `onSelectionUpdate` property.
+ */
+export class GlspVscodeConnector implements vscode.Disposable {
+
+ /** Maps clientId to corresponding GlspVscodeClient. */
+ protected readonly clientMap = new Map>();
+ /** Maps Documents to corresponding clientId. */
+ protected readonly documentMap = new Map();
+ /** Maps clientId to selected elementIDs for that client. */
+ protected readonly clientSelectionMap = new Map();
+
+ protected readonly options: Required;
+ protected readonly diagnostics = vscode.languages.createDiagnosticCollection();
+ protected readonly selectionUpdateEmitter = new vscode.EventEmitter();
+ protected readonly onDocumentSavedEmitter = new vscode.EventEmitter();
+ protected readonly onDidChangeCustomDocumentEventEmitter = new vscode.EventEmitter>();
+ protected readonly disposables: vscode.Disposable[] = [];
+
+ /**
+ * A subscribable event which fires with an array containing the IDs of all
+ * selected elements when the selection of the editor changes.
+ */
+ public onSelectionUpdate: vscode.Event;
+
+ /**
+ * A subscribable event which fires when a document changed. The event body
+ * will contain that document. Use this event for the onDidChangeCustomDocument
+ * on your implementation of the `CustomEditorProvider`.
+ */
+ public onDidChangeCustomDocument: vscode.Event>;
+
+ constructor(options: GlspVscodeConnectorOptions) {
+ // Create default options
+ this.options = {
+ logging: false,
+ onBeforeReceiveMessageFromClient: (message, callback) => {
+ callback(message, true);
+ },
+ onBeforeReceiveMessageFromServer: (message, callback) => {
+ callback(message, true);
+ },
+ onBeforePropagateMessageToClient: (_originalMessage, processedMessage) => processedMessage,
+ onBeforePropagateMessageToServer: (_originalMessage, processedMessage) => processedMessage,
+ ...options
+ };
+
+ this.onSelectionUpdate = this.selectionUpdateEmitter.event;
+ this.onDidChangeCustomDocument = this.onDidChangeCustomDocumentEventEmitter.event;
+
+ // Set up message listener for server
+ const serverMessageListener = this.options.server.onServerMessage(message => {
+ if (this.options.logging) {
+ if (isActionMessage(message)) {
+ console.log(`Server (${message.clientId}): ${message.action.kind}`, message.action);
+ } else {
+ console.log('Server (no action message):', message);
+ }
+ }
+
+ // Run message through first user-provided interceptor (pre-receive)
+ this.options.onBeforeReceiveMessageFromServer(message, (newMessage, shouldBeProcessedByConnector = true) => {
+ const { processedMessage, messageChanged } = shouldBeProcessedByConnector ?
+ this.processMessage(newMessage, MessageOrigin.SERVER) :
+ { processedMessage: message, messageChanged: false };
+
+ // Run message through second user-provided interceptor (pre-send) - processed
+ const filteredMessage = this.options.onBeforePropagateMessageToClient(newMessage, processedMessage, messageChanged);
+ if (typeof filteredMessage !== 'undefined' && isActionMessage(filteredMessage)) {
+ this.sendMessageToClient(filteredMessage.clientId, filteredMessage);
+ }
+ });
+ });
+
+ this.disposables.push(
+ this.diagnostics,
+ this.selectionUpdateEmitter,
+ serverMessageListener
+ );
+ }
+
+ /**
+ * Register a client on the GLSP-VSCode connector. All communication will subsequently
+ * run through the VSCode integration. Clients do not need to be unregistered
+ * as they are automatically disposed of when the panel they belong to is closed.
+ *
+ * @param client The client to register.
+ */
+ public registerClient(client: GlspVscodeClient): void {
+ this.clientMap.set(client.clientId, client);
+ this.documentMap.set(client.document, client.clientId);
+
+ // Set up message listener for client
+ const clientMessageListener = client.onClientMessage(message => {
+ if (this.options.logging) {
+ if (isActionMessage(message)) {
+ console.log(`Client (${message.clientId}): ${message.action.kind}`, message.action);
+ } else {
+ console.log('Client (no action message):', message);
+ }
+ }
+
+ // Run message through first user-provided interceptor (pre-receive)
+ this.options.onBeforeReceiveMessageFromClient(message, (newMessage, shouldBeProcessedByConnector = true) => {
+ const { processedMessage, messageChanged } = shouldBeProcessedByConnector ?
+ this.processMessage(newMessage, MessageOrigin.CLIENT) :
+ { processedMessage: message, messageChanged: false };
+
+ const filteredMessage = this.options.onBeforePropagateMessageToServer(newMessage, processedMessage, messageChanged);
+
+ if (typeof filteredMessage !== 'undefined') {
+ this.options.server.onSendToServerEmitter.fire(filteredMessage);
+ }
+ });
+ });
+
+ const viewStateListener = client.webviewPanel.onDidChangeViewState(e => {
+ if (e.webviewPanel.active) {
+ this.selectionUpdateEmitter.fire(this.clientSelectionMap.get(client.clientId) || []);
+ }
+ });
+
+ // Cleanup when client panel is closed
+ const panelOnDisposeListener = client.webviewPanel.onDidDispose(() => {
+ this.diagnostics.set(client.document.uri, undefined); // this clears the diagnostics for the file
+ this.clientMap.delete(client.clientId);
+ this.documentMap.delete(client.document);
+ this.clientSelectionMap.delete(client.clientId);
+ viewStateListener.dispose();
+ clientMessageListener.dispose();
+ panelOnDisposeListener.dispose();
+ });
+ }
+
+ /**
+ * Send an action to the client/panel that is currently focused. If no registered
+ * panel is focused, the message will not be sent.
+ *
+ * @param action The action to send to the active client.
+ */
+ public sendActionToActiveClient(action: Action): void {
+ this.clientMap.forEach(client => {
+ if (client.webviewPanel.active) {
+ client.onSendToClientEmitter.fire({
+ clientId: client.clientId,
+ action: action,
+ __localDispatch: true
+ });
+ }
+ });
+ }
+
+ /**
+ * Send message to registered client by id.
+ *
+ * @param clientId Id of client.
+ * @param message Message to send.
+ */
+ protected sendMessageToClient(clientId: string, message: unknown): void {
+ const client = this.clientMap.get(clientId);
+ if (client) {
+ client.onSendToClientEmitter.fire(message);
+ }
+ }
+
+ /**
+ * Send action to registered client by id.
+ *
+ * @param clientId Id of client.
+ * @param action Action to send.
+ */
+ protected sendActionToClient(clientId: string, action: Action): void {
+ this.sendMessageToClient(clientId, {
+ clientId: clientId,
+ action: action,
+ __localDispatch: true
+ });
+ }
+
+ /**
+ * Provides the functionality of the VSCode integration.
+ *
+ * Incoming messages (unless intercepted) will run through this function and
+ * be acted upon by providing default functionality for VSCode.
+ *
+ * @param message The original received message.
+ * @param origin The origin of the received message.
+ * @returns An object containing the processed message and an indicator wether
+ * the message was modified.
+ */
+ protected processMessage(message: unknown, origin: MessageOrigin): MessageProcessingResult {
+ if (isActionMessage(message)) {
+ const client = this.clientMap.get(message.clientId);
+
+ // Dirty state & save actions
+ if (SetDirtyStateAction.is(message.action)) {
+ return this.handleSetDirtyStateAction(message as ActionMessage, client, origin);
+ }
+
+ // Diagnostic actions
+ if (SetMarkersAction.is(message.action)) {
+ return this.handleSetMarkersAction(message as ActionMessage, client, origin);
+ }
+
+ // External targets action
+ if (NavigateToExternalTargetAction.is(message.action)) {
+ return this.handleNavigateToExternalTargetAction(message as ActionMessage, client, origin);
+ }
+
+ // Selection action
+ if (SelectAction.is(message.action)) {
+ return this.handleSelectAction(message as ActionMessage, client, origin);
+ }
+
+ // Export SVG action
+ if (ExportSvgAction.is(message.action)) {
+ return this.handleExportSvgAction(message as ActionMessage, client, origin);
+ }
+ }
+
+ // Propagate unchanged message
+ return { processedMessage: message, messageChanged: false };
+ }
+
+ protected handleSetDirtyStateAction(
+ message: ActionMessage,
+ client: GlspVscodeClient | undefined,
+ _origin: MessageOrigin
+ ): MessageProcessingResult {
+ if (client) {
+ const reason = message.action.reason || '';
+ if (reason === DirtyStateChangeReason.SAVE) {
+ this.onDocumentSavedEmitter.fire(client.document);
+ } else if (reason === DirtyStateChangeReason.OPERATION && message.action.isDirty) {
+ this.onDidChangeCustomDocumentEventEmitter.fire({
+ document: client.document,
+ undo: () => {
+ this.sendActionToClient(client.clientId, new UndoOperation());
+ },
+ redo: () => {
+ this.sendActionToClient(client.clientId, new RedoOperation());
+ }
+ });
+ }
+ }
+
+ // Propagate unchanged message
+ return { processedMessage: message, messageChanged: false };
+ }
+
+ protected handleSetMarkersAction(
+ message: ActionMessage,
+ client: GlspVscodeClient | undefined,
+ _origin: MessageOrigin
+ ): MessageProcessingResult {
+ if (client) {
+ const SEVERITY_MAP = {
+ 'info': vscode.DiagnosticSeverity.Information,
+ 'warning': vscode.DiagnosticSeverity.Warning,
+ 'error': vscode.DiagnosticSeverity.Error
+ };
+
+ const updatedDiagnostics = message.action.markers.map(marker => new vscode.Diagnostic(
+ new vscode.Range(0, 0, 0, 0), // Must have be defined as such - no workarounds
+ marker.description,
+ SEVERITY_MAP[marker.kind]
+ ));
+
+ this.diagnostics.set(client.document.uri, updatedDiagnostics);
+ }
+
+ // Propagate unchanged message
+ return { processedMessage: message, messageChanged: false };
+ }
+
+ protected handleNavigateToExternalTargetAction(
+ message: ActionMessage,
+ _client: GlspVscodeClient | undefined,
+ _origin: MessageOrigin
+ ): MessageProcessingResult {
+ const SHOW_OPTIONS = 'jsonOpenerOptions';
+ const { uri, args } = message.action.target;
+ let showOptions = { ...args };
+
+ // Give server the possibility to provide options through the `showOptions` field by providing a
+ // stringified version of the `TextDocumentShowOptions`
+ // See: https://code.visualstudio.com/api/references/vscode-api#TextDocumentShowOptions
+ const showOptionsField = args?.[SHOW_OPTIONS];
+ if (showOptionsField) {
+ showOptions = { ...args, ...JSON.parse(showOptionsField.toString()) };
+ }
+
+ vscode.window.showTextDocument(vscode.Uri.parse(uri), showOptions)
+ .then(
+ () => undefined, // onFulfilled: Do nothing.
+ () => undefined // onRejected: Do nothing - This is needed as error handling in case the navigationTarget does not exist.
+ );
+
+ // Do not propagate action
+ return { processedMessage: undefined, messageChanged: true };
+ }
+
+ protected handleSelectAction(
+ message: ActionMessage,
+ client: GlspVscodeClient | undefined,
+ origin: MessageOrigin
+ ): MessageProcessingResult {
+ if (client) {
+ this.clientSelectionMap.set(client.clientId, message.action.selectedElementsIDs);
+ this.selectionUpdateEmitter.fire(message.action.selectedElementsIDs);
+ }
+
+ if (origin === MessageOrigin.CLIENT) {
+ // Do not propagate action if it comes from client to avoid an infinite loop, as both, client and server will mirror the Selection action
+ return { processedMessage: undefined, messageChanged: true };
+ } else {
+ // Propagate unchanged message
+ return { processedMessage: message, messageChanged: false };
+ }
+ }
+
+ protected handleExportSvgAction(
+ message: ActionMessage,
+ _client: GlspVscodeClient | undefined,
+ _origin: MessageOrigin
+ ): MessageProcessingResult {
+ vscode.window.showSaveDialog({
+ filters: { 'SVG': ['svg'] },
+ saveLabel: 'Export',
+ title: 'Export as SVG'
+ }).then(
+ (uri: vscode.Uri | undefined) => {
+ if (uri) {
+ fs.writeFile(uri.fsPath, message.action.svg, { encoding: 'utf-8' }, err => {
+ if (err) {
+ console.error(err);
+ }
+ });
+ }
+ },
+ console.error
+ );
+
+ // Do not propagate action to avoid an infinite loop, as both, client and server will mirror the Export SVG action
+ return { processedMessage: undefined, messageChanged: true };
+ }
+
+ /**
+ * Saves a document. Make sure to call this function in the `saveCustomDocument`
+ * and `saveCustomDocumentAs` functions of your `CustomEditorProvider` implementation.
+ *
+ * @param document The document to save.
+ * @param destination Optional parameter. When this parameter is provided the
+ * file will instead be saved at this location.
+ * @returns A promise that resolves when the file has been successfully saved.
+ */
+ public async saveDocument(document: D, destination?: vscode.Uri): Promise {
+ const clientId = this.documentMap.get(document);
+ if (clientId) {
+ return new Promise(resolve => {
+ const listener = this.onDocumentSavedEmitter.event(savedDocument => {
+ if (document === savedDocument) {
+ listener.dispose();
+ resolve();
+ }
+ });
+ this.sendActionToClient(clientId, new SaveModelAction(destination?.path));
+ });
+ } else {
+ if (this.options.logging) {
+ console.error('Saving failed: Document not registered');
+ }
+ throw new Error('Saving failed.');
+ }
+ }
+
+ /**
+ * Reverts a document. Make sure to call this function in the `revertCustomDocument`
+ * functions of your `CustomEditorProvider` implementation.
+ *
+ * @param document Document to revert.
+ * @param diagramType Diagram type as it is configured on the server.
+ * @returns A promise that resolves when the file has been successfully reverted.
+ */
+ public async revertDocument(document: D, diagramType: string): Promise {
+ const clientId = this.documentMap.get(document);
+ if (clientId) {
+ this.sendActionToClient(clientId, new RequestModelAction({
+ sourceUri: document.uri.toString(),
+ diagramType
+ }));
+ } else {
+ if (this.options.logging) {
+ console.error('Backup failed: Document not registered');
+ }
+ throw new Error('Backup failed.');
+ }
+ }
+
+ public dispose(): void {
+ this.disposables.forEach(disposable => disposable.dispose());
+ }
+}
diff --git a/packages/vscode-integration/src/glsp-webview.ts b/packages/vscode-integration/src/glsp-webview.ts
deleted file mode 100644
index fdc851d..0000000
--- a/packages/vscode-integration/src/glsp-webview.ts
+++ /dev/null
@@ -1,228 +0,0 @@
-/********************************************************************************
- * Copyright (c) 2020-2021 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
- ********************************************************************************/
-// Implementation is based on https://github.com/eclipse/sprotty-vscode/blob/master/sprotty-vscode-extension/src/sprotty-webview.ts
-import { GLSPClient } from '@eclipse-glsp/protocol';
-import {
- Action,
- ActionMessage,
- isActionMessage,
- isWebviewReadyMessage,
- SprottyDiagramIdentifier
-} from 'sprotty-vscode-protocol';
-import * as vscode from 'vscode';
-import { ResponseMessage } from 'vscode-jsonrpc/lib/messages';
-
-import { ExtensionActionDispatcher } from './action';
-import { ExtensionActionHandler } from './action/action-handler';
-import { GlspDiagramEditorContext } from './glsp-diagram-editor-context';
-import { Disposable } from './utils/disposable';
-
-export class GLSPWebViewRegistry {
- private registry: Map = new Map();
-
- get(uri: vscode.Uri): GLSPWebView[] {
- return this.registry.get(uri.toString()) || [];
- }
-
- add(uri: vscode.Uri, webview: GLSPWebView): void {
- const key = uri.toString();
- const webviews = this.registry.get(key) || [];
- webviews.push(webview);
- this.registry.set(key, webviews);
-
- webview.onDidDispose(() => this.delete(uri, webview));
-
- }
-
- delete(uri: vscode.Uri, webview: GLSPWebView): void {
- const key = uri.toString();
- const webviews = this.registry.get(key) || [];
- const index = webviews.indexOf(webview);
- if (index > -1) {
- webviews.splice(index, 1);
- this.registry.set(key, webviews);
- }
- }
-
- getActiveWebview(): GLSPWebView | undefined {
- let activeWebview: GLSPWebView | undefined;
- this.registry.forEach((webviews: GLSPWebView[], key: string) => {
- const activeViews = webviews.filter(webview => webview.diagramPanel.active);
- if (activeViews.length === 1) {
- activeWebview = activeViews[0];
- return;
- }
- });
- return activeWebview;
- }
-}
-
-export interface GLSPWebviewOptions {
- editorContext: GlspDiagramEditorContext;
- identifier: SprottyDiagramIdentifier;
- localResourceRoots: vscode.Uri[];
- scriptUri: vscode.Uri;
- webviewPanel: vscode.WebviewPanel;
-}
-
-export type GLSPWebviewMessage = ActionMessage | SprottyDiagramIdentifier | ResponseMessage;
-
-export class GLSPWebView extends Disposable implements ExtensionActionDispatcher {
- static viewCount = 0;
-
- protected readonly editorContext: GlspDiagramEditorContext;
- protected readonly scriptUri: vscode.Uri;
- protected readonly diagramIdentifier: SprottyDiagramIdentifier;
- readonly diagramPanel: vscode.WebviewPanel;
- protected readonly actionHandlers = new Map();
-
- protected messageQueue: (ActionMessage | SprottyDiagramIdentifier | ResponseMessage)[] = [];
- private resolveWebviewReady: () => void;
- // eslint-disable-next-line no-invalid-this
- private readonly webviewReady = new Promise(resolve => this.resolveWebviewReady = resolve);
-
- constructor(options: GLSPWebviewOptions) {
- super();
- this.editorContext = options.editorContext;
- this.diagramIdentifier = options.identifier;
- this.scriptUri = options.scriptUri;
- this.diagramPanel = this.initializeDiagramPanel(options.webviewPanel, options.localResourceRoots);
- }
-
- protected ready(): Promise {
- return this.webviewReady;
- }
-
- protected glspClient(): Promise {
- return this.editorContext.glspClient();
- }
-
- protected initializeDiagramPanel(webViewPanel: vscode.WebviewPanel, localResourceRoots: vscode.Uri[]): vscode.WebviewPanel {
- webViewPanel.webview.options = {
- localResourceRoots,
- enableScripts: true
- };
- this.initializeWebview(webViewPanel.webview);
- webViewPanel.onDidDispose(() => this.dispose());
- return webViewPanel;
- }
-
- protected initializeWebview(webview: vscode.Webview): void {
- webview.html = `
-
-
-
-
-
-
-
-
-
-
-
- `;
- }
-
- public async connect(): Promise {
- this.addDisposable(this.diagramPanel.onDidChangeViewState(event => {
- if (event.webviewPanel.visible) {
- this.messageQueue.forEach(message => this.sendToWebview(message));
- this.messageQueue = [];
- }
- this.setWebviewActiveContext(event.webviewPanel.active);
- }));
-
- this.setWebviewActiveContext(this.diagramPanel.active);
-
- this.addDisposable(this.diagramPanel.webview.onDidReceiveMessage(message => this.receiveFromWebview(message)));
- this.addDisposable(this.editorContext.onMessageFromGLSPServer(message => {
- // only handle messages that are meant for this webview
- if (message.clientId === this.diagramIdentifier.clientId) {
- this.sendToWebview(message);
- }
- }));
-
- this.sendDiagramIdentifier();
- }
-
- protected setWebviewActiveContext(isActive: boolean): void {
- vscode.commands.executeCommand('setContext', 'glsp-' + this.diagramIdentifier.diagramType + '-focused', isActive);
- }
-
- protected async sendToWebview(message: GLSPWebviewMessage): Promise {
- if (this.diagramPanel.visible) {
- if (isActionMessage(message)) {
- const shouldForwardToWebview = await this.handleLocally(message.action);
- if (shouldForwardToWebview) {
- this.diagramPanel.webview.postMessage(message);
- }
- } else {
- this.diagramPanel.webview.postMessage(message);
- }
- } else {
- this.messageQueue.push(message);
- }
- }
-
- protected async sendDiagramIdentifier(): Promise {
- await this.ready();
- this.sendToWebview(this.diagramIdentifier);
- }
-
- protected async receiveFromWebview(message: any): Promise {
- if (isWebviewReadyMessage(message)) {
- this.resolveWebviewReady();
- } else if (isActionMessage(message)) {
- const shouldForwardToServer = await this.handleLocally(message.action);
- if (shouldForwardToServer) {
- this.forwardToGlspServer(message);
- }
- }
- }
-
- protected async forwardToGlspServer(message: ActionMessage): Promise {
- const glspClient = await this.glspClient();
- glspClient.sendActionMessage(message);
- }
-
- dispatch(action: Action): void {
- this.sendToWebview({
- clientId: this.diagramIdentifier.clientId,
- action,
- __localDispatch: true
- } as ActionMessage); // TODO: Type Messages properly so that this casting isn't necessary
- }
-
- /**
- * Handle the action locally if a local action handler is present.
- * @param action The action that should be handled.
- * @returns true if the action should be further propagated
- * (either the glspServer ot the webview), false otherwise.
- */
- protected handleLocally(action: Action): Thenable {
- const actionHandler = this.actionHandlers.get(action.kind);
- if (actionHandler) {
- return actionHandler.handleAction(action);
- }
- return Promise.resolve(true);
- }
-
- addActionHandler(actionHandler: ExtensionActionHandler): void {
- actionHandler.kinds.forEach(kind => this.actionHandlers.set(kind, actionHandler));
- }
-}
diff --git a/packages/vscode-integration/src/index.ts b/packages/vscode-integration/src/index.ts
index ba45db9..44a7641 100644
--- a/packages/vscode-integration/src/index.ts
+++ b/packages/vscode-integration/src/index.ts
@@ -13,10 +13,7 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-export * from './utils';
-export * from './action';
-export * from './glsp-diagram-editor-context';
-export * from './glsp-diagram-document';
-export * from './glsp-diagram-editor-provider';
-export * from './glsp-webview';
-export * from './java-socket-server-connection-provider';
+
+export * from './actions';
+export * from './glsp-vscode-connector';
+export * from './types';
diff --git a/packages/vscode-integration/src/java-socket-server-connection-provider.ts b/packages/vscode-integration/src/java-socket-server-connection-provider.ts
deleted file mode 100644
index 306737e..0000000
--- a/packages/vscode-integration/src/java-socket-server-connection-provider.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-/********************************************************************************
- * Copyright (c) 2021 EclipseSource 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 { ChildProcess, spawn, SpawnOptions } from 'child_process';
-import * as fs from 'fs';
-import * as net from 'net';
-import { MessageConnection } from 'vscode-jsonrpc';
-
-import { createSocketConnection } from './glsp-diagram-editor-context';
-import { ServerConnectionProvider } from './server-connection-provider';
-
-export interface JavaSocketServerLaunchOptions {
- /** Path to the location of the jar file that should be launched as process */
- readonly jarPath: string;
- /** Port on which the server should listen for new client connections */
- serverPort: number;
- /** Indicates wether the server is already running or should be started in an embedded process. */
- isRunning: boolean;
- /** Indicates wether console logging of server process should be disabled*/
- noConsoleLog: boolean;
- /** Additional arguments that should be passed when starting the server process. */
- additionalArgs?: string[];
-}
-
-export namespace JavaSocketServerLaunchOptions {
- export function createDefaultOptions(): JavaSocketServerLaunchOptions {
- return {
- jarPath: '',
- serverPort: NaN,
- isRunning: false,
- noConsoleLog: false
- };
- }
-
- export function createOptions(options?: Partial): JavaSocketServerLaunchOptions {
- return options ? {
- ...createDefaultOptions,
- ...options
- } as JavaSocketServerLaunchOptions : createDefaultOptions();
- }
-
- export const START_UP_COMPLETE_MSG = '[GLSP-Server]:Startup completed';
-
-}
-export class JavaSocketServerConnectionProvider implements ServerConnectionProvider {
- protected readonly options: JavaSocketServerLaunchOptions;
- protected resolveReady: (value?: void | PromiseLike | undefined) => void;
- // eslint-disable-next-line no-invalid-this
- onReady: Promise = new Promise(resolve => this.resolveReady = resolve);
-
- constructor(partialOptions?: Partial) {
- this.options = JavaSocketServerLaunchOptions.createOptions(partialOptions);
- }
-
- public async createConnection(): Promise {
-
- const port = this.options.serverPort;
-
- if (isNaN(port)) {
- throw new Error(`Could not launch GLSP Server. The given server port is not a number: ${port}`);
- }
-
- await this.launchServer();
-
- const socket = new net.Socket();
- const connection = createSocketConnection(socket, socket);
- socket.connect(port);
- return connection;
- }
-
- protected async launchServer(): Promise {
- if (this.options.isRunning) {
- this.resolveReady();
- return this.onReady;
- }
- const jarPath = this.options.jarPath;
- if (!fs.existsSync(jarPath)) {
- throw Error(`Could not launch GLSP server. The given jar path is not valid: ${jarPath}`);
- }
- let args = ['-jar', this.options.jarPath, '--port', `${this.options.serverPort}`];
- if (this.options.additionalArgs) {
- args = [...args, ...this.options.additionalArgs];
- }
- await this.spawnProcessAsync('java', args);
- return this.onReady;
-
- }
-
- protected get processName(): string {
- return 'GLSP-Server';
- }
-
- protected spawnProcessAsync(command: string, args?: string[], options?: SpawnOptions): Promise {
- const rawProcess = spawn(command, args, options);
- rawProcess.stderr.on('data', this.processLogError.bind(this));
- rawProcess.stdout.on('data', this.processLogInfo.bind(this));
- return new Promise((resolve, reject) => {
- rawProcess.on('error', error => {
- this.onDidFailSpawnProcess(error);
- if (error.message.includes('ENOENT')) {
- const guess = command.split(/\s+/).shift();
- if (guess) {
- reject(new Error(`Failed to spawn ${guess}\nPerhaps it is not on the PATH.`));
- return;
- }
- }
- reject(error);
- });
-
- process.nextTick(() => resolve(rawProcess));
- });
- }
-
- protected onDidFailSpawnProcess(error: Error): void {
- if (!this.options.noConsoleLog) {
- console.error(`${this.processName}: ${error}`);
- }
- }
-
- protected processLogError(data: string | Buffer): void {
- if (data && !this.options.noConsoleLog) {
- console.error(`${this.processName}: ${data}`);
- }
- }
-
- protected processLogInfo(data: string | Buffer): void {
- if (data) {
- const message = data.toString();
- if (message.startsWith(JavaSocketServerLaunchOptions.START_UP_COMPLETE_MSG)) {
- this.resolveReady();
- }
- if (!this.options.noConsoleLog) {
- console.log(`${this.processName}: ${data}`);
- }
- }
- }
-
-}
diff --git a/packages/vscode-integration/src/quickstart-components/glsp-editor-provider.ts b/packages/vscode-integration/src/quickstart-components/glsp-editor-provider.ts
new file mode 100644
index 0000000..5e6874e
--- /dev/null
+++ b/packages/vscode-integration/src/quickstart-components/glsp-editor-provider.ts
@@ -0,0 +1,159 @@
+/********************************************************************************
+ * Copyright (c) 2021 EclipseSource 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.01
+ ********************************************************************************/
+
+import * as vscode from 'vscode';
+
+import { isActionMessage, isWebviewReadyMessage } from 'sprotty-vscode-protocol';
+import { GlspVscodeConnector } from '../glsp-vscode-connector';
+
+/**
+ * An extensible base class to create a CustomEditorProvider that takes care of
+ * diagram initialization and custom document events.
+ *
+ * Webview setup needs to be implemented.
+ */
+export abstract class GlspEditorProvider implements vscode.CustomEditorProvider {
+
+ /** The diagram type identifier the diagram server is responsible for. */
+ abstract diagramType: string;
+
+ /** Used to generate continuous and unique clientIds - TODO: consider replacing this with uuid. */
+ private viewCount = 0;
+
+ onDidChangeCustomDocument: vscode.Event>;
+
+ constructor(
+ protected readonly glspVscodeConnector: GlspVscodeConnector
+ ) {
+ this.onDidChangeCustomDocument = glspVscodeConnector.onDidChangeCustomDocument;
+ }
+
+ saveCustomDocument(document: vscode.CustomDocument, _cancellation: vscode.CancellationToken): Thenable {
+ return this.glspVscodeConnector.saveDocument(document);
+ }
+
+ saveCustomDocumentAs(document: vscode.CustomDocument, destination: vscode.Uri, _cancellation: vscode.CancellationToken): Thenable {
+ return this.glspVscodeConnector.saveDocument(document, destination);
+ }
+
+ revertCustomDocument(document: vscode.CustomDocument, _cancellation: vscode.CancellationToken): Thenable {
+ return this.glspVscodeConnector.revertDocument(document, this.diagramType);
+ }
+
+ backupCustomDocument(
+ _document: vscode.CustomDocument,
+ context: vscode.CustomDocumentBackupContext,
+ _cancellation: vscode.CancellationToken
+ ): Thenable {
+ // Basically do the bare minimum - which is nothing
+ return Promise.resolve({ id: context.destination.toString(), delete: () => undefined });
+ }
+
+ openCustomDocument(uri: vscode.Uri, _openContext: vscode.CustomDocumentOpenContext, _token: vscode.CancellationToken): vscode.CustomDocument | Thenable {
+ // Return the most basic implementation possible.
+ return { uri, dispose: () => undefined };
+ }
+
+ resolveCustomEditor(document: vscode.CustomDocument, webviewPanel: vscode.WebviewPanel, token: vscode.CancellationToken): void | Thenable {
+ // This is used to initialize sprotty for our diagram
+ const sprottyDiagramIdentifier = {
+ diagramType: this.diagramType,
+ uri: serializeUri(document.uri),
+ clientId: `${this.diagramType}_${this.viewCount++}`
+ };
+
+ // Promise that resolves when sprotty sends its ready-message
+ const webviewReadyPromise = new Promise(resolve => {
+ const messageListener = webviewPanel.webview.onDidReceiveMessage((message: unknown) => {
+ if (isWebviewReadyMessage(message)) {
+ resolve();
+ messageListener.dispose();
+ }
+ });
+ });
+
+ const sendMessageToWebview = async (message: unknown): Promise => {
+ webviewReadyPromise.then(() => {
+ if (webviewPanel.active) {
+ webviewPanel.webview.postMessage(message);
+ } else {
+ console.log('Message stalled for webview:', document.uri.path, message);
+ const viewStateListener = webviewPanel.onDidChangeViewState(() => {
+ viewStateListener.dispose();
+ sendMessageToWebview(message);
+ });
+ }
+ });
+ };
+
+ const receiveMessageFromServerEmitter = new vscode.EventEmitter();
+ const sendMessageToServerEmitter = new vscode.EventEmitter();
+
+ webviewPanel.onDidDispose(() => {
+ receiveMessageFromServerEmitter.dispose();
+ sendMessageToServerEmitter.dispose();
+ });
+
+ // Listen for Messages from webview (only after ready-message has been received)
+ webviewReadyPromise.then(() => {
+ webviewPanel.webview.onDidReceiveMessage((message: unknown) => {
+ if (isActionMessage(message)) {
+ sendMessageToServerEmitter.fire(message);
+ }
+ });
+ });
+
+ // Listen for Messages from server
+ receiveMessageFromServerEmitter.event(message => {
+ if (isActionMessage(message)) {
+ sendMessageToWebview(message);
+ }
+ });
+
+ // Register document/diagram panel/model in vscode connector
+ this.glspVscodeConnector.registerClient({
+ clientId: sprottyDiagramIdentifier.clientId,
+ document: document,
+ webviewPanel: webviewPanel,
+ onClientMessage: sendMessageToServerEmitter.event,
+ onSendToClientEmitter: receiveMessageFromServerEmitter
+ });
+
+ // Initialize diagram
+ sendMessageToWebview(sprottyDiagramIdentifier);
+
+ this.setUpWebview(document, webviewPanel, token, sprottyDiagramIdentifier.clientId);
+ }
+
+ /**
+ * Used to set up the webview within the webview panel.
+ */
+ abstract setUpWebview(
+ document: vscode.CustomDocument,
+ webviewPanel: vscode.WebviewPanel,
+ token: vscode.CancellationToken,
+ clientId: string
+ ): void;
+}
+
+function serializeUri(uri: vscode.Uri): string {
+ let uriString = uri.toString();
+ const match = uriString.match(/file:\/\/\/([a-z])%3A/i);
+ if (match) {
+ uriString = 'file:///' + match[1] + ':' + uriString.substring(match[0].length);
+ }
+ return uriString;
+}
diff --git a/packages/vscode-integration/src/quickstart-components/glsp-server-launcher.ts b/packages/vscode-integration/src/quickstart-components/glsp-server-launcher.ts
new file mode 100644
index 0000000..087867f
--- /dev/null
+++ b/packages/vscode-integration/src/quickstart-components/glsp-server-launcher.ts
@@ -0,0 +1,124 @@
+/********************************************************************************
+ * Copyright (c) 2021 EclipseSource 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 childProcess from 'child_process';
+import * as fs from 'fs';
+import * as vscode from 'vscode';
+
+const START_UP_COMPLETE_MSG = '[GLSP-Server]:Startup completed';
+
+interface JavaSocketServerLauncherOptions {
+ /** Path to the location of the jar file that should be launched as process */
+ readonly jarPath: string;
+ /** Port on which the server should listen for new client connections */
+ readonly serverPort: number;
+ /** Set to `true` if server stdout and stderr should be printed in extension host console. Default: `false` */
+ readonly logging?: boolean;
+ /** Additional arguments that should be passed when starting the server process. */
+ readonly additionalArgs?: string[];
+}
+
+/**
+ * This component can be used to bootstrap your extension when using the default
+ * GLSP server implementation, which you can find here:
+ * https://github.com/eclipse-glsp/glsp-server
+ *
+ * It simply starts up a server JAR located at a specified path on a specified port.
+ * You can pass additional launch arguments through the options.
+ *
+ * If you need a component to quickly connect your default GLSP server to the GLSP-VSCode
+ * integration, take a look at the `SocketGlspVscodeServer` quickstart component.
+ */
+export class GlspServerLauncher implements vscode.Disposable {
+ protected readonly options: Required;
+ protected serverProcess?: childProcess.ChildProcess;
+
+ constructor(options: JavaSocketServerLauncherOptions) {
+ // Create default options
+ this.options = {
+ logging: false,
+ additionalArgs: [],
+ ...options
+ };
+ }
+
+ /**
+ * Starts up the server.
+ */
+ async start(): Promise {
+ return new Promise(resolve => {
+ const jarPath = this.options.jarPath;
+
+ if (!fs.existsSync(jarPath)) {
+ throw Error(`Could not launch GLSP server. The given jar path is not valid: ${jarPath}`);
+ }
+
+ const args = [
+ '-jar', this.options.jarPath,
+ '--port', `${this.options.serverPort}`,
+ ...this.options.additionalArgs
+ ];
+
+ const process = childProcess.spawn('java', args);
+ this.serverProcess = process;
+
+ process.stdout.on('data', data => {
+ if (data.toString().includes(START_UP_COMPLETE_MSG)) {
+ resolve();
+ }
+
+ this.handleStdoutData(data);
+ });
+
+ process.stderr.on('data', this.handleStderrData);
+ process.on('error', this.handleProcessError);
+ });
+ }
+
+ protected handleStdoutData(data: string | Buffer): void {
+ if (this.options.logging) {
+ console.log('GLSP-Server:', data.toString());
+ }
+ }
+
+ protected handleStderrData(data: string | Buffer): void {
+ if (data && this.options.logging) {
+ console.error('GLSP-Server:', data.toString());
+ }
+ }
+
+ protected handleProcessError(error: Error): never {
+ if (this.options.logging) {
+ console.error('GLSP-Server:', error);
+ }
+
+ throw error;
+ }
+
+ /**
+ * Stops the server.
+ */
+ stop(): void {
+ if (this.serverProcess && !this.serverProcess.killed) {
+ this.serverProcess.kill('SIGINT');
+ // TODO: Think of a process that does this elegantly with the same consistency.
+ }
+ }
+
+ dispose(): void {
+ this.stop();
+ }
+}
diff --git a/packages/vscode-integration/src/utils/index.ts b/packages/vscode-integration/src/quickstart-components/index.ts
similarity index 81%
rename from packages/vscode-integration/src/utils/index.ts
rename to packages/vscode-integration/src/quickstart-components/index.ts
index 2771213..25e0cdf 100644
--- a/packages/vscode-integration/src/utils/index.ts
+++ b/packages/vscode-integration/src/quickstart-components/index.ts
@@ -1,5 +1,5 @@
/********************************************************************************
- * Copyright (c) 2021 EclipseSource and others.
+ * Copyright (c) 2021 EclipseSource 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
@@ -13,7 +13,7 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-export * from './disposable';
-export * from './promise';
-export * from './glsp-env-var';
-export * from './glsp-java-server-args';
+
+export * from './glsp-editor-provider';
+export * from './glsp-server-launcher';
+export * from './socket-glsp-vscode-server';
diff --git a/packages/vscode-integration/src/quickstart-components/socket-glsp-vscode-server.ts b/packages/vscode-integration/src/quickstart-components/socket-glsp-vscode-server.ts
new file mode 100644
index 0000000..ddd4642
--- /dev/null
+++ b/packages/vscode-integration/src/quickstart-components/socket-glsp-vscode-server.ts
@@ -0,0 +1,114 @@
+/********************************************************************************
+ * Copyright (c) 2021 EclipseSource 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 net from 'net';
+import * as vscode from 'vscode';
+import { createMessageConnection, SocketMessageReader, SocketMessageWriter } from 'vscode-jsonrpc';
+import { ApplicationIdProvider, BaseJsonrpcGLSPClient } from '@eclipse-glsp/protocol';
+import { isActionMessage } from '../actions';
+
+import { GlspVscodeServer } from '../types';
+
+interface SocketGlspVscodeServerOptions {
+ /** Port of the running server. */
+ readonly serverPort: number;
+ /** Client ID to register the jsonRPC client with on the server. */
+ readonly clientId: string;
+ /** Name to register the client with on the server. */
+ readonly clientName: string;
+}
+
+/**
+ * This component can be used to bootstrap your extension when using the default
+ * GLSP server implementation, which you can find here:
+ * https://github.com/eclipse-glsp/glsp-server
+ *
+ * It sets up a JSON-RPC connection to a server running on a specified port and
+ * provides an interface, ready to be used by the `GlspVscodeConnector` for the
+ * GLSP-VSCode integration.
+ *
+ * If you need a component to quickly start your default GLSP server, take a look
+ * at the `GlspServerStarter` quickstart component.
+ */
+export class SocketGlspVscodeServer implements GlspVscodeServer, vscode.Disposable {
+ readonly onSendToServerEmitter = new vscode.EventEmitter();
+ readonly onServerMessage: vscode.Event;
+
+ protected readonly onServerSendEmitter = new vscode.EventEmitter();
+
+ protected readonly socket = new net.Socket();
+ protected readonly glspClient: BaseJsonrpcGLSPClient;
+
+ protected readonly onReady: Promise;
+ protected setReady: () => void;
+
+ constructor(protected readonly options: SocketGlspVscodeServerOptions) {
+ this.onReady = new Promise(resolve => {
+ this.setReady = resolve;
+ });
+
+ this.onServerMessage = this.onServerSendEmitter.event;
+
+ const reader = new SocketMessageReader(this.socket);
+ const writer = new SocketMessageWriter(this.socket);
+ const connection = createMessageConnection(reader, writer);
+
+ this.glspClient = new BaseJsonrpcGLSPClient({
+ id: options.clientId,
+ name: options.clientName,
+ connectionProvider: connection
+ });
+
+ this.onSendToServerEmitter.event(message => {
+ this.onReady.then(() => {
+ if (isActionMessage(message)) {
+ this.glspClient.sendActionMessage(message);
+ }
+ });
+ });
+ }
+
+ /**
+ * Starts up the JSON-RPC client and connects it to a running server.
+ */
+ async start(): Promise {
+ this.socket.connect(this.options.serverPort);
+
+ await this.glspClient.start();
+ await this.glspClient.initializeServer({ applicationId: ApplicationIdProvider.get() });
+
+ // The listener cant be registered before `glspClient.start()` because the
+ // glspClient will reject the listener if it has not connected to the server yet.
+ this.glspClient.onActionMessage(message => {
+ this.onServerSendEmitter.fire(message);
+ });
+
+ this.setReady();
+ }
+
+ /**
+ * Stops the client. It cannot be restarted.
+ */
+ async stop(): Promise {
+ return this.glspClient.stop();
+ }
+
+ dispose(): void {
+ this.onSendToServerEmitter.dispose();
+ this.onServerSendEmitter.dispose();
+ this.stop();
+ }
+}
diff --git a/packages/vscode-integration/src/server-connection-provider.ts b/packages/vscode-integration/src/server-connection-provider.ts
deleted file mode 100644
index b519145..0000000
--- a/packages/vscode-integration/src/server-connection-provider.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/********************************************************************************
- * Copyright (c) 2021 EclipseSource 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 { MaybePromise } from '@eclipse-glsp/protocol';
-import { MessageConnection } from 'vscode-jsonrpc';
-
-export interface ServerConnectionProvider {
- createConnection(): MaybePromise;
-}
diff --git a/packages/vscode-integration/src/types.ts b/packages/vscode-integration/src/types.ts
new file mode 100644
index 0000000..8a32391
--- /dev/null
+++ b/packages/vscode-integration/src/types.ts
@@ -0,0 +1,197 @@
+/********************************************************************************
+ * Copyright (c) 2021 EclipseSource 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';
+
+/**
+ * Any clients registered on the GLSP VSCode integration need to implement this
+ * interface.
+ */
+export interface GlspVscodeClient {
+
+ /**
+ * A unique identifier for the client/panel with which the client will be registered
+ * on the server.
+ */
+ readonly clientId: string;
+
+ /**
+ * The webview belonging to the client.
+ */
+ readonly webviewPanel: vscode.WebviewPanel;
+
+ /**
+ * The document object belonging to the client;
+ */
+ readonly document: D;
+
+ /**
+ * This event emitter is used by the VSCode integration to pass messages/actions
+ * to the client. These messages can come from the server or the VSCode integration
+ * itself.
+ *
+ * You should subscribe to the attached event and pass contents of the event
+ * to the webview.
+ *
+ * Use the properties `onBeforeReceiveMessageFromServer` and `onBeforePropagateMessageToClient`
+ * of the GlspVscodeConnector in order to control what messages are propagated
+ * and processed.
+ */
+ readonly onSendToClientEmitter: vscode.EventEmitter;
+
+ /**
+ * The VSCode integration will subscribe to this event to listen to messages
+ * from the client.
+ *
+ * Fire this event with the message the client wants to send to the server.
+ *
+ * Use the properties `onBeforeReceiveMessageFromClient` and `onBeforePropagateMessageToServer`
+ * of the GlspVscodeConnector in order to control what messages are propagated
+ * and processed.
+ */
+ readonly onClientMessage: vscode.Event;
+}
+
+/**
+ * The server or server wrapper used by the VSCode integration needs to implement
+ * this interface.
+ */
+export interface GlspVscodeServer {
+
+ /**
+ * An event emitter used by the VSCode extension to send messages to the server.
+ *
+ * You should subscribe to the event attached to this emitter to receive messages
+ * from the client/VSCode integration and pass them to the server.
+ *
+ * Use the properties `onBeforeReceiveMessageFromClient` and `onBeforePropagateMessageToServer`
+ * of the GlspVscodeConnector in order to control what messages are propagated
+ * and processed.
+ */
+ readonly onSendToServerEmitter: vscode.EventEmitter;
+
+ /**
+ * An event the VSCode integration uses to receive messages from the server.
+ * The messages are then propagated to the client or processed by the VSCode
+ * integration to provide functionality.
+ *
+ * Fire this event with the message the server wants to send to the client.
+ *
+ * Use the properties `onBeforeReceiveMessageFromServer` and `onBeforePropagateMessageToClient`
+ * of the GlspVscodeConnector in order to control what messages are propagated
+ * and processed.
+ */
+ readonly onServerMessage: vscode.Event;
+}
+
+interface InterceptorCallback {
+ /**
+ * This callback controls what message should be propagated to the VSCode integration
+ * and whether the VSCode integration should process it (ie. provide functionality
+ * based on the message).
+ *
+ * @param newMessage The message to be propagated. This value can be anything,
+ * however if it is `undefined` the message will not be propagated further.
+ * @param shouldBeProcessedByConnector Optional parameter indicating whether the
+ * VSCode integration should process the message. That usually means providing
+ * functionality based on the message but also modifying it or blocking it from
+ * being propagated further.
+ */
+ (newMessage: unknown | undefined, shouldBeProcessedByConnector?: boolean): void;
+}
+
+export interface GlspVscodeConnectorOptions {
+
+ /**
+ * The GLSP server (or its wrapper) that the VSCode integration should use.
+ */
+ server: GlspVscodeServer;
+
+ /**
+ * Wether the GLSP-VSCode integration should log various events. This is useful
+ * if you want to find out what events the VSCode integration is receiving from
+ * and sending to the server and clients.
+ *
+ * Defaults to `false`.
+ */
+ logging?: boolean;
+
+ /**
+ * Optional property to intercept (and/or modify) messages sent from the client
+ * to the VSCode integration via `GlspVscodeClient.onClientSend`.
+ *
+ * @param message Contains the original message sent by the client.
+ * @param callback A callback to control how messages are handled further.
+ */
+ onBeforeReceiveMessageFromClient?: (message: unknown, callback: InterceptorCallback) => void;
+
+ /**
+ * Optional property to intercept (and/or modify) messages sent from the server
+ * to the VSCode integration via `GlspVscodeServer.onServerSend`.
+ *
+ * @param message Contains the original message sent by the client.
+ * @param callback A callback to control how messages are handled further.
+ */
+ onBeforeReceiveMessageFromServer?(message: unknown, callback: InterceptorCallback): void;
+
+ /**
+ * Optional property to intercept (and/or modify) messages sent from the VSCode
+ * integration to the server via the `GlspVscodeServer.onServerReceiveEmitter`.
+ *
+ * The returned value from this function is the message that will be propagated
+ * to the server. It can be modified to anything. Returning `undefined` will
+ * cancel the propagation.
+ *
+ * @param originalMessage The original message received by the VSCode integration
+ * from the client.
+ * @param processedMessage If the VSCode integration modified the received message
+ * in any way, this parameter will contain the modified message. If the VSCode
+ * integration did not modify the message, this parameter will be identical to
+ * `originalMessage`.
+ * @param messageChanged This parameter will indicate wether the VSCode integration
+ * modified the incoming message. In other words: Whether `originalMessage`
+ * and `processedMessage` are different.
+ * @returns A message that will be propagated to the server. If the message
+ * is `undefined` the propagation will be cancelled.
+ */
+ onBeforePropagateMessageToServer?(
+ originalMessage: unknown, processedMessage: unknown, messageChanged: boolean
+ ): unknown | undefined;
+
+ /**
+ * Optional property to intercept (and/or modify) messages sent from the VSCode
+ * integration to a client via the `GlspVscodeClient.onClientReceiveEmitter`.
+ *
+ * The returned value from this function is the message that will be propagated
+ * to the client. It can be modified to anything. Returning `undefined` will
+ * cancel the propagation.
+ *
+ * @param originalMessage The original message received by the VSCode integration
+ * from the server.
+ * @param processedMessage If the VSCode integration modified the received message
+ * in any way, this parameter will contain the modified message. If the VSCode
+ * integration did not modify the message, this parameter will be identical to
+ * `originalMessage`.
+ * @param messageChanged This parameter will indicate wether the VSCode integration
+ * modified the incoming message. In other words: Whether `originalMessage`
+ * and `processedMessage` are different.
+ * @returns A message that will be propagated to the client. If the message
+ * is `undefined` the propagation will be cancelled.
+ */
+ onBeforePropagateMessageToClient?(
+ originalMessage: unknown, processedMessage: unknown, messageChanged: boolean
+ ): unknown | undefined;
+}
diff --git a/packages/vscode-integration/src/utils/disposable.ts b/packages/vscode-integration/src/utils/disposable.ts
deleted file mode 100644
index 59a96e2..0000000
--- a/packages/vscode-integration/src/utils/disposable.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/********************************************************************************
- * Copyright (c) 2021 EclipseSource 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';
-
-export function disposeAll(toDispose: vscode.Disposable[]): void {
- while (toDispose.length) {
- const disposable = toDispose.pop();
- if (disposable) {
- disposable.dispose();
- }
- }
-}
-
-export abstract class Disposable implements vscode.Disposable {
-
- private _isDisposed = false;
-
- protected disposables: vscode.Disposable[] = [];
-
- protected _onDidDispose: vscode.EventEmitter;
-
- constructor() {
- this._onDidDispose = this.addDisposable(new vscode.EventEmitter());
- }
-
- get onDidDispose(): vscode.Event {
- return this._onDidDispose.event;
- }
- protected addDisposable(disposable: T): T {
- if (this._isDisposed) {
- disposable.dispose();
- } else {
- this.disposables.push(disposable);
- }
- return disposable;
- }
-
- dispose(): any {
- if (!this._isDisposed) {
- this._onDidDispose.fire();
- this._isDisposed = true;
- disposeAll(this.disposables);
- }
- }
-}
diff --git a/packages/vscode-integration/src/utils/promise.ts b/packages/vscode-integration/src/utils/promise.ts
deleted file mode 100644
index c88fd01..0000000
--- a/packages/vscode-integration/src/utils/promise.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/********************************************************************************
- * Copyright (c) 2021 EclipseSource 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';
-
-export function waitForEventWithTimeout(event: vscode.Event, timeout: number, eventName?: string): Promise {
- return new Promise((resolve, reject) => {
- const timer: ReturnType = setTimeout(() => {
- listener.dispose();
- reject(new Error('Timeout waiting for ' + eventName || event.toString()));
- }, timeout);
-
- const listener = event((e: E) => {
- clearTimeout(timer);
- listener.dispose();
- resolve();
- });
- });
-}
diff --git a/yarn.lock b/yarn.lock
index 17adc6e..5b75435 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4928,7 +4928,7 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
-sprotty-vscode-protocol@0.0.5, sprotty-vscode-protocol@^0.0.5:
+sprotty-vscode-protocol@^0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/sprotty-vscode-protocol/-/sprotty-vscode-protocol-0.0.5.tgz#6d0578d0094b224ea50549786ebd59dadace7fcf"
integrity sha512-nhuOLHgWEczQRjYgHE3C8oHv7cxGsv2mAacdETkMYnnBkLG3t28N68ydIt9a/lv6EhMJDwh9dLqyDBbvHPA3Wg==