Skip to content

Commit

Permalink
Added editing features to Langium-based example
Browse files Browse the repository at this point in the history
  • Loading branch information
spoenemann committed Jun 17, 2024
1 parent 918b000 commit c2a70b1
Show file tree
Hide file tree
Showing 16 changed files with 195 additions and 65 deletions.
16 changes: 8 additions & 8 deletions examples/states-langium/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,19 +106,19 @@
},
{
"command": "states.diagram.fit",
"when": "states-diagram-focused"
"when": "states-focused"
},
{
"command": "states.diagram.center",
"when": "states-diagram-focused"
"when": "states-focused"
},
{
"command": "states.diagram.delete",
"when": "states-diagram-focused"
"when": "states-focused"
},
{
"command": "states.diagram.export",
"when": "states-diagram-focused"
"when": "states-focused"
}
],
"editor/context": [
Expand Down Expand Up @@ -148,25 +148,25 @@
"key": "alt+f",
"mac": "alt+f",
"command": "states.diagram.fit",
"when": "states-diagram-focused"
"when": "states-focused"
},
{
"key": "alt+c",
"mac": "alt+c",
"command": "states.diagram.center",
"when": "states-diagram-focused"
"when": "states-focused"
},
{
"key": "alt+e",
"mac": "alt+e",
"command": "states.diagram.export",
"when": "states-diagram-focused"
"when": "states-focused"
},
{
"key": "delete",
"mac": "delete",
"command": "states.diagram.delete",
"when": "states-diagram-focused"
"when": "states-focused"
}
]
},
Expand Down
19 changes: 14 additions & 5 deletions examples/states-langium/extension/src/states-extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@

import * as path from 'path';
import {
SprottyDiagramIdentifier, WebviewContainer, createFileUri, createWebviewHtml as doCreateWebviewHtml,
registerDefaultCommands, registerTextEditorSync
SprottyDiagramIdentifier, WebviewContainer, WebviewEndpoint, createFileUri, createWebviewHtml as doCreateWebviewHtml,
registerDefaultCommands, registerLspEditCommands, registerTextEditorSync
} from 'sprotty-vscode';
import { LspSprottyEditorProvider, LspSprottyViewProvider, LspWebviewPanelManager } from 'sprotty-vscode/lib/lsp';
import { LspSprottyEditorProvider, LspSprottyViewProvider, LspWebviewEndpoint, LspWebviewPanelManager } from 'sprotty-vscode/lib/lsp';
import { addLspLabelEditActionHandler, addWorkspaceEditActionHandler } from 'sprotty-vscode/lib/lsp/editing';
import * as vscode from 'vscode';
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient/node';
import { Messenger } from 'vscode-messenger';
Expand All @@ -39,6 +40,10 @@ export function activate(context: vscode.ExtensionContext) {
scriptUri: createFileUri(extensionPath, 'pack', 'diagram', 'main.js'),
cssUri: createFileUri(extensionPath, 'pack', 'diagram', 'main.css')
});
const configureEndpoint = (endpoint: WebviewEndpoint) => {
addWorkspaceEditActionHandler(endpoint as LspWebviewEndpoint);
addLspLabelEditActionHandler(endpoint as LspWebviewEndpoint);
};

if (diagramMode === 'panel') {
// Set up webview panel manager for freestyle webviews
Expand All @@ -48,9 +53,11 @@ export function activate(context: vscode.ExtensionContext) {
languageClient,
supportedFileExtensions: ['.sm'],
localResourceRoots,
createWebviewHtml
createWebviewHtml,
configureEndpoint
});
registerDefaultCommands(webviewPanelManager, context, { extensionPrefix: 'states' });
registerLspEditCommands(webviewPanelManager, context, { extensionPrefix: 'states' });
}

if (diagramMode === 'editor') {
Expand All @@ -61,14 +68,16 @@ export function activate(context: vscode.ExtensionContext) {
languageClient,
supportedFileExtensions: ['.sm'],
localResourceRoots,
createWebviewHtml
createWebviewHtml,
configureEndpoint
});
context.subscriptions.push(
vscode.window.registerCustomEditorProvider('states', webviewEditorProvider, {
webviewOptions: { retainContextWhenHidden: true }
})
);
registerDefaultCommands(webviewEditorProvider, context, { extensionPrefix: 'states' });
registerLspEditCommands(webviewEditorProvider, context, { extensionPrefix: 'states' });
}

if (diagramMode === 'view') {
Expand Down
89 changes: 89 additions & 0 deletions examples/states-langium/language-server/src/code-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/********************************************************************************
* Copyright (c) 2023 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* 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 { LangiumDocument, MaybePromise, URI } from 'langium';
import { CodeActionProvider } from 'langium/lsp';
import { CodeActionParams, Command, CodeAction, Position, WorkspaceEdit } from 'vscode-languageserver';
import { StateMachine } from './generated/ast.js';

const CREATE_STATE_KIND = 'sprotty.create.state';
const CREATE_EVENT_KIND = 'sprotty.create.event';

export class StatesCodeActionProvider implements CodeActionProvider {

getCodeActions(document: LangiumDocument<StateMachine>, params: CodeActionParams): MaybePromise<(Command | CodeAction)[] | undefined> {
const sm = document.parseResult.value;
const result: CodeAction[] = [];
const endOfDocument = document.textDocument.positionAt(document.textDocument.getText().length);
if (matchesContext(CREATE_STATE_KIND, params)) {
result.push({
kind: CREATE_STATE_KIND,
title: 'new State',
edit: createInsertWorkspaceEdit(
document.uri,
endOfDocument,
'\n' + 'state ' + getNewName('state', sm.states.map(s => s.name))
)
});
}
if (matchesContext(CREATE_EVENT_KIND, params)) {
result.push({
kind: CREATE_EVENT_KIND,
title: 'new Event',
edit: createInsertWorkspaceEdit(
document.uri,
endOfDocument,
'\n' + 'event '+ getNewName('event', sm.events.map(e => e.name))
)
});
}
return result;
}

}

function matchesContext(kind: string, params: CodeActionParams): boolean {
if (!params.context?.only) {
return true;
} else {
return params.context.only.some(k => kind.startsWith(k));
}
}

function getNewName(prefix: string, siblings: string[]): string {
for (let i = 0;; i++) {
const currentName = prefix + i;
if (!siblings.some(s => s === currentName)) {
return currentName;
}
}
}

function createInsertWorkspaceEdit(uri: URI, position: Position, text: string): WorkspaceEdit {
return {
changes: {
[uri.toString()]: [
{
range: {
start: position,
end: position
},
newText: text
}
]
}
};
}
26 changes: 15 additions & 11 deletions examples/states-langium/language-server/src/diagram-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
********************************************************************************/

import { GeneratorContext, LangiumDiagramGenerator } from 'langium-sprotty';
import { SEdge, SLabel, SModelRoot, SNode, SPort, EdgeLayoutable } from 'sprotty-protocol';
import { SEdge, SLabel, SModelRoot, SNode, SPort } from 'sprotty-protocol';
import { State, StateMachine, Transition } from './generated/ast.js';

export class StatesDiagramGenerator extends LangiumDiagramGenerator {
Expand All @@ -38,15 +38,17 @@ export class StatesDiagramGenerator extends LangiumDiagramGenerator {
protected generateNode(state: State, ctx: GeneratorContext<StateMachine>): SNode {
const { idCache } = ctx;
const nodeId = idCache.uniqueId(state.name, state);
const label: SLabel = {
type: 'label',
id: idCache.uniqueId(nodeId + '.label'),
text: state.name
};
this.traceProvider.trace(label, state, 'name');
const node = {
type: 'node',
id: nodeId,
children: [
<SLabel>{
type: 'label',
id: idCache.uniqueId(nodeId + '.label'),
text: state.name
},
label,
<SPort>{
type: 'port',
id: idCache.uniqueId(nodeId + '.newTransition')
Expand All @@ -70,17 +72,19 @@ export class StatesDiagramGenerator extends LangiumDiagramGenerator {
const sourceId = idCache.getId(transition.$container);
const targetId = idCache.getId(transition.state?.ref);
const edgeId = idCache.uniqueId(`${sourceId}:${transition.event?.ref?.name}:${targetId}`, transition);
const label: SLabel = {
type: 'label:xref',
id: idCache.uniqueId(edgeId + '.label'),
text: transition.event?.ref?.name ?? ''
}
this.traceProvider.trace(label, transition, 'event');
const edge = {
type: 'edge',
id: edgeId,
sourceId: sourceId!,
targetId: targetId!,
children: [
<SLabel & EdgeLayoutable>{
type: 'label:xref',
id: idCache.uniqueId(edgeId + '.label'),
text: transition.event?.ref?.name
}
label
]
};
this.traceProvider.trace(edge, transition);
Expand Down
4 changes: 4 additions & 0 deletions examples/states-langium/language-server/src/states-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { StatesDiagramGenerator } from './diagram-generator.js';
import { StatesGeneratedModule, StatesGeneratedSharedModule } from './generated/module.js';
import { StatesLayoutConfigurator } from './layout-config.js';
import { registerValidationChecks, StatesValidator } from './states-validator.js';
import { StatesCodeActionProvider } from './code-actions.js';

/**
* Declaration of custom services - add your own service classes here.
Expand Down Expand Up @@ -61,6 +62,9 @@ export const StatesModule: Module<StatesServices, PartialLangiumServices & Sprot
ElkFactory: () => () => new ElkConstructor({ algorithms: ['layered'] }),
ElementFilter: () => new DefaultElementFilter,
LayoutConfigurator: () => new StatesLayoutConfigurator
},
lsp: {
CodeActionProvider: () => new StatesCodeActionProvider()
}
};

Expand Down
15 changes: 15 additions & 0 deletions examples/states-webview/css/popup.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.sprotty-popup {
padding: 0;
color: var(--vscode-editorHoverWidget-foreground);
background: var(--vscode-editorHoverWidget-background);
border-color: var(--vscode-editorHoverWidget-border);
}

.sprotty-palette > div {
padding: 0 4px;
cursor: pointer;
}

.sprotty-palette > div:hover {
color: var(--vscode-textLink-activeForeground);
}
3 changes: 2 additions & 1 deletion examples/states-webview/src/di.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import '../css/diagram.css';
import 'sprotty/css/sprotty.css';
import '../css/diagram.css';
import '../css/popup.css';

import { Container, ContainerModule } from 'inversify';
import {
Expand Down
19 changes: 7 additions & 12 deletions examples/states-xtext/extension/src/states-extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
********************************************************************************/

import * as path from 'path';
import { registerDefaultCommands, registerLspEditCommands, SprottyDiagramIdentifier } from 'sprotty-vscode';
import { registerDefaultCommands, registerLspEditCommands } from 'sprotty-vscode';
import { LspWebviewEndpoint, LspWebviewPanelManager } from 'sprotty-vscode/lib/lsp';
import { addLspLabelEditActionHandler, addWorkspaceEditActionHandler } from 'sprotty-vscode/lib/lsp/editing';
import * as vscode from 'vscode';
Expand All @@ -25,11 +25,15 @@ let languageClient: LanguageClient;

export function activate(context: vscode.ExtensionContext) {
languageClient = createLanguageClient(context);
const webviewPanelManager = new StatesWebviewPanelManager({
const webviewPanelManager = new LspWebviewPanelManager({
extensionUri: context.extensionUri,
defaultDiagramType: 'states-diagram',
languageClient,
supportedFileExtensions: ['.sm']
supportedFileExtensions: ['.sm'],
configureEndpoint: endpoint => {
addWorkspaceEditActionHandler(endpoint as LspWebviewEndpoint);
addLspLabelEditActionHandler(endpoint as LspWebviewEndpoint);
}
});
registerDefaultCommands(webviewPanelManager, context, { extensionPrefix: 'states' });
registerLspEditCommands(webviewPanelManager, context, { extensionPrefix: 'states' });
Expand Down Expand Up @@ -57,15 +61,6 @@ function createLanguageClient(context: vscode.ExtensionContext): LanguageClient
return languageClient;
}

class StatesWebviewPanelManager extends LspWebviewPanelManager {
protected override createEndpoint(identifier: SprottyDiagramIdentifier): LspWebviewEndpoint {
const endpoint = super.createEndpoint(identifier);
addWorkspaceEditActionHandler(endpoint);
addLspLabelEditActionHandler(endpoint);
return endpoint;
}
}

export async function deactivate(): Promise<void> {
if (languageClient) {
await languageClient.stop();
Expand Down
Loading

0 comments on commit c2a70b1

Please sign in to comment.