aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src/xtext
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/frontend/src/xtext')
-rw-r--r--subprojects/frontend/src/xtext/BackendConfig.ts2
-rw-r--r--subprojects/frontend/src/xtext/ContentAssistService.ts18
-rw-r--r--subprojects/frontend/src/xtext/SemanticsService.ts32
-rw-r--r--subprojects/frontend/src/xtext/UpdateService.ts2
-rw-r--r--subprojects/frontend/src/xtext/ValidationService.ts44
-rw-r--r--subprojects/frontend/src/xtext/XtextClient.ts13
-rw-r--r--subprojects/frontend/src/xtext/XtextWebSocketClient.ts5
-rw-r--r--subprojects/frontend/src/xtext/xtextMessages.ts6
-rw-r--r--subprojects/frontend/src/xtext/xtextServiceResults.ts46
9 files changed, 152 insertions, 16 deletions
diff --git a/subprojects/frontend/src/xtext/BackendConfig.ts b/subprojects/frontend/src/xtext/BackendConfig.ts
index 4c7eac5f..e7043bd5 100644
--- a/subprojects/frontend/src/xtext/BackendConfig.ts
+++ b/subprojects/frontend/src/xtext/BackendConfig.ts
@@ -11,7 +11,7 @@ import { z } from 'zod';
11export const ENDPOINT = 'config.json'; 11export const ENDPOINT = 'config.json';
12 12
13const BackendConfig = z.object({ 13const BackendConfig = z.object({
14 webSocketURL: z.string().url(), 14 webSocketURL: z.string().url().optional(),
15}); 15});
16 16
17type BackendConfig = z.infer<typeof BackendConfig>; 17type BackendConfig = z.infer<typeof BackendConfig>;
diff --git a/subprojects/frontend/src/xtext/ContentAssistService.ts b/subprojects/frontend/src/xtext/ContentAssistService.ts
index fd30c4f9..ac8ab36a 100644
--- a/subprojects/frontend/src/xtext/ContentAssistService.ts
+++ b/subprojects/frontend/src/xtext/ContentAssistService.ts
@@ -248,10 +248,20 @@ export default class ContentAssistService {
248 if (lastTo === undefined) { 248 if (lastTo === undefined) {
249 return true; 249 return true;
250 } 250 }
251 const [transformedFrom, transformedTo] = this.mapRangeInclusive( 251 let transformedFrom: number;
252 lastFrom, 252 let transformedTo: number;
253 lastTo, 253 try {
254 ); 254 [transformedFrom, transformedTo] = this.mapRangeInclusive(
255 lastFrom,
256 lastTo,
257 );
258 } catch (error) {
259 if (error instanceof RangeError) {
260 log.debug('Invalidating cache due to invalid range', error);
261 return true;
262 }
263 throw error;
264 }
255 let invalidate = false; 265 let invalidate = false;
256 transaction.changes.iterChangedRanges((fromA, toA) => { 266 transaction.changes.iterChangedRanges((fromA, toA) => {
257 if (fromA < transformedFrom || toA > transformedTo) { 267 if (fromA < transformedFrom || toA > transformedTo) {
diff --git a/subprojects/frontend/src/xtext/SemanticsService.ts b/subprojects/frontend/src/xtext/SemanticsService.ts
new file mode 100644
index 00000000..d68b87a9
--- /dev/null
+++ b/subprojects/frontend/src/xtext/SemanticsService.ts
@@ -0,0 +1,32 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import type EditorStore from '../editor/EditorStore';
8
9import type ValidationService from './ValidationService';
10import { SemanticsResult } from './xtextServiceResults';
11
12export default class SemanticsService {
13 constructor(
14 private readonly store: EditorStore,
15 private readonly validationService: ValidationService,
16 ) {}
17
18 onPush(push: unknown): void {
19 const result = SemanticsResult.parse(push);
20 if ('issues' in result) {
21 this.validationService.setSemanticsIssues(result.issues);
22 } else {
23 this.validationService.setSemanticsIssues([]);
24 if ('error' in result) {
25 this.store.setSemanticsError(result.error);
26 } else {
27 this.store.setSemantics(result);
28 }
29 }
30 this.store.analysisCompleted();
31 }
32}
diff --git a/subprojects/frontend/src/xtext/UpdateService.ts b/subprojects/frontend/src/xtext/UpdateService.ts
index ee5ebde2..1ac722e1 100644
--- a/subprojects/frontend/src/xtext/UpdateService.ts
+++ b/subprojects/frontend/src/xtext/UpdateService.ts
@@ -133,6 +133,7 @@ export default class UpdateService {
133 return; 133 return;
134 } 134 }
135 log.trace('Editor delta', delta); 135 log.trace('Editor delta', delta);
136 this.store.analysisStarted();
136 const result = await this.webSocketClient.send({ 137 const result = await this.webSocketClient.send({
137 resource: this.resourceName, 138 resource: this.resourceName,
138 serviceType: 'update', 139 serviceType: 'update',
@@ -157,6 +158,7 @@ export default class UpdateService {
157 private async updateFullTextExclusive(): Promise<void> { 158 private async updateFullTextExclusive(): Promise<void> {
158 log.debug('Performing full text update'); 159 log.debug('Performing full text update');
159 this.tracker.prepareFullTextUpdateExclusive(); 160 this.tracker.prepareFullTextUpdateExclusive();
161 this.store.analysisStarted();
160 const result = await this.webSocketClient.send({ 162 const result = await this.webSocketClient.send({
161 resource: this.resourceName, 163 resource: this.resourceName,
162 serviceType: 'update', 164 serviceType: 'update',
diff --git a/subprojects/frontend/src/xtext/ValidationService.ts b/subprojects/frontend/src/xtext/ValidationService.ts
index 64fb63eb..1a896db3 100644
--- a/subprojects/frontend/src/xtext/ValidationService.ts
+++ b/subprojects/frontend/src/xtext/ValidationService.ts
@@ -9,7 +9,7 @@ import type { Diagnostic } from '@codemirror/lint';
9import type EditorStore from '../editor/EditorStore'; 9import type EditorStore from '../editor/EditorStore';
10 10
11import type UpdateService from './UpdateService'; 11import type UpdateService from './UpdateService';
12import { ValidationResult } from './xtextServiceResults'; 12import { Issue, ValidationResult } from './xtextServiceResults';
13 13
14export default class ValidationService { 14export default class ValidationService {
15 constructor( 15 constructor(
@@ -17,11 +17,41 @@ export default class ValidationService {
17 private readonly updateService: UpdateService, 17 private readonly updateService: UpdateService,
18 ) {} 18 ) {}
19 19
20 private lastValidationIssues: Issue[] = [];
21
22 private lastSemanticsIssues: Issue[] = [];
23
20 onPush(push: unknown): void { 24 onPush(push: unknown): void {
21 const { issues } = ValidationResult.parse(push); 25 ({ issues: this.lastValidationIssues } = ValidationResult.parse(push));
26 this.lastSemanticsIssues = [];
27 this.updateDiagnostics();
28 if (
29 this.lastValidationIssues.some(({ severity }) => severity === 'error')
30 ) {
31 this.store.analysisCompleted(true);
32 }
33 }
34
35 onDisconnect(): void {
36 this.store.updateDiagnostics([]);
37 this.lastValidationIssues = [];
38 this.lastSemanticsIssues = [];
39 }
40
41 setSemanticsIssues(issues: Issue[]): void {
42 this.lastSemanticsIssues = issues;
43 this.updateDiagnostics();
44 }
45
46 private updateDiagnostics(): void {
22 const allChanges = this.updateService.computeChangesSinceLastUpdate(); 47 const allChanges = this.updateService.computeChangesSinceLastUpdate();
23 const diagnostics: Diagnostic[] = []; 48 const diagnostics: Diagnostic[] = [];
24 issues.forEach(({ offset, length, severity, description }) => { 49 function createDiagnostic({
50 offset,
51 length,
52 severity,
53 description,
54 }: Issue): void {
25 if (severity === 'ignore') { 55 if (severity === 'ignore') {
26 return; 56 return;
27 } 57 }
@@ -31,11 +61,9 @@ export default class ValidationService {
31 severity, 61 severity,
32 message: description, 62 message: description,
33 }); 63 });
34 }); 64 }
65 this.lastValidationIssues.forEach(createDiagnostic);
66 this.lastSemanticsIssues.forEach(createDiagnostic);
35 this.store.updateDiagnostics(diagnostics); 67 this.store.updateDiagnostics(diagnostics);
36 } 68 }
37
38 onDisconnect(): void {
39 this.store.updateDiagnostics([]);
40 }
41} 69}
diff --git a/subprojects/frontend/src/xtext/XtextClient.ts b/subprojects/frontend/src/xtext/XtextClient.ts
index e8181af0..87778084 100644
--- a/subprojects/frontend/src/xtext/XtextClient.ts
+++ b/subprojects/frontend/src/xtext/XtextClient.ts
@@ -17,6 +17,7 @@ import getLogger from '../utils/getLogger';
17import ContentAssistService from './ContentAssistService'; 17import ContentAssistService from './ContentAssistService';
18import HighlightingService from './HighlightingService'; 18import HighlightingService from './HighlightingService';
19import OccurrencesService from './OccurrencesService'; 19import OccurrencesService from './OccurrencesService';
20import SemanticsService from './SemanticsService';
20import UpdateService from './UpdateService'; 21import UpdateService from './UpdateService';
21import ValidationService from './ValidationService'; 22import ValidationService from './ValidationService';
22import XtextWebSocketClient from './XtextWebSocketClient'; 23import XtextWebSocketClient from './XtextWebSocketClient';
@@ -37,7 +38,12 @@ export default class XtextClient {
37 38
38 private readonly occurrencesService: OccurrencesService; 39 private readonly occurrencesService: OccurrencesService;
39 40
40 constructor(store: EditorStore, private readonly pwaStore: PWAStore) { 41 private readonly semanticsService: SemanticsService;
42
43 constructor(
44 private readonly store: EditorStore,
45 private readonly pwaStore: PWAStore,
46 ) {
41 this.webSocketClient = new XtextWebSocketClient( 47 this.webSocketClient = new XtextWebSocketClient(
42 () => this.onReconnect(), 48 () => this.onReconnect(),
43 () => this.onDisconnect(), 49 () => this.onDisconnect(),
@@ -51,6 +57,7 @@ export default class XtextClient {
51 ); 57 );
52 this.validationService = new ValidationService(store, this.updateService); 58 this.validationService = new ValidationService(store, this.updateService);
53 this.occurrencesService = new OccurrencesService(store, this.updateService); 59 this.occurrencesService = new OccurrencesService(store, this.updateService);
60 this.semanticsService = new SemanticsService(store, this.validationService);
54 } 61 }
55 62
56 start(): void { 63 start(): void {
@@ -64,6 +71,7 @@ export default class XtextClient {
64 } 71 }
65 72
66 private onDisconnect(): void { 73 private onDisconnect(): void {
74 this.store.analysisCompleted(true);
67 this.highlightingService.onDisconnect(); 75 this.highlightingService.onDisconnect();
68 this.validationService.onDisconnect(); 76 this.validationService.onDisconnect();
69 this.occurrencesService.onDisconnect(); 77 this.occurrencesService.onDisconnect();
@@ -111,6 +119,9 @@ export default class XtextClient {
111 case 'validate': 119 case 'validate':
112 this.validationService.onPush(push); 120 this.validationService.onPush(push);
113 return; 121 return;
122 case 'semantics':
123 this.semanticsService.onPush(push);
124 return;
114 default: 125 default:
115 throw new Error('Unknown service'); 126 throw new Error('Unknown service');
116 } 127 }
diff --git a/subprojects/frontend/src/xtext/XtextWebSocketClient.ts b/subprojects/frontend/src/xtext/XtextWebSocketClient.ts
index 6bb7eec8..963c1d4c 100644
--- a/subprojects/frontend/src/xtext/XtextWebSocketClient.ts
+++ b/subprojects/frontend/src/xtext/XtextWebSocketClient.ts
@@ -282,7 +282,10 @@ export default class XtextWebSocketClient {
282 log.debug('Creating WebSocket'); 282 log.debug('Creating WebSocket');
283 283
284 (async () => { 284 (async () => {
285 const { webSocketURL } = await fetchBackendConfig(); 285 let { webSocketURL } = await fetchBackendConfig();
286 if (webSocketURL === undefined) {
287 webSocketURL = `${window.origin.replace(/^http/, 'ws')}/xtext-service`;
288 }
286 this.openWebSocketWithURL(webSocketURL); 289 this.openWebSocketWithURL(webSocketURL);
287 })().catch((error) => { 290 })().catch((error) => {
288 log.error('Error while initializing connection', error); 291 log.error('Error while initializing connection', error);
diff --git a/subprojects/frontend/src/xtext/xtextMessages.ts b/subprojects/frontend/src/xtext/xtextMessages.ts
index bbbff064..971720e1 100644
--- a/subprojects/frontend/src/xtext/xtextMessages.ts
+++ b/subprojects/frontend/src/xtext/xtextMessages.ts
@@ -34,7 +34,11 @@ export const XtextWebErrorResponse = z.object({
34 34
35export type XtextWebErrorResponse = z.infer<typeof XtextWebErrorResponse>; 35export type XtextWebErrorResponse = z.infer<typeof XtextWebErrorResponse>;
36 36
37export const XtextWebPushService = z.enum(['highlight', 'validate']); 37export const XtextWebPushService = z.enum([
38 'highlight',
39 'validate',
40 'semantics',
41]);
38 42
39export type XtextWebPushService = z.infer<typeof XtextWebPushService>; 43export type XtextWebPushService = z.infer<typeof XtextWebPushService>;
40 44
diff --git a/subprojects/frontend/src/xtext/xtextServiceResults.ts b/subprojects/frontend/src/xtext/xtextServiceResults.ts
index d3b467ad..caf2cf0b 100644
--- a/subprojects/frontend/src/xtext/xtextServiceResults.ts
+++ b/subprojects/frontend/src/xtext/xtextServiceResults.ts
@@ -125,3 +125,49 @@ export const FormattingResult = DocumentStateResult.extend({
125}); 125});
126 126
127export type FormattingResult = z.infer<typeof FormattingResult>; 127export type FormattingResult = z.infer<typeof FormattingResult>;
128
129export const NodeMetadata = z.object({
130 name: z.string(),
131 simpleName: z.string(),
132 kind: z.enum(['IMPLICIT', 'INDIVIDUAL', 'NEW']),
133});
134
135export type NodeMetadata = z.infer<typeof NodeMetadata>;
136
137export const RelationMetadata = z.object({
138 name: z.string(),
139 simpleName: z.string(),
140 arity: z.number().nonnegative(),
141 detail: z.union([
142 z.object({ type: z.literal('class'), abstractClass: z.boolean() }),
143 z.object({ type: z.literal('reference'), containment: z.boolean() }),
144 z.object({
145 type: z.literal('opposite'),
146 container: z.boolean(),
147 opposite: z.string(),
148 }),
149 z.object({ type: z.literal('predicate'), error: z.boolean() }),
150 z.object({ type: z.literal('builtin') }),
151 ]),
152});
153
154export type RelationMetadata = z.infer<typeof RelationMetadata>;
155
156export const SemanticsSuccessResult = z.object({
157 nodes: NodeMetadata.array(),
158 relations: RelationMetadata.array(),
159 partialInterpretation: z.record(
160 z.string(),
161 z.union([z.number(), z.string()]).array().array(),
162 ),
163});
164
165export type SemanticsSuccessResult = z.infer<typeof SemanticsSuccessResult>;
166
167export const SemanticsResult = z.union([
168 z.object({ error: z.string() }),
169 z.object({ issues: Issue.array() }),
170 SemanticsSuccessResult,
171]);
172
173export type SemanticsResult = z.infer<typeof SemanticsResult>;