diff options
Diffstat (limited to 'subprojects/frontend/src/xtext')
-rw-r--r-- | subprojects/frontend/src/xtext/BackendConfig.ts | 2 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/ContentAssistService.ts | 18 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/SemanticsService.ts | 32 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/UpdateService.ts | 2 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/ValidationService.ts | 44 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/XtextClient.ts | 13 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/XtextWebSocketClient.ts | 5 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/xtextMessages.ts | 6 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/xtextServiceResults.ts | 46 |
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'; | |||
11 | export const ENDPOINT = 'config.json'; | 11 | export const ENDPOINT = 'config.json'; |
12 | 12 | ||
13 | const BackendConfig = z.object({ | 13 | const BackendConfig = z.object({ |
14 | webSocketURL: z.string().url(), | 14 | webSocketURL: z.string().url().optional(), |
15 | }); | 15 | }); |
16 | 16 | ||
17 | type BackendConfig = z.infer<typeof BackendConfig>; | 17 | type 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 | |||
7 | import type EditorStore from '../editor/EditorStore'; | ||
8 | |||
9 | import type ValidationService from './ValidationService'; | ||
10 | import { SemanticsResult } from './xtextServiceResults'; | ||
11 | |||
12 | export 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'; | |||
9 | import type EditorStore from '../editor/EditorStore'; | 9 | import type EditorStore from '../editor/EditorStore'; |
10 | 10 | ||
11 | import type UpdateService from './UpdateService'; | 11 | import type UpdateService from './UpdateService'; |
12 | import { ValidationResult } from './xtextServiceResults'; | 12 | import { Issue, ValidationResult } from './xtextServiceResults'; |
13 | 13 | ||
14 | export default class ValidationService { | 14 | export 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'; | |||
17 | import ContentAssistService from './ContentAssistService'; | 17 | import ContentAssistService from './ContentAssistService'; |
18 | import HighlightingService from './HighlightingService'; | 18 | import HighlightingService from './HighlightingService'; |
19 | import OccurrencesService from './OccurrencesService'; | 19 | import OccurrencesService from './OccurrencesService'; |
20 | import SemanticsService from './SemanticsService'; | ||
20 | import UpdateService from './UpdateService'; | 21 | import UpdateService from './UpdateService'; |
21 | import ValidationService from './ValidationService'; | 22 | import ValidationService from './ValidationService'; |
22 | import XtextWebSocketClient from './XtextWebSocketClient'; | 23 | import 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 | ||
35 | export type XtextWebErrorResponse = z.infer<typeof XtextWebErrorResponse>; | 35 | export type XtextWebErrorResponse = z.infer<typeof XtextWebErrorResponse>; |
36 | 36 | ||
37 | export const XtextWebPushService = z.enum(['highlight', 'validate']); | 37 | export const XtextWebPushService = z.enum([ |
38 | 'highlight', | ||
39 | 'validate', | ||
40 | 'semantics', | ||
41 | ]); | ||
38 | 42 | ||
39 | export type XtextWebPushService = z.infer<typeof XtextWebPushService>; | 43 | export 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 | ||
127 | export type FormattingResult = z.infer<typeof FormattingResult>; | 127 | export type FormattingResult = z.infer<typeof FormattingResult>; |
128 | |||
129 | export const NodeMetadata = z.object({ | ||
130 | name: z.string(), | ||
131 | simpleName: z.string(), | ||
132 | kind: z.enum(['IMPLICIT', 'INDIVIDUAL', 'NEW']), | ||
133 | }); | ||
134 | |||
135 | export type NodeMetadata = z.infer<typeof NodeMetadata>; | ||
136 | |||
137 | export 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 | |||
154 | export type RelationMetadata = z.infer<typeof RelationMetadata>; | ||
155 | |||
156 | export 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 | |||
165 | export type SemanticsSuccessResult = z.infer<typeof SemanticsSuccessResult>; | ||
166 | |||
167 | export const SemanticsResult = z.union([ | ||
168 | z.object({ error: z.string() }), | ||
169 | z.object({ issues: Issue.array() }), | ||
170 | SemanticsSuccessResult, | ||
171 | ]); | ||
172 | |||
173 | export type SemanticsResult = z.infer<typeof SemanticsResult>; | ||