aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src/xtext
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-08-20 19:41:32 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-08-20 20:29:02 +0200
commita3f1e6872f4f768d14899a1e70bbdc14f32e478d (patch)
treeb2daf0c81724f31ee190f5d63eb42988086dabf2 /subprojects/frontend/src/xtext
parentfix: nullary model initialization (diff)
downloadrefinery-a3f1e6872f4f768d14899a1e70bbdc14f32e478d.tar.gz
refinery-a3f1e6872f4f768d14899a1e70bbdc14f32e478d.tar.zst
refinery-a3f1e6872f4f768d14899a1e70bbdc14f32e478d.zip
feat: improve semantics error reporting
Also makes model seeds cancellable to reduce server load during semantic analysis.
Diffstat (limited to 'subprojects/frontend/src/xtext')
-rw-r--r--subprojects/frontend/src/xtext/SemanticsService.ts28
-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.ts7
-rw-r--r--subprojects/frontend/src/xtext/xtextServiceResults.ts7
5 files changed, 79 insertions, 9 deletions
diff --git a/subprojects/frontend/src/xtext/SemanticsService.ts b/subprojects/frontend/src/xtext/SemanticsService.ts
new file mode 100644
index 00000000..50ec371a
--- /dev/null
+++ b/subprojects/frontend/src/xtext/SemanticsService.ts
@@ -0,0 +1,28 @@
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 this.validationService.setSemanticsIssues(result.issues ?? []);
21 if (result.error !== undefined) {
22 this.store.setSemanticsError(result.error);
23 } else {
24 this.store.setSemantics(push);
25 }
26 this.store.analysisCompleted();
27 }
28}
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 d145cd30..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,6 +38,8 @@ export default class XtextClient {
37 38
38 private readonly occurrencesService: OccurrencesService; 39 private readonly occurrencesService: OccurrencesService;
39 40
41 private readonly semanticsService: SemanticsService;
42
40 constructor( 43 constructor(
41 private readonly store: EditorStore, 44 private readonly store: EditorStore,
42 private readonly pwaStore: PWAStore, 45 private readonly pwaStore: PWAStore,
@@ -54,6 +57,7 @@ export default class XtextClient {
54 ); 57 );
55 this.validationService = new ValidationService(store, this.updateService); 58 this.validationService = new ValidationService(store, this.updateService);
56 this.occurrencesService = new OccurrencesService(store, this.updateService); 59 this.occurrencesService = new OccurrencesService(store, this.updateService);
60 this.semanticsService = new SemanticsService(store, this.validationService);
57 } 61 }
58 62
59 start(): void { 63 start(): void {
@@ -67,6 +71,7 @@ export default class XtextClient {
67 } 71 }
68 72
69 private onDisconnect(): void { 73 private onDisconnect(): void {
74 this.store.analysisCompleted(true);
70 this.highlightingService.onDisconnect(); 75 this.highlightingService.onDisconnect();
71 this.validationService.onDisconnect(); 76 this.validationService.onDisconnect();
72 this.occurrencesService.onDisconnect(); 77 this.occurrencesService.onDisconnect();
@@ -115,7 +120,7 @@ export default class XtextClient {
115 this.validationService.onPush(push); 120 this.validationService.onPush(push);
116 return; 121 return;
117 case 'semantics': 122 case 'semantics':
118 this.store.setSemantics(push); 123 this.semanticsService.onPush(push);
119 return; 124 return;
120 default: 125 default:
121 throw new Error('Unknown service'); 126 throw new Error('Unknown service');
diff --git a/subprojects/frontend/src/xtext/xtextServiceResults.ts b/subprojects/frontend/src/xtext/xtextServiceResults.ts
index d3b467ad..cae95771 100644
--- a/subprojects/frontend/src/xtext/xtextServiceResults.ts
+++ b/subprojects/frontend/src/xtext/xtextServiceResults.ts
@@ -125,3 +125,10 @@ 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 SemanticsResult = z.object({
130 error: z.string().optional(),
131 issues: Issue.array().optional(),
132});
133
134export type SemanticsResult = z.infer<typeof SemanticsResult>;