aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-10-30 20:14:50 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-10-31 19:26:14 +0100
commitcbf442d8fd9f72c567ebf9f036a219a9ff100487 (patch)
treea128345d9a9863bef7f3670da585bcb62d13cb34
parentfeat(web): show error count on generate button (diff)
downloadrefinery-cbf442d8fd9f72c567ebf9f036a219a9ff100487.tar.gz
refinery-cbf442d8fd9f72c567ebf9f036a219a9ff100487.tar.zst
refinery-cbf442d8fd9f72c567ebf9f036a219a9ff100487.zip
feat(web): semantic highlighting
-rw-r--r--language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java51
-rw-r--r--language-web/package.json1
-rw-r--r--language-web/src/main/js/editor/EditorParent.ts28
-rw-r--r--language-web/src/main/js/editor/EditorStore.ts9
-rw-r--r--language-web/src/main/js/editor/semanticHighlighting.ts34
-rw-r--r--language-web/src/main/js/index.tsx2
-rw-r--r--language-web/src/main/js/xtext/HighlightingService.ts43
-rw-r--r--language-web/src/main/js/xtext/ValidationService.ts17
-rw-r--r--language-web/src/main/js/xtext/XtextClient.ts10
-rw-r--r--language-web/src/main/js/xtext/xtextServiceResults.ts26
-rw-r--r--language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java2
-rw-r--r--language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java4
-rw-r--r--language-web/yarn.lock2
13 files changed, 177 insertions, 52 deletions
diff --git a/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java b/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java
index 76a67f96..d9abf12d 100644
--- a/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java
+++ b/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java
@@ -17,30 +17,20 @@ import com.google.inject.Inject;
17 17
18import tools.refinery.language.model.ProblemUtil; 18import tools.refinery.language.model.ProblemUtil;
19import tools.refinery.language.model.problem.ClassDeclaration; 19import tools.refinery.language.model.problem.ClassDeclaration;
20import tools.refinery.language.model.problem.EnumDeclaration;
21import tools.refinery.language.model.problem.NamedElement; 20import tools.refinery.language.model.problem.NamedElement;
22import tools.refinery.language.model.problem.Node; 21import tools.refinery.language.model.problem.Node;
23import tools.refinery.language.model.problem.Parameter;
24import tools.refinery.language.model.problem.PredicateDefinition; 22import tools.refinery.language.model.problem.PredicateDefinition;
25import tools.refinery.language.model.problem.ProblemPackage; 23import tools.refinery.language.model.problem.ProblemPackage;
26import tools.refinery.language.model.problem.ReferenceDeclaration; 24import tools.refinery.language.model.problem.ReferenceDeclaration;
27import tools.refinery.language.model.problem.Variable;
28 25
29public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighlightingCalculator { 26public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighlightingCalculator {
30 private static final String BUILTIN_CLASS = "cm-keyword"; 27 private static final String BUILTIN_CLASS = "builtin";
31 private static final String CLASS_CLASS = "problem-class"; 28 private static final String ABSTRACT_CLASS = "abstract";
32 private static final String ABSTRACT_CLASS = "problem-abstract"; 29 private static final String CONTAINMENT_CLASS = "containment";
33 private static final String ENUM_CLASS = "problem-enum"; 30 private static final String ERROR_CLASS = "error";
34 private static final String REFERENCE_CLASS = "problem-reference"; 31 private static final String NODE_CLASS = "node";
35 private static final String CONTAINMENT_CLASS = "problem-containment"; 32 private static final String UNIQUE_NODE_CLASS = "unique";
36 private static final String PREDICATE_CLASS = "problem-predicate"; 33 private static final String NEW_NODE_CLASS = "new";
37 private static final String ERROR_CLASS = "problem-error";
38 private static final String NODE_CLASS = "problem-node";
39 private static final String UNIQUE_NODE_CLASS = "problem-unique-node";
40 private static final String NEW_NODE_CLASS = "problem-new-node";
41 private static final String PARAMETER_CLASS = "problem-parameter";
42 private static final String VARIABLE_CLASS = "problem-variable";
43 private static final String SINGLETON_VARIABLE_CLASS = "problem-singleton-variable";
44 34
45 @Inject 35 @Inject
46 private OperationCanceledManager operationCanceledManager; 36 private OperationCanceledManager operationCanceledManager;
@@ -57,7 +47,7 @@ public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighli
57 if (!(object instanceof NamedElement)) { 47 if (!(object instanceof NamedElement)) {
58 return; 48 return;
59 } 49 }
60 String[] highlightClass = getHighlightClass(object); 50 String[] highlightClass = getHighlightClass(object, null);
61 if (highlightClass.length > 0) { 51 if (highlightClass.length > 0) {
62 highlightFeature(acceptor, object, ProblemPackage.Literals.NAMED_ELEMENT__NAME, highlightClass); 52 highlightFeature(acceptor, object, ProblemPackage.Literals.NAMED_ELEMENT__NAME, highlightClass);
63 } 53 }
@@ -80,7 +70,7 @@ public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighli
80 70
81 protected void highlightSingleValue(EObject object, EReference reference, IHighlightedPositionAcceptor acceptor) { 71 protected void highlightSingleValue(EObject object, EReference reference, IHighlightedPositionAcceptor acceptor) {
82 EObject valueObj = (EObject) object.eGet(reference); 72 EObject valueObj = (EObject) object.eGet(reference);
83 String[] highlightClass = getHighlightClass(valueObj); 73 String[] highlightClass = getHighlightClass(valueObj, reference);
84 if (highlightClass.length > 0) { 74 if (highlightClass.length > 0) {
85 highlightFeature(acceptor, object, reference, highlightClass); 75 highlightFeature(acceptor, object, reference, highlightClass);
86 } 76 }
@@ -94,41 +84,37 @@ public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighli
94 for (var i = 0; i < size; i++) { 84 for (var i = 0; i < size; i++) {
95 EObject valueInList = values.get(i); 85 EObject valueInList = values.get(i);
96 INode node = nodes.get(i); 86 INode node = nodes.get(i);
97 String[] highlightClass = getHighlightClass(valueInList); 87 String[] highlightClass = getHighlightClass(valueInList, reference);
98 if (highlightClass.length > 0) { 88 if (highlightClass.length > 0) {
99 highlightNode(acceptor, node, highlightClass); 89 highlightNode(acceptor, node, highlightClass);
100 } 90 }
101 } 91 }
102 } 92 }
103 93
104 protected String[] getHighlightClass(EObject eObject) { 94 protected String[] getHighlightClass(EObject eObject, EReference reference) {
105 if (ProblemUtil.isBuiltIn(eObject)) { 95 if (ProblemUtil.isBuiltIn(eObject)) {
106 return new String[] { BUILTIN_CLASS }; 96 return new String[] { BUILTIN_CLASS };
107 } 97 }
108 ImmutableList.Builder<String> classesBuilder = ImmutableList.builder(); 98 ImmutableList.Builder<String> classesBuilder = ImmutableList.builder();
109 if (eObject instanceof ClassDeclaration classDeclaration) { 99 if (eObject instanceof ClassDeclaration classDeclaration) {
110 classesBuilder.add(CLASS_CLASS);
111 if (classDeclaration.isAbstract()) { 100 if (classDeclaration.isAbstract()) {
112 classesBuilder.add(ABSTRACT_CLASS); 101 classesBuilder.add(ABSTRACT_CLASS);
113 } 102 }
114 } 103 }
115 if (eObject instanceof EnumDeclaration) {
116 classesBuilder.add(ENUM_CLASS);
117 }
118 if (eObject instanceof ReferenceDeclaration referenceDeclaration) { 104 if (eObject instanceof ReferenceDeclaration referenceDeclaration) {
119 classesBuilder.add(REFERENCE_CLASS);
120 if (referenceDeclaration.isContainment()) { 105 if (referenceDeclaration.isContainment()) {
121 classesBuilder.add(CONTAINMENT_CLASS); 106 classesBuilder.add(CONTAINMENT_CLASS);
122 } 107 }
123 } 108 }
124 if (eObject instanceof PredicateDefinition predicateDefinition) { 109 if (eObject instanceof PredicateDefinition predicateDefinition) {
125 classesBuilder.add(PREDICATE_CLASS);
126 if (predicateDefinition.isError()) { 110 if (predicateDefinition.isError()) {
127 classesBuilder.add(ERROR_CLASS); 111 classesBuilder.add(ERROR_CLASS);
128 } 112 }
129 } 113 }
130 if (eObject instanceof Node node) { 114 if (eObject instanceof Node node) {
131 classesBuilder.add(NODE_CLASS); 115 if (reference == ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__VARIABLE_OR_NODE) {
116 classesBuilder.add(NODE_CLASS);
117 }
132 if (ProblemUtil.isUniqueNode(node)) { 118 if (ProblemUtil.isUniqueNode(node)) {
133 classesBuilder.add(UNIQUE_NODE_CLASS); 119 classesBuilder.add(UNIQUE_NODE_CLASS);
134 } 120 }
@@ -136,15 +122,6 @@ public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighli
136 classesBuilder.add(NEW_NODE_CLASS); 122 classesBuilder.add(NEW_NODE_CLASS);
137 } 123 }
138 } 124 }
139 if (eObject instanceof Parameter) {
140 classesBuilder.add(PARAMETER_CLASS);
141 }
142 if (eObject instanceof Variable variable) {
143 classesBuilder.add(VARIABLE_CLASS);
144 if (ProblemUtil.isSingletonVariable(variable)) {
145 classesBuilder.add(SINGLETON_VARIABLE_CLASS);
146 }
147 }
148 List<String> classes = classesBuilder.build(); 125 List<String> classes = classesBuilder.build();
149 return classes.toArray(new String[0]); 126 return classes.toArray(new String[0]);
150 } 127 }
diff --git a/language-web/package.json b/language-web/package.json
index 9185f8fe..76d27865 100644
--- a/language-web/package.json
+++ b/language-web/package.json
@@ -75,6 +75,7 @@
75 "@codemirror/language": "^0.19.3", 75 "@codemirror/language": "^0.19.3",
76 "@codemirror/lint": "^0.19.2", 76 "@codemirror/lint": "^0.19.2",
77 "@codemirror/matchbrackets": "^0.19.3", 77 "@codemirror/matchbrackets": "^0.19.3",
78 "@codemirror/rangeset": "^0.19.1",
78 "@codemirror/rectangular-selection": "^0.19.1", 79 "@codemirror/rectangular-selection": "^0.19.1",
79 "@codemirror/search": "^0.19.2", 80 "@codemirror/search": "^0.19.2",
80 "@codemirror/state": "^0.19.0", 81 "@codemirror/state": "^0.19.0",
diff --git a/language-web/src/main/js/editor/EditorParent.ts b/language-web/src/main/js/editor/EditorParent.ts
index a19759a4..ea8c13b6 100644
--- a/language-web/src/main/js/editor/EditorParent.ts
+++ b/language-web/src/main/js/editor/EditorParent.ts
@@ -133,6 +133,34 @@ export const EditorParent = styled('div')(({ theme }) => {
133 '.cmt-variableName': { 133 '.cmt-variableName': {
134 color: '#c8ae9d', 134 color: '#c8ae9d',
135 }, 135 },
136 '.cmt-problem-node': {
137 '&, & .cmt-variableName': {
138 color: theme.palette.text.secondary,
139 },
140 },
141 '.cmt-problem-unique': {
142 '&, & .cmt-variableName': {
143 color: theme.palette.text.primary,
144 },
145 },
146 '.cmt-problem-abstract, .cmt-problem-new': {
147 fontStyle: 'italic',
148 },
149 '.cmt-problem-containment': {
150 fontWeight: 700,
151 },
152 '.cmt-problem-error': {
153 '&, & .cmt-typeName': {
154 color: theme.palette.error.main,
155 },
156 },
157 '.cmt-problem-builtin': {
158 '&, & .cmt-typeName, & .cmt-atom, & .cmt-variableName': {
159 color: theme.palette.primary.main,
160 fontWeight: 400,
161 fontStyle: 'normal',
162 },
163 },
136 '.cm-tooltip-autocomplete': { 164 '.cm-tooltip-autocomplete': {
137 background: theme.palette.background.paper, 165 background: theme.palette.background.paper,
138 boxShadow: `0px 2px 4px -1px rgb(0 0 0 / 20%), 166 boxShadow: `0px 2px 4px -1px rgb(0 0 0 / 20%),
diff --git a/language-web/src/main/js/editor/EditorStore.ts b/language-web/src/main/js/editor/EditorStore.ts
index 78cf763c..f47f47a0 100644
--- a/language-web/src/main/js/editor/EditorStore.ts
+++ b/language-web/src/main/js/editor/EditorStore.ts
@@ -30,6 +30,7 @@ import {
30 TransactionSpec, 30 TransactionSpec,
31} from '@codemirror/state'; 31} from '@codemirror/state';
32import { 32import {
33 DecorationSet,
33 drawSelection, 34 drawSelection,
34 EditorView, 35 EditorView,
35 highlightActiveLine, 36 highlightActiveLine,
@@ -43,8 +44,9 @@ import {
43} from 'mobx'; 44} from 'mobx';
44 45
45import { problemLanguageSupport } from '../language/problemLanguageSupport'; 46import { problemLanguageSupport } from '../language/problemLanguageSupport';
46import { getLogger } from '../utils/logger'; 47import { semanticHighlighting, setSemanticHighlighting } from './semanticHighlighting';
47import type { ThemeStore } from '../theme/ThemeStore'; 48import type { ThemeStore } from '../theme/ThemeStore';
49import { getLogger } from '../utils/logger';
48import { XtextClient } from '../xtext/XtextClient'; 50import { XtextClient } from '../xtext/XtextClient';
49 51
50const log = getLogger('editor.EditorStore'); 52const log = getLogger('editor.EditorStore');
@@ -103,6 +105,7 @@ export class EditorStore {
103 top: true, 105 top: true,
104 matchCase: true, 106 matchCase: true,
105 }), 107 }),
108 semanticHighlighting,
106 // We add the gutters to `extensions` in the order we want them to appear. 109 // We add the gutters to `extensions` in the order we want them to appear.
107 foldGutter(), 110 foldGutter(),
108 lineNumbers(), 111 lineNumbers(),
@@ -201,6 +204,10 @@ export class EditorStore {
201 return null; 204 return null;
202 } 205 }
203 206
207 updateSemanticHighlighting(decorations: DecorationSet): void {
208 this.dispatch(setSemanticHighlighting(decorations));
209 }
210
204 /** 211 /**
205 * @returns `true` if there is history to undo 212 * @returns `true` if there is history to undo
206 */ 213 */
diff --git a/language-web/src/main/js/editor/semanticHighlighting.ts b/language-web/src/main/js/editor/semanticHighlighting.ts
new file mode 100644
index 00000000..2d6804f8
--- /dev/null
+++ b/language-web/src/main/js/editor/semanticHighlighting.ts
@@ -0,0 +1,34 @@
1import { StateEffect, StateField, TransactionSpec } from '@codemirror/state';
2import { EditorView, Decoration, DecorationSet } from '@codemirror/view';
3
4const setSemanticHighlightingEffect = StateEffect.define<DecorationSet>();
5
6export function setSemanticHighlighting(decorations: DecorationSet): TransactionSpec {
7 return {
8 effects: [
9 setSemanticHighlightingEffect.of(decorations),
10 ],
11 };
12}
13
14export const semanticHighlighting = StateField.define<DecorationSet>({
15 create() {
16 return Decoration.none;
17 },
18 update(currentDecorations, transaction) {
19 let newDecorations: DecorationSet | null = null;
20 transaction.effects.forEach((effect) => {
21 if (effect.is(setSemanticHighlightingEffect)) {
22 newDecorations = effect.value;
23 }
24 });
25 if (newDecorations === null) {
26 if (transaction.docChanged) {
27 return currentDecorations.map(transaction.changes);
28 }
29 return currentDecorations;
30 }
31 return newDecorations;
32 },
33 provide: (f) => EditorView.decorations.from(f),
34});
diff --git a/language-web/src/main/js/index.tsx b/language-web/src/main/js/index.tsx
index 1b24eadb..13a62af0 100644
--- a/language-web/src/main/js/index.tsx
+++ b/language-web/src/main/js/index.tsx
@@ -24,7 +24,7 @@ enum TaxStatus {
24} 24}
25 25
26% A child cannot have any dependents. 26% A child cannot have any dependents.
27error invalidTaxStatus(Person p) <-> 27pred invalidTaxStatus(Person p) <->
28 taxStatus(p, child), 28 taxStatus(p, child),
29 children(p, _q) 29 children(p, _q)
30; taxStatus(p, retired), 30; taxStatus(p, retired),
diff --git a/language-web/src/main/js/xtext/HighlightingService.ts b/language-web/src/main/js/xtext/HighlightingService.ts
new file mode 100644
index 00000000..b8ceed20
--- /dev/null
+++ b/language-web/src/main/js/xtext/HighlightingService.ts
@@ -0,0 +1,43 @@
1import { Decoration } from '@codemirror/view';
2import { Range, RangeSet } from '@codemirror/rangeset';
3
4import type { EditorStore } from '../editor/EditorStore';
5import type { UpdateService } from './UpdateService';
6import { getLogger } from '../utils/logger';
7import { isHighlightingResult } from './xtextServiceResults';
8
9const log = getLogger('xtext.ValidationService');
10
11export class HighlightingService {
12 private store: EditorStore;
13
14 private updateService: UpdateService;
15
16 constructor(store: EditorStore, updateService: UpdateService) {
17 this.store = store;
18 this.updateService = updateService;
19 }
20
21 onPush(push: unknown): void {
22 if (!isHighlightingResult(push)) {
23 log.error('Invalid highlighting result', push);
24 return;
25 }
26 const allChanges = this.updateService.computeChangesSinceLastUpdate();
27 const decorations: Range<Decoration>[] = [];
28 push.regions.forEach(({ offset, length, styleClasses }) => {
29 if (styleClasses.length === 0) {
30 return;
31 }
32 const from = allChanges.mapPos(offset);
33 const to = allChanges.mapPos(offset + length);
34 if (to <= from) {
35 return;
36 }
37 decorations.push(Decoration.mark({
38 class: styleClasses.map((c) => `cmt-problem-${c}`).join(' '),
39 }).range(from, to));
40 });
41 this.store.updateSemanticHighlighting(RangeSet.of(decorations, true));
42 }
43}
diff --git a/language-web/src/main/js/xtext/ValidationService.ts b/language-web/src/main/js/xtext/ValidationService.ts
index 163b5535..31c8f716 100644
--- a/language-web/src/main/js/xtext/ValidationService.ts
+++ b/language-web/src/main/js/xtext/ValidationService.ts
@@ -24,15 +24,20 @@ export class ValidationService {
24 } 24 }
25 const allChanges = this.updateService.computeChangesSinceLastUpdate(); 25 const allChanges = this.updateService.computeChangesSinceLastUpdate();
26 const diagnostics: Diagnostic[] = []; 26 const diagnostics: Diagnostic[] = [];
27 push.issues.forEach((issue) => { 27 push.issues.forEach(({
28 if (issue.severity === 'ignore') { 28 offset,
29 length,
30 severity,
31 description,
32 }) => {
33 if (severity === 'ignore') {
29 return; 34 return;
30 } 35 }
31 diagnostics.push({ 36 diagnostics.push({
32 from: allChanges.mapPos(issue.offset), 37 from: allChanges.mapPos(offset),
33 to: allChanges.mapPos(issue.offset + issue.length), 38 to: allChanges.mapPos(offset + length),
34 severity: issue.severity, 39 severity,
35 message: issue.description, 40 message: description,
36 }); 41 });
37 }); 42 });
38 this.store.updateDiagnostics(diagnostics); 43 this.store.updateDiagnostics(diagnostics);
diff --git a/language-web/src/main/js/xtext/XtextClient.ts b/language-web/src/main/js/xtext/XtextClient.ts
index 7683a8ef..ccb58ab4 100644
--- a/language-web/src/main/js/xtext/XtextClient.ts
+++ b/language-web/src/main/js/xtext/XtextClient.ts
@@ -6,6 +6,7 @@ import type { Transaction } from '@codemirror/state';
6 6
7import type { EditorStore } from '../editor/EditorStore'; 7import type { EditorStore } from '../editor/EditorStore';
8import { ContentAssistService } from './ContentAssistService'; 8import { ContentAssistService } from './ContentAssistService';
9import { HighlightingService } from './HighlightingService';
9import { UpdateService } from './UpdateService'; 10import { UpdateService } from './UpdateService';
10import { getLogger } from '../utils/logger'; 11import { getLogger } from '../utils/logger';
11import { ValidationService } from './ValidationService'; 12import { ValidationService } from './ValidationService';
@@ -20,6 +21,8 @@ export class XtextClient {
20 21
21 private contentAssistService: ContentAssistService; 22 private contentAssistService: ContentAssistService;
22 23
24 private highlightingService: HighlightingService;
25
23 private validationService: ValidationService; 26 private validationService: ValidationService;
24 27
25 constructor(store: EditorStore) { 28 constructor(store: EditorStore) {
@@ -29,6 +32,7 @@ export class XtextClient {
29 ); 32 );
30 this.updateService = new UpdateService(store, this.webSocketClient); 33 this.updateService = new UpdateService(store, this.webSocketClient);
31 this.contentAssistService = new ContentAssistService(this.updateService); 34 this.contentAssistService = new ContentAssistService(this.updateService);
35 this.highlightingService = new HighlightingService(store, this.updateService);
32 this.validationService = new ValidationService(store, this.updateService); 36 this.validationService = new ValidationService(store, this.updateService);
33 } 37 }
34 38
@@ -50,12 +54,12 @@ export class XtextClient {
50 await this.updateService.updateFullText(); 54 await this.updateService.updateFullText();
51 } 55 }
52 switch (service) { 56 switch (service) {
57 case 'highlight':
58 this.highlightingService.onPush(push);
59 return;
53 case 'validate': 60 case 'validate':
54 this.validationService.onPush(push); 61 this.validationService.onPush(push);
55 return; 62 return;
56 case 'highlight':
57 // TODO
58 return;
59 default: 63 default:
60 log.error('Unknown push service:', service); 64 log.error('Unknown push service:', service);
61 break; 65 break;
diff --git a/language-web/src/main/js/xtext/xtextServiceResults.ts b/language-web/src/main/js/xtext/xtextServiceResults.ts
index 6c3d9daf..e32d49c3 100644
--- a/language-web/src/main/js/xtext/xtextServiceResults.ts
+++ b/language-web/src/main/js/xtext/xtextServiceResults.ts
@@ -198,3 +198,29 @@ export function isContentAssistResult(result: unknown): result is IContentAssist
198 return isDocumentStateResult(result) 198 return isDocumentStateResult(result)
199 && isArrayOfType(contentAssistResult.entries, isContentAssistEntry); 199 && isArrayOfType(contentAssistResult.entries, isContentAssistEntry);
200} 200}
201
202export interface IHighlightingRegion {
203 offset: number;
204
205 length: number;
206
207 styleClasses: string[];
208}
209
210export function isHighlightingRegion(value: unknown): value is IHighlightingRegion {
211 const region = value as IHighlightingRegion;
212 return typeof region === 'object'
213 && typeof region.offset === 'number'
214 && typeof region.length === 'number'
215 && isArrayOfType(region.styleClasses, (s): s is string => typeof s === 'string');
216}
217
218export interface IHighlightingResult {
219 regions: IHighlightingRegion[];
220}
221
222export function isHighlightingResult(result: unknown): result is IHighlightingResult {
223 const highlightingResult = result as IHighlightingResult;
224 return typeof highlightingResult === 'object'
225 && isArrayOfType(highlightingResult.regions, isHighlightingRegion);
226}
diff --git a/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java b/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
index 5ccd155f..d42cc15c 100644
--- a/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
+++ b/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
@@ -96,7 +96,7 @@ class ProblemWebSocketServletIntegrationTest {
96 case 0 -> session.getRemote().sendString( 96 case 0 -> session.getRemote().sendString(
97 "{\"id\":\"foo\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\",\"fullText\":\"class Person.\n\"}}"); 97 "{\"id\":\"foo\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\",\"fullText\":\"class Person.\n\"}}");
98 case 3 -> session.getRemote().sendString( 98 case 3 -> session.getRemote().sendString(
99 "{\"id\":\"bar\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\",\"requiredStateId\":\"-80000000\",\"deltaText\":\"class Car.\n\",\"deltaOffset\":\"0\",\"deltaReplaceLength\":\"0\"}}"); 99 "{\"id\":\"bar\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\",\"requiredStateId\":\"-80000000\",\"deltaText\":\"unique q.\nnode(q).\n\",\"deltaOffset\":\"0\",\"deltaReplaceLength\":\"0\"}}");
100 case 5 -> session.close(); 100 case 5 -> session.close();
101 } 101 }
102 } 102 }
diff --git a/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java b/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
index 0892954b..975d120c 100644
--- a/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
+++ b/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
@@ -79,7 +79,7 @@ class TransactionExecutorTest {
79 var stateId = updateFullText(); 79 var stateId = updateFullText();
80 var responseHandler = sendRequestAndWaitForAllResponses( 80 var responseHandler = sendRequestAndWaitForAllResponses(
81 new XtextWebRequest("bar", Map.of("resource", RESOURCE_NAME, "serviceType", "update", "requiredStateId", 81 new XtextWebRequest("bar", Map.of("resource", RESOURCE_NAME, "serviceType", "update", "requiredStateId",
82 stateId, "deltaText", "<invalid text>\n", "deltaOffset", "0", "deltaReplaceLength", "0"))); 82 stateId, "deltaText", "unique q.\nnode(q).\n<invalid text>\n", "deltaOffset", "0", "deltaReplaceLength", "0")));
83 83
84 var captor = newCaptor(); 84 var captor = newCaptor();
85 verify(responseHandler, times(3)).onResponse(captor.capture()); 85 verify(responseHandler, times(3)).onResponse(captor.capture());
@@ -92,7 +92,7 @@ class TransactionExecutorTest {
92 var stateId = updateFullText(); 92 var stateId = updateFullText();
93 var responseHandler = sendRequestAndWaitForAllResponses( 93 var responseHandler = sendRequestAndWaitForAllResponses(
94 new XtextWebRequest("bar", Map.of("resource", RESOURCE_NAME, "serviceType", "update", "requiredStateId", 94 new XtextWebRequest("bar", Map.of("resource", RESOURCE_NAME, "serviceType", "update", "requiredStateId",
95 stateId, "deltaText", "class Vehicle.\n", "deltaOffset", "0", "deltaReplaceLength", "0"))); 95 stateId, "deltaText", "unique q.\nnode(q).\n", "deltaOffset", "0", "deltaReplaceLength", "0")));
96 96
97 var captor = newCaptor(); 97 var captor = newCaptor();
98 verify(responseHandler, times(2)).onResponse(captor.capture()); 98 verify(responseHandler, times(2)).onResponse(captor.capture());
diff --git a/language-web/yarn.lock b/language-web/yarn.lock
index a29eb715..7c00795a 100644
--- a/language-web/yarn.lock
+++ b/language-web/yarn.lock
@@ -1096,7 +1096,7 @@
1096 "@codemirror/state" "^0.19.0" 1096 "@codemirror/state" "^0.19.0"
1097 "@codemirror/view" "^0.19.0" 1097 "@codemirror/view" "^0.19.0"
1098 1098
1099"@codemirror/rangeset@^0.19.0": 1099"@codemirror/rangeset@^0.19.0", "@codemirror/rangeset@^0.19.1":
1100 version "0.19.1" 1100 version "0.19.1"
1101 resolved "https://registry.yarnpkg.com/@codemirror/rangeset/-/rangeset-0.19.1.tgz#03ab6f93fb60d9ba98f810b98ed9471cba1e3854" 1101 resolved "https://registry.yarnpkg.com/@codemirror/rangeset/-/rangeset-0.19.1.tgz#03ab6f93fb60d9ba98f810b98ed9471cba1e3854"
1102 integrity sha512-WaKTEw8JB/3QFlQzpdgRoklopcWvG8O/Xp+rxxOfFKYTaeaejpY/tjpyBBg+Ea65Ka3m7+pPp9d5j/oR2rd9NA== 1102 integrity sha512-WaKTEw8JB/3QFlQzpdgRoklopcWvG8O/Xp+rxxOfFKYTaeaejpY/tjpyBBg+Ea65Ka3m7+pPp9d5j/oR2rd9NA==