Skip to content

Commit c2a70b1

Browse files
committed
Added editing features to Langium-based example
1 parent 918b000 commit c2a70b1

File tree

16 files changed

+195
-65
lines changed

16 files changed

+195
-65
lines changed

examples/states-langium/extension/package.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,19 +106,19 @@
106106
},
107107
{
108108
"command": "states.diagram.fit",
109-
"when": "states-diagram-focused"
109+
"when": "states-focused"
110110
},
111111
{
112112
"command": "states.diagram.center",
113-
"when": "states-diagram-focused"
113+
"when": "states-focused"
114114
},
115115
{
116116
"command": "states.diagram.delete",
117-
"when": "states-diagram-focused"
117+
"when": "states-focused"
118118
},
119119
{
120120
"command": "states.diagram.export",
121-
"when": "states-diagram-focused"
121+
"when": "states-focused"
122122
}
123123
],
124124
"editor/context": [
@@ -148,25 +148,25 @@
148148
"key": "alt+f",
149149
"mac": "alt+f",
150150
"command": "states.diagram.fit",
151-
"when": "states-diagram-focused"
151+
"when": "states-focused"
152152
},
153153
{
154154
"key": "alt+c",
155155
"mac": "alt+c",
156156
"command": "states.diagram.center",
157-
"when": "states-diagram-focused"
157+
"when": "states-focused"
158158
},
159159
{
160160
"key": "alt+e",
161161
"mac": "alt+e",
162162
"command": "states.diagram.export",
163-
"when": "states-diagram-focused"
163+
"when": "states-focused"
164164
},
165165
{
166166
"key": "delete",
167167
"mac": "delete",
168168
"command": "states.diagram.delete",
169-
"when": "states-diagram-focused"
169+
"when": "states-focused"
170170
}
171171
]
172172
},

examples/states-langium/extension/src/states-extension.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616

1717
import * as path from 'path';
1818
import {
19-
SprottyDiagramIdentifier, WebviewContainer, createFileUri, createWebviewHtml as doCreateWebviewHtml,
20-
registerDefaultCommands, registerTextEditorSync
19+
SprottyDiagramIdentifier, WebviewContainer, WebviewEndpoint, createFileUri, createWebviewHtml as doCreateWebviewHtml,
20+
registerDefaultCommands, registerLspEditCommands, registerTextEditorSync
2121
} from 'sprotty-vscode';
22-
import { LspSprottyEditorProvider, LspSprottyViewProvider, LspWebviewPanelManager } from 'sprotty-vscode/lib/lsp';
22+
import { LspSprottyEditorProvider, LspSprottyViewProvider, LspWebviewEndpoint, LspWebviewPanelManager } from 'sprotty-vscode/lib/lsp';
23+
import { addLspLabelEditActionHandler, addWorkspaceEditActionHandler } from 'sprotty-vscode/lib/lsp/editing';
2324
import * as vscode from 'vscode';
2425
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient/node';
2526
import { Messenger } from 'vscode-messenger';
@@ -39,6 +40,10 @@ export function activate(context: vscode.ExtensionContext) {
3940
scriptUri: createFileUri(extensionPath, 'pack', 'diagram', 'main.js'),
4041
cssUri: createFileUri(extensionPath, 'pack', 'diagram', 'main.css')
4142
});
43+
const configureEndpoint = (endpoint: WebviewEndpoint) => {
44+
addWorkspaceEditActionHandler(endpoint as LspWebviewEndpoint);
45+
addLspLabelEditActionHandler(endpoint as LspWebviewEndpoint);
46+
};
4247

4348
if (diagramMode === 'panel') {
4449
// Set up webview panel manager for freestyle webviews
@@ -48,9 +53,11 @@ export function activate(context: vscode.ExtensionContext) {
4853
languageClient,
4954
supportedFileExtensions: ['.sm'],
5055
localResourceRoots,
51-
createWebviewHtml
56+
createWebviewHtml,
57+
configureEndpoint
5258
});
5359
registerDefaultCommands(webviewPanelManager, context, { extensionPrefix: 'states' });
60+
registerLspEditCommands(webviewPanelManager, context, { extensionPrefix: 'states' });
5461
}
5562

5663
if (diagramMode === 'editor') {
@@ -61,14 +68,16 @@ export function activate(context: vscode.ExtensionContext) {
6168
languageClient,
6269
supportedFileExtensions: ['.sm'],
6370
localResourceRoots,
64-
createWebviewHtml
71+
createWebviewHtml,
72+
configureEndpoint
6573
});
6674
context.subscriptions.push(
6775
vscode.window.registerCustomEditorProvider('states', webviewEditorProvider, {
6876
webviewOptions: { retainContextWhenHidden: true }
6977
})
7078
);
7179
registerDefaultCommands(webviewEditorProvider, context, { extensionPrefix: 'states' });
80+
registerLspEditCommands(webviewEditorProvider, context, { extensionPrefix: 'states' });
7281
}
7382

7483
if (diagramMode === 'view') {
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/********************************************************************************
2+
* Copyright (c) 2023 TypeFox and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the Eclipse
10+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
* with the GNU Classpath Exception which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
********************************************************************************/
16+
17+
import { LangiumDocument, MaybePromise, URI } from 'langium';
18+
import { CodeActionProvider } from 'langium/lsp';
19+
import { CodeActionParams, Command, CodeAction, Position, WorkspaceEdit } from 'vscode-languageserver';
20+
import { StateMachine } from './generated/ast.js';
21+
22+
const CREATE_STATE_KIND = 'sprotty.create.state';
23+
const CREATE_EVENT_KIND = 'sprotty.create.event';
24+
25+
export class StatesCodeActionProvider implements CodeActionProvider {
26+
27+
getCodeActions(document: LangiumDocument<StateMachine>, params: CodeActionParams): MaybePromise<(Command | CodeAction)[] | undefined> {
28+
const sm = document.parseResult.value;
29+
const result: CodeAction[] = [];
30+
const endOfDocument = document.textDocument.positionAt(document.textDocument.getText().length);
31+
if (matchesContext(CREATE_STATE_KIND, params)) {
32+
result.push({
33+
kind: CREATE_STATE_KIND,
34+
title: 'new State',
35+
edit: createInsertWorkspaceEdit(
36+
document.uri,
37+
endOfDocument,
38+
'\n' + 'state ' + getNewName('state', sm.states.map(s => s.name))
39+
)
40+
});
41+
}
42+
if (matchesContext(CREATE_EVENT_KIND, params)) {
43+
result.push({
44+
kind: CREATE_EVENT_KIND,
45+
title: 'new Event',
46+
edit: createInsertWorkspaceEdit(
47+
document.uri,
48+
endOfDocument,
49+
'\n' + 'event '+ getNewName('event', sm.events.map(e => e.name))
50+
)
51+
});
52+
}
53+
return result;
54+
}
55+
56+
}
57+
58+
function matchesContext(kind: string, params: CodeActionParams): boolean {
59+
if (!params.context?.only) {
60+
return true;
61+
} else {
62+
return params.context.only.some(k => kind.startsWith(k));
63+
}
64+
}
65+
66+
function getNewName(prefix: string, siblings: string[]): string {
67+
for (let i = 0;; i++) {
68+
const currentName = prefix + i;
69+
if (!siblings.some(s => s === currentName)) {
70+
return currentName;
71+
}
72+
}
73+
}
74+
75+
function createInsertWorkspaceEdit(uri: URI, position: Position, text: string): WorkspaceEdit {
76+
return {
77+
changes: {
78+
[uri.toString()]: [
79+
{
80+
range: {
81+
start: position,
82+
end: position
83+
},
84+
newText: text
85+
}
86+
]
87+
}
88+
};
89+
}

examples/states-langium/language-server/src/diagram-generator.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
********************************************************************************/
1616

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

2121
export class StatesDiagramGenerator extends LangiumDiagramGenerator {
@@ -38,15 +38,17 @@ export class StatesDiagramGenerator extends LangiumDiagramGenerator {
3838
protected generateNode(state: State, ctx: GeneratorContext<StateMachine>): SNode {
3939
const { idCache } = ctx;
4040
const nodeId = idCache.uniqueId(state.name, state);
41+
const label: SLabel = {
42+
type: 'label',
43+
id: idCache.uniqueId(nodeId + '.label'),
44+
text: state.name
45+
};
46+
this.traceProvider.trace(label, state, 'name');
4147
const node = {
4248
type: 'node',
4349
id: nodeId,
4450
children: [
45-
<SLabel>{
46-
type: 'label',
47-
id: idCache.uniqueId(nodeId + '.label'),
48-
text: state.name
49-
},
51+
label,
5052
<SPort>{
5153
type: 'port',
5254
id: idCache.uniqueId(nodeId + '.newTransition')
@@ -70,17 +72,19 @@ export class StatesDiagramGenerator extends LangiumDiagramGenerator {
7072
const sourceId = idCache.getId(transition.$container);
7173
const targetId = idCache.getId(transition.state?.ref);
7274
const edgeId = idCache.uniqueId(`${sourceId}:${transition.event?.ref?.name}:${targetId}`, transition);
75+
const label: SLabel = {
76+
type: 'label:xref',
77+
id: idCache.uniqueId(edgeId + '.label'),
78+
text: transition.event?.ref?.name ?? ''
79+
}
80+
this.traceProvider.trace(label, transition, 'event');
7381
const edge = {
7482
type: 'edge',
7583
id: edgeId,
7684
sourceId: sourceId!,
7785
targetId: targetId!,
7886
children: [
79-
<SLabel & EdgeLayoutable>{
80-
type: 'label:xref',
81-
id: idCache.uniqueId(edgeId + '.label'),
82-
text: transition.event?.ref?.name
83-
}
87+
label
8488
]
8589
};
8690
this.traceProvider.trace(edge, transition);

examples/states-langium/language-server/src/states-module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { StatesDiagramGenerator } from './diagram-generator.js';
2323
import { StatesGeneratedModule, StatesGeneratedSharedModule } from './generated/module.js';
2424
import { StatesLayoutConfigurator } from './layout-config.js';
2525
import { registerValidationChecks, StatesValidator } from './states-validator.js';
26+
import { StatesCodeActionProvider } from './code-actions.js';
2627

2728
/**
2829
* Declaration of custom services - add your own service classes here.
@@ -61,6 +62,9 @@ export const StatesModule: Module<StatesServices, PartialLangiumServices & Sprot
6162
ElkFactory: () => () => new ElkConstructor({ algorithms: ['layered'] }),
6263
ElementFilter: () => new DefaultElementFilter,
6364
LayoutConfigurator: () => new StatesLayoutConfigurator
65+
},
66+
lsp: {
67+
CodeActionProvider: () => new StatesCodeActionProvider()
6468
}
6569
};
6670

examples/states-webview/css/popup.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.sprotty-popup {
2+
padding: 0;
3+
color: var(--vscode-editorHoverWidget-foreground);
4+
background: var(--vscode-editorHoverWidget-background);
5+
border-color: var(--vscode-editorHoverWidget-border);
6+
}
7+
8+
.sprotty-palette > div {
9+
padding: 0 4px;
10+
cursor: pointer;
11+
}
12+
13+
.sprotty-palette > div:hover {
14+
color: var(--vscode-textLink-activeForeground);
15+
}

examples/states-webview/src/di.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
1515
********************************************************************************/
1616

17-
import '../css/diagram.css';
1817
import 'sprotty/css/sprotty.css';
18+
import '../css/diagram.css';
19+
import '../css/popup.css';
1920

2021
import { Container, ContainerModule } from 'inversify';
2122
import {

examples/states-xtext/extension/src/states-extension.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
********************************************************************************/
1616

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

2626
export function activate(context: vscode.ExtensionContext) {
2727
languageClient = createLanguageClient(context);
28-
const webviewPanelManager = new StatesWebviewPanelManager({
28+
const webviewPanelManager = new LspWebviewPanelManager({
2929
extensionUri: context.extensionUri,
3030
defaultDiagramType: 'states-diagram',
3131
languageClient,
32-
supportedFileExtensions: ['.sm']
32+
supportedFileExtensions: ['.sm'],
33+
configureEndpoint: endpoint => {
34+
addWorkspaceEditActionHandler(endpoint as LspWebviewEndpoint);
35+
addLspLabelEditActionHandler(endpoint as LspWebviewEndpoint);
36+
}
3337
});
3438
registerDefaultCommands(webviewPanelManager, context, { extensionPrefix: 'states' });
3539
registerLspEditCommands(webviewPanelManager, context, { extensionPrefix: 'states' });
@@ -57,15 +61,6 @@ function createLanguageClient(context: vscode.ExtensionContext): LanguageClient
5761
return languageClient;
5862
}
5963

60-
class StatesWebviewPanelManager extends LspWebviewPanelManager {
61-
protected override createEndpoint(identifier: SprottyDiagramIdentifier): LspWebviewEndpoint {
62-
const endpoint = super.createEndpoint(identifier);
63-
addWorkspaceEditActionHandler(endpoint);
64-
addLspLabelEditActionHandler(endpoint);
65-
return endpoint;
66-
}
67-
}
68-
6964
export async function deactivate(): Promise<void> {
7065
if (languageClient) {
7166
await languageClient.stop();

0 commit comments

Comments
 (0)