aboutsummaryrefslogtreecommitdiffstats
path: root/language-web
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-10-30 13:48:52 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-10-31 19:26:13 +0100
commitcdb493b0a47bcf64e8e670b94fa399fcd731f531 (patch)
treeb6b03aec77ef87a2dda7585be7884a30c65d93f5 /language-web
parentfeat(web): add xtext content assist (diff)
downloadrefinery-cdb493b0a47bcf64e8e670b94fa399fcd731f531.tar.gz
refinery-cdb493b0a47bcf64e8e670b94fa399fcd731f531.tar.zst
refinery-cdb493b0a47bcf64e8e670b94fa399fcd731f531.zip
chore(web): refactor xtext client
Diffstat (limited to 'language-web')
-rw-r--r--language-web/.eslintrc.ci.js1
-rw-r--r--language-web/build.gradle6
-rw-r--r--language-web/package.json2
-rw-r--r--language-web/src/main/css/xtext/xtext-codemirror.css58
-rw-r--r--language-web/src/main/images/error_an.gifbin553 -> 0 bytes
-rw-r--r--language-web/src/main/images/info_an.gifbin101 -> 0 bytes
-rw-r--r--language-web/src/main/images/warning_an.gifbin522 -> 0 bytes
-rw-r--r--language-web/src/main/js/editor/EditorStore.ts4
-rw-r--r--language-web/src/main/js/language/folding.ts (renamed from language-web/src/main/js/editor/folding.ts)0
-rw-r--r--language-web/src/main/js/language/indentation.ts (renamed from language-web/src/main/js/editor/indentation.ts)0
-rw-r--r--language-web/src/main/js/language/problem.grammar (renamed from language-web/src/main/js/editor/problem.grammar)0
-rw-r--r--language-web/src/main/js/language/problemLanguageSupport.ts (renamed from language-web/src/main/js/editor/problemLanguageSupport.ts)0
-rw-r--r--language-web/src/main/js/xtext/CodeMirrorEditorContext.js111
-rw-r--r--language-web/src/main/js/xtext/ContentAssistService.ts133
-rw-r--r--language-web/src/main/js/xtext/ServiceBuilder.js285
-rw-r--r--language-web/src/main/js/xtext/UpdateService.ts (renamed from language-web/src/main/js/editor/XtextClient.ts)231
-rw-r--r--language-web/src/main/js/xtext/ValidationService.ts40
-rw-r--r--language-web/src/main/js/xtext/XtextClient.ts73
-rw-r--r--language-web/src/main/js/xtext/XtextWebSocketClient.ts (renamed from language-web/src/main/js/editor/XtextWebSocketClient.ts)0
-rw-r--r--language-web/src/main/js/xtext/compatibility.js63
-rw-r--r--language-web/src/main/js/xtext/services/ContentAssistService.js132
-rw-r--r--language-web/src/main/js/xtext/services/FormattingService.js52
-rw-r--r--language-web/src/main/js/xtext/services/HighlightingService.js33
-rw-r--r--language-web/src/main/js/xtext/services/HoverService.js59
-rw-r--r--language-web/src/main/js/xtext/services/LoadResourceService.js42
-rw-r--r--language-web/src/main/js/xtext/services/OccurrencesService.js39
-rw-r--r--language-web/src/main/js/xtext/services/SaveResourceService.js32
-rw-r--r--language-web/src/main/js/xtext/services/UpdateService.js159
-rw-r--r--language-web/src/main/js/xtext/services/ValidationService.js33
-rw-r--r--language-web/src/main/js/xtext/services/XtextService.js280
-rw-r--r--language-web/src/main/js/xtext/xtext-codemirror.d.ts43
-rw-r--r--language-web/src/main/js/xtext/xtext-codemirror.js473
-rw-r--r--language-web/src/main/js/xtext/xtextMessages.ts (renamed from language-web/src/main/js/editor/xtextMessages.ts)0
-rw-r--r--language-web/src/main/js/xtext/xtextServiceResults.ts (renamed from language-web/src/main/js/editor/xtextServiceResults.ts)0
-rw-r--r--language-web/tsconfig.json3
-rw-r--r--language-web/tsconfig.sonar.json3
-rw-r--r--language-web/webpack.config.js4
37 files changed, 289 insertions, 2105 deletions
diff --git a/language-web/.eslintrc.ci.js b/language-web/.eslintrc.ci.js
index b4c83bb8..804f66de 100644
--- a/language-web/.eslintrc.ci.js
+++ b/language-web/.eslintrc.ci.js
@@ -28,6 +28,5 @@ module.exports = {
28 ignorePatterns: [ 28 ignorePatterns: [
29 '*.js', 29 '*.js',
30 'build/**/*', 30 'build/**/*',
31 'src/main/js/xtext/**/*',
32 ], 31 ],
33}; 32};
diff --git a/language-web/build.gradle b/language-web/build.gradle
index 6aaf546b..ea2f1269 100644
--- a/language-web/build.gradle
+++ b/language-web/build.gradle
@@ -47,7 +47,7 @@ def installFrontend = tasks.named('installFrontend')
47 47
48def generateLezerGrammar = tasks.register('generateLezerGrammar', RunNpmYarn) { 48def generateLezerGrammar = tasks.register('generateLezerGrammar', RunNpmYarn) {
49 dependsOn installFrontend 49 dependsOn installFrontend
50 inputs.file('src/main/js/editor/problem.grammar') 50 inputs.file('src/main/js/language/problem.grammar')
51 inputs.files('package.json', 'yarn.lock') 51 inputs.files('package.json', 'yarn.lock')
52 outputs.file "${buildDir}/generated/sources/lezer/problem.ts" 52 outputs.file "${buildDir}/generated/sources/lezer/problem.ts"
53 outputs.file "${buildDir}/generated/sources/lezer/problem.terms.ts" 53 outputs.file "${buildDir}/generated/sources/lezer/problem.terms.ts"
@@ -153,10 +153,6 @@ sonarqube.properties {
153 'src/main/html', 153 'src/main/html',
154 'src/main/js', 154 'src/main/js',
155 ] 155 ]
156 properties['sonar.exclusions'] += [
157 'src/main/css/xtext/**',
158 'src/main/js/xtext/**',
159 ]
160 property 'sonar.nodejs.executable', "${nodeDirectory}/bin/node" 156 property 'sonar.nodejs.executable', "${nodeDirectory}/bin/node"
161 property 'sonar.eslint.reportPaths', "${buildDir}/eslint.json" 157 property 'sonar.eslint.reportPaths', "${buildDir}/eslint.json"
162 property 'sonar.css.stylelint.reportPaths', "${buildDir}/stylelint.json" 158 property 'sonar.css.stylelint.reportPaths', "${buildDir}/stylelint.json"
diff --git a/language-web/package.json b/language-web/package.json
index 016d9dc1..a602ec8d 100644
--- a/language-web/package.json
+++ b/language-web/package.json
@@ -4,7 +4,7 @@
4 "description": "Web frontend for VIATRA-Generator", 4 "description": "Web frontend for VIATRA-Generator",
5 "main": "index.js", 5 "main": "index.js",
6 "scripts": { 6 "scripts": {
7 "assemble:lezer": "lezer-generator src/main/js/editor/problem.grammar -o build/generated/sources/lezer/problem.ts", 7 "assemble:lezer": "lezer-generator src/main/js/language/problem.grammar -o build/generated/sources/lezer/problem.ts",
8 "assemble:webpack": "webpack --node-env production", 8 "assemble:webpack": "webpack --node-env production",
9 "serve": "webpack serve --node-env development --hot", 9 "serve": "webpack serve --node-env development --hot",
10 "check": "yarn run check:eslint && yarn run check:stylelint", 10 "check": "yarn run check:eslint && yarn run check:stylelint",
diff --git a/language-web/src/main/css/xtext/xtext-codemirror.css b/language-web/src/main/css/xtext/xtext-codemirror.css
deleted file mode 100644
index 831b6daf..00000000
--- a/language-web/src/main/css/xtext/xtext-codemirror.css
+++ /dev/null
@@ -1,58 +0,0 @@
1.CodeMirror {
2 height: 100%;
3}
4
5.annotations-gutter {
6 width: 12px;
7 background: #f0f0f0;
8}
9
10.xtext-annotation_error {
11 width: 12px;
12 height: 12px;
13 background-image: url('images/error_an.gif');
14 background-repeat: no-repeat;
15}
16
17.xtext-annotation_warning {
18 width: 12px;
19 height: 12px;
20 background-image: url('images/warning_an.gif');
21 background-repeat: no-repeat;
22}
23
24.xtext-annotation_info {
25 width: 12px;
26 height: 12px;
27 background-image: url('images/info_an.gif');
28 background-repeat: no-repeat;
29}
30
31.xtext-marker_error {
32 z-index: 30;
33 background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAABmJLR0QA/wD/AP+gvaeTAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==");
34 background-repeat: repeat-x;
35 background-position: left bottom;
36}
37
38.xtext-marker_warning {
39 z-index: 20;
40 background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAABmJLR0QA/wD/AP+gvaeTAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=");
41 background-repeat: repeat-x;
42 background-position: left bottom;
43}
44
45.xtext-marker_info {
46 z-index: 10;
47 background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAABmJLR0QA/wD/AP+gvaeTAAAANklEQVQI12NkgIIVRx8tZGBg6GZccfRRKAMDgw8DA0M3AwPDIiYGBoZKBgaG7ghruSsMDAwpABH5CoqwzCoTAAAAAElFTkSuQmCC");
48 background-repeat: repeat-x;
49 background-position: left bottom;
50}
51
52.xtext-marker_read {
53 background-color: #ddd;
54}
55
56.xtext-marker_write {
57 background-color: yellow;
58}
diff --git a/language-web/src/main/images/error_an.gif b/language-web/src/main/images/error_an.gif
deleted file mode 100644
index e014ce90..00000000
--- a/language-web/src/main/images/error_an.gif
+++ /dev/null
Binary files differ
diff --git a/language-web/src/main/images/info_an.gif b/language-web/src/main/images/info_an.gif
deleted file mode 100644
index d62ad9dd..00000000
--- a/language-web/src/main/images/info_an.gif
+++ /dev/null
Binary files differ
diff --git a/language-web/src/main/images/warning_an.gif b/language-web/src/main/images/warning_an.gif
deleted file mode 100644
index 9ef66dd7..00000000
--- a/language-web/src/main/images/warning_an.gif
+++ /dev/null
Binary files differ
diff --git a/language-web/src/main/js/editor/EditorStore.ts b/language-web/src/main/js/editor/EditorStore.ts
index dcc69fd1..be9295bf 100644
--- a/language-web/src/main/js/editor/EditorStore.ts
+++ b/language-web/src/main/js/editor/EditorStore.ts
@@ -42,10 +42,10 @@ import {
42 reaction, 42 reaction,
43} from 'mobx'; 43} from 'mobx';
44 44
45import { problemLanguageSupport } from '../language/problemLanguageSupport';
45import { getLogger } from '../logging'; 46import { getLogger } from '../logging';
46import { problemLanguageSupport } from './problemLanguageSupport';
47import type { ThemeStore } from '../theme/ThemeStore'; 47import type { ThemeStore } from '../theme/ThemeStore';
48import { XtextClient } from './XtextClient'; 48import { XtextClient } from '../xtext/XtextClient';
49 49
50const log = getLogger('EditorStore'); 50const log = getLogger('EditorStore');
51 51
diff --git a/language-web/src/main/js/editor/folding.ts b/language-web/src/main/js/language/folding.ts
index 54c7294d..54c7294d 100644
--- a/language-web/src/main/js/editor/folding.ts
+++ b/language-web/src/main/js/language/folding.ts
diff --git a/language-web/src/main/js/editor/indentation.ts b/language-web/src/main/js/language/indentation.ts
index b2f0134b..b2f0134b 100644
--- a/language-web/src/main/js/editor/indentation.ts
+++ b/language-web/src/main/js/language/indentation.ts
diff --git a/language-web/src/main/js/editor/problem.grammar b/language-web/src/main/js/language/problem.grammar
index cf940698..cf940698 100644
--- a/language-web/src/main/js/editor/problem.grammar
+++ b/language-web/src/main/js/language/problem.grammar
diff --git a/language-web/src/main/js/editor/problemLanguageSupport.ts b/language-web/src/main/js/language/problemLanguageSupport.ts
index c9e61b31..c9e61b31 100644
--- a/language-web/src/main/js/editor/problemLanguageSupport.ts
+++ b/language-web/src/main/js/language/problemLanguageSupport.ts
diff --git a/language-web/src/main/js/xtext/CodeMirrorEditorContext.js b/language-web/src/main/js/xtext/CodeMirrorEditorContext.js
deleted file mode 100644
index b829c680..00000000
--- a/language-web/src/main/js/xtext/CodeMirrorEditorContext.js
+++ /dev/null
@@ -1,111 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-2.0.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10define([], function() {
11
12 /**
13 * An editor context mediates between the Xtext services and the CodeMirror editor framework.
14 */
15 function CodeMirrorEditorContext(editor) {
16 this._editor = editor;
17 this._serverState = {};
18 this._serverStateListeners = [];
19 this._dirty = false;
20 this._dirtyStateListeners = [];
21 };
22
23 CodeMirrorEditorContext.prototype = {
24
25 getServerState: function() {
26 return this._serverState;
27 },
28
29 updateServerState: function(currentText, currentStateId) {
30 this._serverState.text = currentText;
31 this._serverState.stateId = currentStateId;
32 return this._serverStateListeners;
33 },
34
35 addServerStateListener: function(listener) {
36 this._serverStateListeners.push(listener);
37 },
38
39 getCaretOffset: function() {
40 var editor = this._editor;
41 return editor.indexFromPos(editor.getCursor());
42 },
43
44 getLineStart: function(lineNumber) {
45 var editor = this._editor;
46 return editor.indexFromPos({line: lineNumber, ch: 0});
47 },
48
49 getSelection: function() {
50 var editor = this._editor;
51 return {
52 start: editor.indexFromPos(editor.getCursor('from')),
53 end: editor.indexFromPos(editor.getCursor('to'))
54 };
55 },
56
57 getText: function(start, end) {
58 var editor = this._editor;
59 if (start && end) {
60 return editor.getRange(editor.posFromIndex(start), editor.posFromIndex(end));
61 } else {
62 return editor.getValue();
63 }
64 },
65
66 isDirty: function() {
67 return !this._clean;
68 },
69
70 setDirty: function(dirty) {
71 if (dirty != this._dirty) {
72 for (var i = 0; i < this._dirtyStateListeners.length; i++) {
73 this._dirtyStateListeners[i](dirty);
74 }
75 }
76 this._dirty = dirty;
77 },
78
79 addDirtyStateListener: function(listener) {
80 this._dirtyStateListeners.push(listener);
81 },
82
83 clearUndoStack: function() {
84 this._editor.clearHistory();
85 },
86
87 setCaretOffset: function(offset) {
88 var editor = this._editor;
89 editor.setCursor(editor.posFromIndex(offset));
90 },
91
92 setSelection: function(selection) {
93 var editor = this._editor;
94 editor.setSelection(editor.posFromIndex(selection.start), editor.posFromIndex(selection.end));
95 },
96
97 setText: function(text, start, end) {
98 var editor = this._editor;
99 if (!start)
100 start = 0;
101 if (!end)
102 end = editor.getValue().length;
103 var cursor = editor.getCursor();
104 editor.replaceRange(text, editor.posFromIndex(start), editor.posFromIndex(end));
105 editor.setCursor(cursor);
106 }
107
108 };
109
110 return CodeMirrorEditorContext;
111}); \ No newline at end of file
diff --git a/language-web/src/main/js/xtext/ContentAssistService.ts b/language-web/src/main/js/xtext/ContentAssistService.ts
new file mode 100644
index 00000000..91789864
--- /dev/null
+++ b/language-web/src/main/js/xtext/ContentAssistService.ts
@@ -0,0 +1,133 @@
1import type {
2 Completion,
3 CompletionContext,
4 CompletionResult,
5} from '@codemirror/autocomplete';
6import type { ChangeSet, Transaction } from '@codemirror/state';
7
8import { getLogger } from '../logging';
9import type { UpdateService } from './UpdateService';
10
11const log = getLogger('xtext.ContentAssistService');
12
13export class ContentAssistService {
14 updateService: UpdateService;
15
16 lastCompletion: CompletionResult | null = null;
17
18 constructor(updateService: UpdateService) {
19 this.updateService = updateService;
20 }
21
22 onTransaction(transaction: Transaction): void {
23 if (this.shouldInvalidateCachedCompletion(transaction.changes)) {
24 this.lastCompletion = null;
25 }
26 }
27
28 async contentAssist(context: CompletionContext): Promise<CompletionResult> {
29 const tokenBefore = context.tokenBefore(['QualifiedName']);
30 let range: { from: number, to: number };
31 let selection: { selectionStart?: number, selectionEnd?: number };
32 if (tokenBefore === null) {
33 if (!context.explicit) {
34 return {
35 from: context.pos,
36 options: [],
37 };
38 }
39 range = {
40 from: context.pos,
41 to: context.pos,
42 };
43 selection = {};
44 } else {
45 range = {
46 from: tokenBefore.from,
47 to: tokenBefore.to,
48 };
49 selection = {
50 selectionStart: tokenBefore.from,
51 selectionEnd: tokenBefore.to,
52 };
53 }
54 if (!context.explicit && this.shouldReturnCachedCompletion(tokenBefore)) {
55 log.trace('Returning cached completion result');
56 // Postcondition of `shouldReturnCachedCompletion`: `lastCompletion !== null`
57 return {
58 ...this.lastCompletion as CompletionResult,
59 ...range,
60 };
61 }
62 this.lastCompletion = null;
63 const entries = await this.updateService.fetchContentAssist({
64 resource: this.updateService.resourceName,
65 serviceType: 'assist',
66 caretOffset: context.pos,
67 ...selection,
68 }, context);
69 if (context.aborted) {
70 return {
71 ...range,
72 options: [],
73 };
74 }
75 const options: Completion[] = [];
76 entries.forEach((entry) => {
77 options.push({
78 label: entry.proposal,
79 detail: entry.description,
80 info: entry.documentation,
81 type: entry.kind?.toLowerCase(),
82 boost: entry.kind === 'KEYWORD' ? -90 : 0,
83 });
84 });
85 log.debug('Fetched', options.length, 'completions from server');
86 this.lastCompletion = {
87 ...range,
88 options,
89 span: /^[a-zA-Z0-9_:]*$/,
90 };
91 return this.lastCompletion;
92 }
93
94 private shouldReturnCachedCompletion(
95 token: { from: number, to: number, text: string } | null,
96 ) {
97 if (token === null || this.lastCompletion === null) {
98 return false;
99 }
100 const { from, to, text } = token;
101 const { from: lastFrom, to: lastTo, span } = this.lastCompletion;
102 if (!lastTo) {
103 return true;
104 }
105 const [transformedFrom, transformedTo] = this.mapRangeInclusive(lastFrom, lastTo);
106 return from >= transformedFrom && to <= transformedTo && span && span.exec(text);
107 }
108
109 private shouldInvalidateCachedCompletion(changes: ChangeSet) {
110 if (changes.empty || this.lastCompletion === null) {
111 return false;
112 }
113 const { from: lastFrom, to: lastTo } = this.lastCompletion;
114 if (!lastTo) {
115 return true;
116 }
117 const [transformedFrom, transformedTo] = this.mapRangeInclusive(lastFrom, lastTo);
118 let invalidate = false;
119 changes.iterChangedRanges((fromA, toA) => {
120 if (fromA < transformedFrom || toA > transformedTo) {
121 invalidate = true;
122 }
123 });
124 return invalidate;
125 }
126
127 private mapRangeInclusive(lastFrom: number, lastTo: number): [number, number] {
128 const changes = this.updateService.computeChangesSinceLastUpdate();
129 const transformedFrom = changes.mapPos(lastFrom);
130 const transformedTo = changes.mapPos(lastTo, 1);
131 return [transformedFrom, transformedTo];
132 }
133}
diff --git a/language-web/src/main/js/xtext/ServiceBuilder.js b/language-web/src/main/js/xtext/ServiceBuilder.js
deleted file mode 100644
index 57fcb310..00000000
--- a/language-web/src/main/js/xtext/ServiceBuilder.js
+++ /dev/null
@@ -1,285 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-2.0.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 ******************************************************************************/
9
10define([
11 'jquery',
12 'xtext/services/XtextService',
13 'xtext/services/LoadResourceService',
14 'xtext/services/SaveResourceService',
15 'xtext/services/HighlightingService',
16 'xtext/services/ValidationService',
17 'xtext/services/UpdateService',
18 'xtext/services/ContentAssistService',
19 'xtext/services/HoverService',
20 'xtext/services/OccurrencesService',
21 'xtext/services/FormattingService',
22 '../logging',
23], function(jQuery, XtextService, LoadResourceService, SaveResourceService, HighlightingService,
24 ValidationService, UpdateService, ContentAssistService, HoverService, OccurrencesService,
25 FormattingService, logging) {
26
27 /**
28 * Builder class for the Xtext services.
29 */
30 function ServiceBuilder(xtextServices) {
31 this.services = xtextServices;
32 };
33
34 /**
35 * Create all the available Xtext services depending on the configuration.
36 */
37 ServiceBuilder.prototype.createServices = function() {
38 var services = this.services;
39 var options = services.options;
40 var editorContext = services.editorContext;
41 editorContext.xtextServices = services;
42 var self = this;
43 if (!options.serviceUrl) {
44 if (!options.baseUrl)
45 options.baseUrl = '/';
46 else if (options.baseUrl.charAt(0) != '/')
47 options.baseUrl = '/' + options.baseUrl;
48 options.serviceUrl = window.location.protocol + '//' + window.location.host + options.baseUrl + 'xtext-service';
49 }
50 if (options.resourceId) {
51 if (!options.xtextLang)
52 options.xtextLang = options.resourceId.split(/[?#]/)[0].split('.').pop();
53 if (options.loadFromServer === undefined)
54 options.loadFromServer = true;
55 if (options.loadFromServer && this.setupPersistenceServices) {
56 services.loadResourceService = new LoadResourceService(options.serviceUrl, options.resourceId, false);
57 services.loadResource = function(addParams) {
58 return services.loadResourceService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options));
59 }
60 services.saveResourceService = new SaveResourceService(options.serviceUrl, options.resourceId);
61 services.saveResource = function(addParams) {
62 return services.saveResourceService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options));
63 }
64 services.revertResourceService = new LoadResourceService(options.serviceUrl, options.resourceId, true);
65 services.revertResource = function(addParams) {
66 return services.revertResourceService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options));
67 }
68 this.setupPersistenceServices();
69 services.loadResource();
70 }
71 } else {
72 if (options.loadFromServer === undefined)
73 options.loadFromServer = false;
74 if (options.xtextLang) {
75 var randomId = Math.floor(Math.random() * 2147483648).toString(16);
76 options.resourceId = randomId + '.' + options.xtextLang;
77 }
78 }
79
80 if (this.setupSyntaxHighlighting) {
81 this.setupSyntaxHighlighting();
82 }
83 if (options.enableHighlightingService || options.enableHighlightingService === undefined) {
84 services.highlightingService = new HighlightingService(options.serviceUrl, options.resourceId);
85 services.computeHighlighting = function(addParams) {
86 return services.highlightingService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options));
87 }
88 }
89 if (options.enableValidationService || options.enableValidationService === undefined) {
90 services.validationService = new ValidationService(options.serviceUrl, options.resourceId);
91 services.validate = function(addParams) {
92 return services.validationService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options));
93 }
94 }
95 if (this.setupUpdateService) {
96 function refreshDocument() {
97 if (services.highlightingService && self.doHighlighting) {
98 services.highlightingService.setState(undefined);
99 self.doHighlighting();
100 }
101 if (services.validationService && self.doValidation) {
102 services.validationService.setState(undefined);
103 self.doValidation();
104 }
105 }
106 if (!options.sendFullText) {
107 services.updateService = new UpdateService(options.serviceUrl, options.resourceId);
108 services.update = function(addParams) {
109 return services.updateService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options));
110 }
111 if (services.saveResourceService)
112 services.saveResourceService._updateService = services.updateService;
113 editorContext.addServerStateListener(refreshDocument);
114 }
115 this.setupUpdateService(refreshDocument);
116 }
117 if ((options.enableContentAssistService || options.enableContentAssistService === undefined)
118 && this.setupContentAssistService) {
119 services.contentAssistService = new ContentAssistService(options.serviceUrl, options.resourceId, services.updateService);
120 services.getContentAssist = function(addParams) {
121 return services.contentAssistService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options));
122 }
123 this.setupContentAssistService();
124 }
125 if ((options.enableHoverService || options.enableHoverService === undefined)
126 && this.setupHoverService) {
127 services.hoverService = new HoverService(options.serviceUrl, options.resourceId, services.updateService);
128 services.getHoverInfo = function(addParams) {
129 return services.hoverService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options));
130 }
131 this.setupHoverService();
132 }
133 if ((options.enableOccurrencesService || options.enableOccurrencesService === undefined)
134 && this.setupOccurrencesService) {
135 services.occurrencesService = new OccurrencesService(options.serviceUrl, options.resourceId, services.updateService);
136 services.getOccurrences = function(addParams) {
137 return services.occurrencesService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options));
138 }
139 this.setupOccurrencesService();
140 }
141 if ((options.enableFormattingService || options.enableFormattingService === undefined)
142 && this.setupFormattingService) {
143 services.formattingService = new FormattingService(options.serviceUrl, options.resourceId, services.updateService);
144 services.format = function(addParams) {
145 return services.formattingService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options));
146 }
147 this.setupFormattingService();
148 }
149 if (options.enableGeneratorService || options.enableGeneratorService === undefined) {
150 services.generatorService = new XtextService();
151 services.generatorService.initialize(services, 'generate');
152 services.generatorService._initServerData = function(serverData, editorContext, params) {
153 if (params.allArtifacts)
154 serverData.allArtifacts = params.allArtifacts;
155 else if (params.artifactId)
156 serverData.artifact = params.artifactId;
157 if (params.includeContent !== undefined)
158 serverData.includeContent = params.includeContent;
159 }
160 services.generate = function(addParams) {
161 return services.generatorService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options));
162 }
163 }
164
165 if (options.dirtyElement) {
166 var doc = options.document || document;
167 var dirtyElement;
168 if (typeof(options.dirtyElement) === 'string')
169 dirtyElement = jQuery('#' + options.dirtyElement, doc);
170 else
171 dirtyElement = jQuery(options.dirtyElement);
172 var dirtyStatusClass = options.dirtyStatusClass;
173 if (!dirtyStatusClass)
174 dirtyStatusClass = 'dirty';
175 editorContext.addDirtyStateListener(function(dirty) {
176 if (dirty)
177 dirtyElement.addClass(dirtyStatusClass);
178 else
179 dirtyElement.removeClass(dirtyStatusClass);
180 });
181 }
182
183 const log = logging.getLoggerFromRoot('xtext.XtextService');
184 services.successListeners = [function(serviceType, result) {
185 if (log.getLevel() <= log.levels.TRACE) {
186 log.trace('service', serviceType, 'request success', JSON.parse(JSON.stringify(result)));
187 }
188 }];
189 services.errorListeners = [function(serviceType, severity, message, requestData) {
190 const messageParts = ['service', serviceType, 'failed:', message || '(no message)'];
191 if (requestData) {
192 messageParts.push(JSON.parse(JSON.stringify(requestData)));
193 }
194 if (severity === 'warning') {
195 log.warn(...messageParts);
196 } else {
197 log.error(...messageParts);
198 }
199 }];
200 }
201
202 /**
203 * Change the resource associated with this service builder.
204 */
205 ServiceBuilder.prototype.changeResource = function(resourceId) {
206 var services = this.services;
207 var options = services.options;
208 options.resourceId = resourceId;
209 for (var p in services) {
210 if (services.hasOwnProperty(p)) {
211 var service = services[p];
212 if (service._serviceType && jQuery.isFunction(service.initialize))
213 services[p].initialize(options.serviceUrl, service._serviceType, resourceId, services.updateService);
214 }
215 }
216 var knownServerState = services.editorContext.getServerState();
217 delete knownServerState.stateId;
218 delete knownServerState.text;
219 if (options.loadFromServer && jQuery.isFunction(services.loadResource)) {
220 services.loadResource();
221 }
222 }
223
224 /**
225 * Create a copy of the given object.
226 */
227 ServiceBuilder.copy = function(obj) {
228 var copy = {};
229 for (var p in obj) {
230 if (obj.hasOwnProperty(p))
231 copy[p] = obj[p];
232 }
233 return copy;
234 }
235
236 /**
237 * Translate an HTML attribute name to a JS option name.
238 */
239 ServiceBuilder.optionName = function(name) {
240 var prefix = 'data-editor-';
241 if (name.substring(0, prefix.length) === prefix) {
242 var key = name.substring(prefix.length);
243 key = key.replace(/-([a-z])/ig, function(all, character) {
244 return character.toUpperCase();
245 });
246 return key;
247 }
248 return undefined;
249 }
250
251 /**
252 * Copy all default options into the given set of additional options.
253 */
254 ServiceBuilder.mergeOptions = function(options, defaultOptions) {
255 if (options) {
256 for (var p in defaultOptions) {
257 if (defaultOptions.hasOwnProperty(p))
258 options[p] = defaultOptions[p];
259 }
260 return options;
261 } else {
262 return ServiceBuilder.copy(defaultOptions);
263 }
264 }
265
266 /**
267 * Merge all properties of the given parent element with the given default options.
268 */
269 ServiceBuilder.mergeParentOptions = function(parent, defaultOptions) {
270 var options = ServiceBuilder.copy(defaultOptions);
271 for (var attr, j = 0, attrs = parent.attributes, l = attrs.length; j < l; j++) {
272 attr = attrs.item(j);
273 var key = ServiceBuilder.optionName(attr.nodeName);
274 if (key) {
275 var value = attr.nodeValue;
276 if (value === 'true' || value === 'false')
277 value = value === 'true';
278 options[key] = value;
279 }
280 }
281 return options;
282 }
283
284 return ServiceBuilder;
285});
diff --git a/language-web/src/main/js/editor/XtextClient.ts b/language-web/src/main/js/xtext/UpdateService.ts
index 6f789fb7..f8ab7438 100644
--- a/language-web/src/main/js/editor/XtextClient.ts
+++ b/language-web/src/main/js/xtext/UpdateService.ts
@@ -1,132 +1,71 @@
1import { 1import {
2 Completion,
3 CompletionContext,
4 CompletionResult,
5} from '@codemirror/autocomplete';
6import type { Diagnostic } from '@codemirror/lint';
7import {
8 ChangeDesc, 2 ChangeDesc,
9 ChangeSet, 3 ChangeSet,
10 Transaction, 4 Transaction,
11} from '@codemirror/state'; 5} from '@codemirror/state';
12import { nanoid } from 'nanoid'; 6import { nanoid } from 'nanoid';
13 7
14import type { EditorStore } from './EditorStore'; 8import type { EditorStore } from '../editor/EditorStore';
15import { getLogger } from '../logging'; 9import { getLogger } from '../logging';
10import type { XtextWebSocketClient } from './XtextWebSocketClient';
11import { PendingTask } from '../utils/PendingTask';
16import { Timer } from '../utils/Timer'; 12import { Timer } from '../utils/Timer';
17import { 13import {
18 IContentAssistEntry, 14 IContentAssistEntry,
19 isContentAssistResult, 15 isContentAssistResult,
20 isDocumentStateResult, 16 isDocumentStateResult,
21 isInvalidStateIdConflictResult, 17 isInvalidStateIdConflictResult,
22 isValidationResult,
23} from './xtextServiceResults'; 18} from './xtextServiceResults';
24import { XtextWebSocketClient } from './XtextWebSocketClient';
25import { PendingTask } from '../utils/PendingTask';
26 19
27const UPDATE_TIMEOUT_MS = 500; 20const UPDATE_TIMEOUT_MS = 500;
28 21
29const WAIT_FOR_UPDATE_TIMEOUT_MS = 1000; 22const WAIT_FOR_UPDATE_TIMEOUT_MS = 1000;
30 23
31const log = getLogger('XtextClient'); 24const log = getLogger('xtext.UpdateService');
32 25
33export class XtextClient { 26export interface IAbortSignal {
34 resourceName: string; 27 aborted: boolean;
28}
35 29
36 webSocketClient: XtextWebSocketClient; 30export class UpdateService {
31 resourceName: string;
37 32
38 xtextStateId: string | null = null; 33 xtextStateId: string | null = null;
39 34
40 pendingUpdate: ChangeDesc | null; 35 private store: EditorStore;
41 36
42 dirtyChanges: ChangeDesc; 37 private pendingUpdate: ChangeDesc | null = null;
43 38
44 lastCompletion: CompletionResult | null = null; 39 private dirtyChanges: ChangeDesc;
45 40
46 updateListeners: PendingTask<void>[] = []; 41 private webSocketClient: XtextWebSocketClient;
47 42
48 updateTimer = new Timer(() => { 43 private updateListeners: PendingTask<void>[] = [];
49 this.handleUpdate();
50 }, UPDATE_TIMEOUT_MS);
51 44
52 store: EditorStore; 45 private idleUpdateTimer = new Timer(() => {
46 this.handleIdleUpdate();
47 }, UPDATE_TIMEOUT_MS);
53 48
54 constructor(store: EditorStore) { 49 constructor(store: EditorStore, webSocketClient: XtextWebSocketClient) {
55 this.resourceName = `${nanoid(7)}.problem`; 50 this.resourceName = `${nanoid(7)}.problem`;
56 this.pendingUpdate = null;
57 this.store = store; 51 this.store = store;
58 this.dirtyChanges = this.newEmptyChangeDesc(); 52 this.dirtyChanges = this.newEmptyChangeDesc();
59 this.webSocketClient = new XtextWebSocketClient( 53 this.webSocketClient = webSocketClient;
60 async () => {
61 this.xtextStateId = null;
62 await this.updateFullText();
63 },
64 async (resource, stateId, service, push) => {
65 await this.onPush(resource, stateId, service, push);
66 },
67 );
68 } 54 }
69 55
70 onTransaction(transaction: Transaction): void { 56 onTransaction(transaction: Transaction): void {
71 const { changes } = transaction; 57 const { changes } = transaction;
72 if (!changes.empty) { 58 if (!changes.empty) {
73 if (this.shouldInvalidateCachedCompletion(transaction)) {
74 log.trace('Invalidating cached completions');
75 this.lastCompletion = null;
76 }
77 this.dirtyChanges = this.dirtyChanges.composeDesc(changes.desc); 59 this.dirtyChanges = this.dirtyChanges.composeDesc(changes.desc);
78 this.updateTimer.reschedule(); 60 this.idleUpdateTimer.reschedule();
79 }
80 }
81
82 private async onPush(resource: string, stateId: string, service: string, push: unknown) {
83 if (resource !== this.resourceName) {
84 log.error('Unknown resource name: expected:', this.resourceName, 'got:', resource);
85 return;
86 } 61 }
87 if (stateId !== this.xtextStateId) {
88 log.error('Unexpected xtext state id: expected:', this.xtextStateId, 'got:', resource);
89 await this.updateFullText();
90 }
91 switch (service) {
92 case 'validate':
93 this.onValidate(push);
94 return;
95 case 'highlight':
96 // TODO
97 return;
98 default:
99 log.error('Unknown push service:', service);
100 break;
101 }
102 }
103
104 private onValidate(push: unknown) {
105 if (!isValidationResult(push)) {
106 log.error('Invalid validation result', push);
107 return;
108 }
109 const allChanges = this.computeChangesSinceLastUpdate();
110 const diagnostics: Diagnostic[] = [];
111 push.issues.forEach((issue) => {
112 if (issue.severity === 'ignore') {
113 return;
114 }
115 diagnostics.push({
116 from: allChanges.mapPos(issue.offset),
117 to: allChanges.mapPos(issue.offset + issue.length),
118 severity: issue.severity,
119 message: issue.description,
120 });
121 });
122 this.store.updateDiagnostics(diagnostics);
123 } 62 }
124 63
125 private computeChangesSinceLastUpdate() { 64 computeChangesSinceLastUpdate(): ChangeDesc {
126 return this.pendingUpdate?.composeDesc(this.dirtyChanges) || this.dirtyChanges; 65 return this.pendingUpdate?.composeDesc(this.dirtyChanges) || this.dirtyChanges;
127 } 66 }
128 67
129 private handleUpdate() { 68 private handleIdleUpdate() {
130 if (!this.webSocketClient.isOpen || this.dirtyChanges.empty) { 69 if (!this.webSocketClient.isOpen || this.dirtyChanges.empty) {
131 return; 70 return;
132 } 71 }
@@ -135,7 +74,7 @@ export class XtextClient {
135 log.error('Unexpected error during scheduled update', error); 74 log.error('Unexpected error during scheduled update', error);
136 }); 75 });
137 } 76 }
138 this.updateTimer.reschedule(); 77 this.idleUpdateTimer.reschedule();
139 } 78 }
140 79
141 private newEmptyChangeDesc() { 80 private newEmptyChangeDesc() {
@@ -143,7 +82,7 @@ export class XtextClient {
143 return changeSet.desc; 82 return changeSet.desc;
144 } 83 }
145 84
146 private async updateFullText() { 85 async updateFullText(): Promise<void> {
147 await this.withUpdate(() => this.doUpdateFullText()); 86 await this.withUpdate(() => this.doUpdateFullText());
148 } 87 }
149 88
@@ -196,99 +135,24 @@ export class XtextClient {
196 return this.doUpdateFullText(); 135 return this.doUpdateFullText();
197 } 136 }
198 137
199 async contentAssist(context: CompletionContext): Promise<CompletionResult> { 138 async fetchContentAssist(
200 const tokenBefore = context.tokenBefore(['QualifiedName']); 139 params: Record<string, unknown>,
201 if (tokenBefore === null && !context.explicit) { 140 signal: IAbortSignal,
202 return { 141 ): Promise<IContentAssistEntry[]> {
203 from: context.pos,
204 options: [],
205 };
206 }
207 const range = {
208 from: tokenBefore?.from || context.pos,
209 to: tokenBefore?.to || context.pos,
210 };
211 if (this.shouldReturnCachedCompletion(tokenBefore)) {
212 log.trace('Returning cached completion result');
213 // Postcondition of `shouldReturnCachedCompletion`: `lastCompletion !== null`
214 return {
215 ...this.lastCompletion as CompletionResult,
216 ...range,
217 };
218 }
219 const entries = await this.fetchContentAssist(context);
220 if (context.aborted) {
221 return {
222 ...range,
223 options: [],
224 };
225 }
226 const options: Completion[] = [];
227 entries.forEach((entry) => {
228 options.push({
229 label: entry.proposal,
230 detail: entry.description,
231 info: entry.documentation,
232 type: entry.kind?.toLowerCase(),
233 boost: entry.kind === 'KEYWORD' ? -90 : 0,
234 });
235 });
236 log.debug('Fetched', options.length, 'completions from server');
237 this.lastCompletion = {
238 ...range,
239 options,
240 span: /[a-zA-Z0-9_:]/,
241 };
242 return this.lastCompletion;
243 }
244
245 private shouldReturnCachedCompletion(
246 token: { from: number, to: number, text: string } | null,
247 ) {
248 if (token === null || this.lastCompletion === null) {
249 return false;
250 }
251 const { from, to, text } = token;
252 const { from: lastFrom, to: lastTo, span } = this.lastCompletion;
253 if (!lastTo) {
254 return true;
255 }
256 const transformedFrom = this.dirtyChanges.mapPos(lastFrom);
257 const transformedTo = this.dirtyChanges.mapPos(lastTo, 1);
258 return from >= transformedFrom && to <= transformedTo && span && span.exec(text);
259 }
260
261 private shouldInvalidateCachedCompletion(transaction: Transaction) {
262 if (this.lastCompletion === null) {
263 return false;
264 }
265 const { from: lastFrom, to: lastTo } = this.lastCompletion;
266 if (!lastTo) {
267 return true;
268 }
269 const transformedFrom = this.dirtyChanges.mapPos(lastFrom);
270 const transformedTo = this.dirtyChanges.mapPos(lastTo, 1);
271 let invalidate = false;
272 transaction.changes.iterChangedRanges((fromA, toA) => {
273 if (fromA < transformedFrom || toA > transformedTo) {
274 invalidate = true;
275 }
276 });
277 return invalidate;
278 }
279
280 private async fetchContentAssist(context: CompletionContext) {
281 await this.prepareForDeltaUpdate(); 142 await this.prepareForDeltaUpdate();
143 if (signal.aborted) {
144 return [];
145 }
282 const delta = this.computeDelta(); 146 const delta = this.computeDelta();
283 if (delta === null) { 147 if (delta === null) {
284 // Poscondition of `prepareForDeltaUpdate`: `xtextStateId !== null` 148 // Poscondition of `prepareForDeltaUpdate`: `xtextStateId !== null`
285 return this.doFetchContentAssist(context, this.xtextStateId as string); 149 return this.doFetchContentAssist(params, this.xtextStateId as string);
286 } 150 }
287 log.trace('Editor delta', delta); 151 log.trace('Editor delta', delta);
288 return await this.withUpdate(async () => { 152 return this.withUpdate(async () => {
289 const result = await this.webSocketClient.send({ 153 const result = await this.webSocketClient.send({
154 ...params,
290 requiredStateId: this.xtextStateId, 155 requiredStateId: this.xtextStateId,
291 ...this.computeContentAssistParams(context),
292 ...delta, 156 ...delta,
293 }); 157 });
294 if (isContentAssistResult(result)) { 158 if (isContentAssistResult(result)) {
@@ -296,10 +160,10 @@ export class XtextClient {
296 } 160 }
297 if (isInvalidStateIdConflictResult(result)) { 161 if (isInvalidStateIdConflictResult(result)) {
298 const [newStateId] = await this.doFallbackToUpdateFullText(); 162 const [newStateId] = await this.doFallbackToUpdateFullText();
299 if (context.aborted) { 163 if (signal.aborted) {
300 return [newStateId, [] as IContentAssistEntry[]]; 164 return [newStateId, []];
301 } 165 }
302 const entries = await this.doFetchContentAssist(context, newStateId); 166 const entries = await this.doFetchContentAssist(params, newStateId);
303 return [newStateId, entries]; 167 return [newStateId, entries];
304 } 168 }
305 log.error('Unextpected content assist result with delta update', result); 169 log.error('Unextpected content assist result with delta update', result);
@@ -307,10 +171,10 @@ export class XtextClient {
307 }); 171 });
308 } 172 }
309 173
310 private async doFetchContentAssist(context: CompletionContext, expectedStateId: string) { 174 private async doFetchContentAssist(params: Record<string, unknown>, expectedStateId: string) {
311 const result = await this.webSocketClient.send({ 175 const result = await this.webSocketClient.send({
176 ...params,
312 requiredStateId: expectedStateId, 177 requiredStateId: expectedStateId,
313 ...this.computeContentAssistParams(context),
314 }); 178 });
315 if (isContentAssistResult(result) && result.stateId === expectedStateId) { 179 if (isContentAssistResult(result) && result.stateId === expectedStateId) {
316 return result.entries; 180 return result.entries;
@@ -319,23 +183,6 @@ export class XtextClient {
319 throw new Error('Unexpected content assist result'); 183 throw new Error('Unexpected content assist result');
320 } 184 }
321 185
322 private computeContentAssistParams(context: CompletionContext) {
323 const tokenBefore = context.tokenBefore(['QualifiedName']);
324 let selection = {};
325 if (tokenBefore !== null) {
326 selection = {
327 selectionStart: tokenBefore.from,
328 selectionEnd: tokenBefore.to,
329 };
330 }
331 return {
332 resource: this.resourceName,
333 serviceType: 'assist',
334 caretOffset: tokenBefore?.from || context.pos,
335 ...selection,
336 };
337 }
338
339 private computeDelta() { 186 private computeDelta() {
340 if (this.dirtyChanges.empty) { 187 if (this.dirtyChanges.empty) {
341 return null; 188 return null;
diff --git a/language-web/src/main/js/xtext/ValidationService.ts b/language-web/src/main/js/xtext/ValidationService.ts
new file mode 100644
index 00000000..838aa31e
--- /dev/null
+++ b/language-web/src/main/js/xtext/ValidationService.ts
@@ -0,0 +1,40 @@
1import type { Diagnostic } from '@codemirror/lint';
2
3import type { EditorStore } from '../editor/EditorStore';
4import { getLogger } from '../logging';
5import type { UpdateService } from './UpdateService';
6import { isValidationResult } from './xtextServiceResults';
7
8const log = getLogger('xtext.ValidationService');
9
10export class ValidationService {
11 private store: EditorStore;
12
13 private updateService: UpdateService;
14
15 constructor(store: EditorStore, updateService: UpdateService) {
16 this.store = store;
17 this.updateService = updateService;
18 }
19
20 onPush(push: unknown): void {
21 if (!isValidationResult(push)) {
22 log.error('Invalid validation result', push);
23 return;
24 }
25 const allChanges = this.updateService.computeChangesSinceLastUpdate();
26 const diagnostics: Diagnostic[] = [];
27 push.issues.forEach((issue) => {
28 if (issue.severity === 'ignore') {
29 return;
30 }
31 diagnostics.push({
32 from: allChanges.mapPos(issue.offset),
33 to: allChanges.mapPos(issue.offset + issue.length),
34 severity: issue.severity,
35 message: issue.description,
36 });
37 });
38 this.store.updateDiagnostics(diagnostics);
39 }
40}
diff --git a/language-web/src/main/js/xtext/XtextClient.ts b/language-web/src/main/js/xtext/XtextClient.ts
new file mode 100644
index 00000000..f8b06258
--- /dev/null
+++ b/language-web/src/main/js/xtext/XtextClient.ts
@@ -0,0 +1,73 @@
1import type {
2 CompletionContext,
3 CompletionResult,
4} from '@codemirror/autocomplete';
5import type { Transaction } from '@codemirror/state';
6
7import type { EditorStore } from '../editor/EditorStore';
8import { ContentAssistService } from './ContentAssistService';
9import { getLogger } from '../logging';
10import { UpdateService } from './UpdateService';
11import { ValidationService } from './ValidationService';
12import { XtextWebSocketClient } from './XtextWebSocketClient';
13
14const log = getLogger('xtext.XtextClient');
15
16export class XtextClient {
17 webSocketClient: XtextWebSocketClient;
18
19 updateService: UpdateService;
20
21 contentAssistService: ContentAssistService;
22
23 validationService: ValidationService;
24
25 constructor(store: EditorStore) {
26 this.webSocketClient = new XtextWebSocketClient(
27 async () => {
28 this.updateService.xtextStateId = null;
29 await this.updateService.updateFullText();
30 },
31 async (resource, stateId, service, push) => {
32 await this.onPush(resource, stateId, service, push);
33 },
34 );
35 this.updateService = new UpdateService(store, this.webSocketClient);
36 this.contentAssistService = new ContentAssistService(this.updateService);
37 this.validationService = new ValidationService(store, this.updateService);
38 }
39
40 onTransaction(transaction: Transaction): void {
41 // `ContentAssistService.prototype.onTransaction` needs the dirty change desc
42 // _before_ the current edit, so we call it before `updateService`.
43 this.contentAssistService.onTransaction(transaction);
44 this.updateService.onTransaction(transaction);
45 }
46
47 private async onPush(resource: string, stateId: string, service: string, push: unknown) {
48 const { resourceName, xtextStateId } = this.updateService;
49 if (resource !== resourceName) {
50 log.error('Unknown resource name: expected:', resourceName, 'got:', resource);
51 return;
52 }
53 if (stateId !== xtextStateId) {
54 log.error('Unexpected xtext state id: expected:', xtextStateId, 'got:', resource);
55 await this.updateService.updateFullText();
56 }
57 switch (service) {
58 case 'validate':
59 this.validationService.onPush(push);
60 return;
61 case 'highlight':
62 // TODO
63 return;
64 default:
65 log.error('Unknown push service:', service);
66 break;
67 }
68 }
69
70 contentAssist(context: CompletionContext): Promise<CompletionResult> {
71 return this.contentAssistService.contentAssist(context);
72 }
73}
diff --git a/language-web/src/main/js/editor/XtextWebSocketClient.ts b/language-web/src/main/js/xtext/XtextWebSocketClient.ts
index 5b775500..5b775500 100644
--- a/language-web/src/main/js/editor/XtextWebSocketClient.ts
+++ b/language-web/src/main/js/xtext/XtextWebSocketClient.ts
diff --git a/language-web/src/main/js/xtext/compatibility.js b/language-web/src/main/js/xtext/compatibility.js
deleted file mode 100644
index c877fc56..00000000
--- a/language-web/src/main/js/xtext/compatibility.js
+++ /dev/null
@@ -1,63 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-2.0.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10define([], function() {
11
12 if (!Function.prototype.bind) {
13 Function.prototype.bind = function(target) {
14 if (typeof this !== 'function')
15 throw new TypeError('bind target is not callable');
16 var args = Array.prototype.slice.call(arguments, 1);
17 var unboundFunc = this;
18 var nopFunc = function() {};
19 boundFunc = function() {
20 var localArgs = Array.prototype.slice.call(arguments);
21 return unboundFunc.apply(this instanceof nopFunc ? this : target,
22 args.concat(localArgs));
23 };
24 nopFunc.prototype = this.prototype;
25 boundFunc.prototype = new nopFunc();
26 return boundFunc;
27 }
28 }
29
30 if (!Array.prototype.map) {
31 Array.prototype.map = function(callback, thisArg) {
32 if (this == null)
33 throw new TypeError('this is null');
34 if (typeof callback !== 'function')
35 throw new TypeError('callback is not callable');
36 var srcArray = Object(this);
37 var len = srcArray.length >>> 0;
38 var tgtArray = new Array(len);
39 for (var i = 0; i < len; i++) {
40 if (i in srcArray)
41 tgtArray[i] = callback.call(thisArg, srcArray[i], i, srcArray);
42 }
43 return tgtArray;
44 }
45 }
46
47 if (!Array.prototype.forEach) {
48 Array.prototype.forEach = function(callback, thisArg) {
49 if (this == null)
50 throw new TypeError('this is null');
51 if (typeof callback !== 'function')
52 throw new TypeError('callback is not callable');
53 var srcArray = Object(this);
54 var len = srcArray.length >>> 0;
55 for (var i = 0; i < len; i++) {
56 if (i in srcArray)
57 callback.call(thisArg, srcArray[i], i, srcArray);
58 }
59 }
60 }
61
62 return {};
63});
diff --git a/language-web/src/main/js/xtext/services/ContentAssistService.js b/language-web/src/main/js/xtext/services/ContentAssistService.js
deleted file mode 100644
index 1686570d..00000000
--- a/language-web/src/main/js/xtext/services/ContentAssistService.js
+++ /dev/null
@@ -1,132 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-2.0.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10define(['xtext/services/XtextService', 'jquery'], function(XtextService, jQuery) {
11
12 /**
13 * Service class for content assist proposals. The proposals are returned as promise of
14 * a Deferred object.
15 */
16 function ContentAssistService(serviceUrl, resourceId, updateService) {
17 this.initialize(serviceUrl, 'assist', resourceId, updateService);
18 }
19
20 ContentAssistService.prototype = new XtextService();
21
22 ContentAssistService.prototype.invoke = function(editorContext, params, deferred) {
23 if (deferred === undefined) {
24 deferred = jQuery.Deferred();
25 }
26 var serverData = {
27 contentType: params.contentType
28 };
29 if (params.offset)
30 serverData.caretOffset = params.offset;
31 else
32 serverData.caretOffset = editorContext.getCaretOffset();
33 var selection = params.selection ? params.selection : editorContext.getSelection();
34 if (selection.start != serverData.caretOffset || selection.end != serverData.caretOffset) {
35 serverData.selectionStart = selection.start;
36 serverData.selectionEnd = selection.end;
37 }
38 var currentText;
39 var httpMethod = 'GET';
40 var onComplete = undefined;
41 var knownServerState = editorContext.getServerState();
42 if (params.sendFullText) {
43 serverData.fullText = editorContext.getText();
44 httpMethod = 'POST';
45 } else {
46 serverData.requiredStateId = knownServerState.stateId;
47 if (this._updateService) {
48 if (knownServerState.text === undefined || knownServerState.updateInProgress) {
49 var self = this;
50 this._updateService.addCompletionCallback(function() {
51 self.invoke(editorContext, params, deferred);
52 });
53 return deferred.promise();
54 }
55 knownServerState.updateInProgress = true;
56 onComplete = this._updateService.onComplete.bind(this._updateService);
57 currentText = editorContext.getText();
58 this._updateService.computeDelta(knownServerState.text, currentText, serverData);
59 if (serverData.deltaText !== undefined) {
60 httpMethod = 'POST';
61 }
62 }
63 }
64
65 var self = this;
66 self.sendRequest(editorContext, {
67 type: httpMethod,
68 data: serverData,
69
70 success: function(result) {
71 if (result.conflict) {
72 // The server has lost its session state and the resource is loaded from the server
73 if (self._increaseRecursionCount(editorContext)) {
74 if (onComplete) {
75 delete knownServerState.updateInProgress;
76 delete knownServerState.text;
77 delete knownServerState.stateId;
78 self._updateService.addCompletionCallback(function() {
79 self.invoke(editorContext, params, deferred);
80 });
81 self._updateService.invoke(editorContext, params);
82 } else {
83 var paramsCopy = {};
84 for (var p in params) {
85 if (params.hasOwnProperty(p))
86 paramsCopy[p] = params[p];
87 }
88 paramsCopy.sendFullText = true;
89 self.invoke(editorContext, paramsCopy, deferred);
90 }
91 } else {
92 deferred.reject(result.conflict);
93 }
94 return false;
95 }
96 if (onComplete && result.stateId !== undefined && result.stateId != editorContext.getServerState().stateId) {
97 var listeners = editorContext.updateServerState(currentText, result.stateId);
98 for (var i = 0; i < listeners.length; i++) {
99 self._updateService.addCompletionCallback(listeners[i], params);
100 }
101 }
102 deferred.resolve(result.entries);
103 },
104
105 error: function(xhr, textStatus, errorThrown) {
106 if (onComplete && xhr.status == 404 && !params.loadFromServer && knownServerState.text !== undefined) {
107 // The server has lost its session state and the resource is not loaded from the server
108 delete knownServerState.updateInProgress;
109 delete knownServerState.text;
110 delete knownServerState.stateId;
111 self._updateService.addCompletionCallback(function() {
112 self.invoke(editorContext, params, deferred);
113 });
114 self._updateService.invoke(editorContext, params);
115 return true;
116 }
117 deferred.reject(errorThrown);
118 },
119
120 complete: onComplete
121 }, !params.sendFullText);
122 var result = deferred.promise();
123 if (onComplete) {
124 result.always(function() {
125 knownServerState.updateInProgress = false;
126 });
127 }
128 return result;
129 };
130
131 return ContentAssistService;
132});
diff --git a/language-web/src/main/js/xtext/services/FormattingService.js b/language-web/src/main/js/xtext/services/FormattingService.js
deleted file mode 100644
index f59099ee..00000000
--- a/language-web/src/main/js/xtext/services/FormattingService.js
+++ /dev/null
@@ -1,52 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-2.0.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10define(['xtext/services/XtextService', 'jquery'], function(XtextService, jQuery) {
11
12 /**
13 * Service class for formatting text.
14 */
15 function FormattingService(serviceUrl, resourceId, updateService) {
16 this.initialize(serviceUrl, 'format', resourceId, updateService);
17 };
18
19 FormattingService.prototype = new XtextService();
20
21 FormattingService.prototype._initServerData = function(serverData, editorContext, params) {
22 var selection = params.selection ? params.selection : editorContext.getSelection();
23 if (selection.end > selection.start) {
24 serverData.selectionStart = selection.start;
25 serverData.selectionEnd = selection.end;
26 }
27 return {
28 httpMethod: 'POST'
29 };
30 };
31
32 FormattingService.prototype._processResult = function(result, editorContext) {
33 // The text update may be asynchronous, so we have to compute the new text ourselves
34 var newText;
35 if (result.replaceRegion) {
36 var fullText = editorContext.getText();
37 var start = result.replaceRegion.offset;
38 var end = result.replaceRegion.offset + result.replaceRegion.length;
39 editorContext.setText(result.formattedText, start, end);
40 newText = fullText.substring(0, start) + result.formattedText + fullText.substring(end);
41 } else {
42 editorContext.setText(result.formattedText);
43 newText = result.formattedText;
44 }
45 var listeners = editorContext.updateServerState(newText, result.stateId);
46 for (var i = 0; i < listeners.length; i++) {
47 listeners[i]({});
48 }
49 };
50
51 return FormattingService;
52}); \ No newline at end of file
diff --git a/language-web/src/main/js/xtext/services/HighlightingService.js b/language-web/src/main/js/xtext/services/HighlightingService.js
deleted file mode 100644
index 5a5ac8ba..00000000
--- a/language-web/src/main/js/xtext/services/HighlightingService.js
+++ /dev/null
@@ -1,33 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-2.0.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10define(['xtext/services/XtextService', 'jquery'], function(XtextService, jQuery) {
11
12 /**
13 * Service class for semantic highlighting.
14 */
15 function HighlightingService(serviceUrl, resourceId) {
16 this.initialize(serviceUrl, 'highlight', resourceId);
17 };
18
19 HighlightingService.prototype = new XtextService();
20
21 HighlightingService.prototype._checkPreconditions = function(editorContext, params) {
22 return this._state === undefined;
23 }
24
25 HighlightingService.prototype._onConflict = function(editorContext, cause) {
26 this.setState(undefined);
27 return {
28 suppressForcedUpdate: true
29 };
30 };
31
32 return HighlightingService;
33}); \ No newline at end of file
diff --git a/language-web/src/main/js/xtext/services/HoverService.js b/language-web/src/main/js/xtext/services/HoverService.js
deleted file mode 100644
index 03c5a52b..00000000
--- a/language-web/src/main/js/xtext/services/HoverService.js
+++ /dev/null
@@ -1,59 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-2.0.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10define(['xtext/services/XtextService', 'jquery'], function(XtextService, jQuery) {
11
12 /**
13 * Service class for hover information.
14 */
15 function HoverService(serviceUrl, resourceId, updateService) {
16 this.initialize(serviceUrl, 'hover', resourceId, updateService);
17 };
18
19 HoverService.prototype = new XtextService();
20
21 HoverService.prototype._initServerData = function(serverData, editorContext, params) {
22 // In order to display hover info for a selected completion proposal while the content
23 // assist popup is shown, the selected proposal is passed as parameter
24 if (params.proposal && params.proposal.proposal)
25 serverData.proposal = params.proposal.proposal;
26 if (params.offset)
27 serverData.caretOffset = params.offset;
28 else
29 serverData.caretOffset = editorContext.getCaretOffset();
30 var selection = params.selection ? params.selection : editorContext.getSelection();
31 if (selection.start != serverData.caretOffset || selection.end != serverData.caretOffset) {
32 serverData.selectionStart = selection.start;
33 serverData.selectionEnd = selection.end;
34 }
35 };
36
37 HoverService.prototype._getSuccessCallback = function(editorContext, params, deferred) {
38 var delay = params.mouseHoverDelay;
39 if (!delay)
40 delay = 500;
41 var showTime = new Date().getTime() + delay;
42 return function(result) {
43 if (result.conflict || !result.title && !result.content) {
44 deferred.reject();
45 } else {
46 var remainingTimeout = Math.max(0, showTime - new Date().getTime());
47 setTimeout(function() {
48 if (!params.sendFullText && result.stateId !== undefined
49 && result.stateId != editorContext.getServerState().stateId)
50 deferred.reject();
51 else
52 deferred.resolve(result);
53 }, remainingTimeout);
54 }
55 };
56 };
57
58 return HoverService;
59}); \ No newline at end of file
diff --git a/language-web/src/main/js/xtext/services/LoadResourceService.js b/language-web/src/main/js/xtext/services/LoadResourceService.js
deleted file mode 100644
index b5a315c3..00000000
--- a/language-web/src/main/js/xtext/services/LoadResourceService.js
+++ /dev/null
@@ -1,42 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-2.0.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10define(['xtext/services/XtextService', 'jquery'], function(XtextService, jQuery) {
11
12 /**
13 * Service class for loading resources. The resulting text is passed to the editor context.
14 */
15 function LoadResourceService(serviceUrl, resourceId, revert) {
16 this.initialize(serviceUrl, revert ? 'revert' : 'load', resourceId);
17 };
18
19 LoadResourceService.prototype = new XtextService();
20
21 LoadResourceService.prototype._initServerData = function(serverData, editorContext, params) {
22 return {
23 suppressContent: true,
24 httpMethod: this._serviceType == 'revert' ? 'POST' : 'GET'
25 };
26 };
27
28 LoadResourceService.prototype._getSuccessCallback = function(editorContext, params, deferred) {
29 return function(result) {
30 editorContext.setText(result.fullText);
31 editorContext.clearUndoStack();
32 editorContext.setDirty(result.dirty);
33 var listeners = editorContext.updateServerState(result.fullText, result.stateId);
34 for (var i = 0; i < listeners.length; i++) {
35 listeners[i](params);
36 }
37 deferred.resolve(result);
38 }
39 }
40
41 return LoadResourceService;
42}); \ No newline at end of file
diff --git a/language-web/src/main/js/xtext/services/OccurrencesService.js b/language-web/src/main/js/xtext/services/OccurrencesService.js
deleted file mode 100644
index 2e2d0b1a..00000000
--- a/language-web/src/main/js/xtext/services/OccurrencesService.js
+++ /dev/null
@@ -1,39 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-2.0.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10define(['xtext/services/XtextService', 'jquery'], function(XtextService, jQuery) {
11
12 /**
13 * Service class for marking occurrences.
14 */
15 function OccurrencesService(serviceUrl, resourceId, updateService) {
16 this.initialize(serviceUrl, 'occurrences', resourceId, updateService);
17 };
18
19 OccurrencesService.prototype = new XtextService();
20
21 OccurrencesService.prototype._initServerData = function(serverData, editorContext, params) {
22 if (params.offset)
23 serverData.caretOffset = params.offset;
24 else
25 serverData.caretOffset = editorContext.getCaretOffset();
26 };
27
28 OccurrencesService.prototype._getSuccessCallback = function(editorContext, params, deferred) {
29 return function(result) {
30 if (result.conflict || !params.sendFullText && result.stateId !== undefined
31 && result.stateId != editorContext.getServerState().stateId)
32 deferred.reject();
33 else
34 deferred.resolve(result);
35 }
36 }
37
38 return OccurrencesService;
39}); \ No newline at end of file
diff --git a/language-web/src/main/js/xtext/services/SaveResourceService.js b/language-web/src/main/js/xtext/services/SaveResourceService.js
deleted file mode 100644
index 66cdaff5..00000000
--- a/language-web/src/main/js/xtext/services/SaveResourceService.js
+++ /dev/null
@@ -1,32 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-2.0.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10define(['xtext/services/XtextService', 'jquery'], function(XtextService, jQuery) {
11
12 /**
13 * Service class for saving resources.
14 */
15 function SaveResourceService(serviceUrl, resourceId) {
16 this.initialize(serviceUrl, 'save', resourceId);
17 };
18
19 SaveResourceService.prototype = new XtextService();
20
21 SaveResourceService.prototype._initServerData = function(serverData, editorContext, params) {
22 return {
23 httpMethod: 'POST'
24 };
25 };
26
27 SaveResourceService.prototype._processResult = function(result, editorContext) {
28 editorContext.setDirty(false);
29 };
30
31 return SaveResourceService;
32}); \ No newline at end of file
diff --git a/language-web/src/main/js/xtext/services/UpdateService.js b/language-web/src/main/js/xtext/services/UpdateService.js
deleted file mode 100644
index b78d846d..00000000
--- a/language-web/src/main/js/xtext/services/UpdateService.js
+++ /dev/null
@@ -1,159 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-2.0.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10define(['xtext/services/XtextService', 'jquery'], function(XtextService, jQuery) {
11
12 /**
13 * Service class for updating the server-side representation of a resource.
14 * This service only makes sense with a stateful server, where an update request is sent
15 * after each modification. This can greatly improve response times compared to the
16 * stateless alternative, where the full text content is sent with each service request.
17 */
18 function UpdateService(serviceUrl, resourceId) {
19 this.initialize(serviceUrl, 'update', resourceId, this);
20 this._completionCallbacks = [];
21 };
22
23 UpdateService.prototype = new XtextService();
24
25 /**
26 * Compute a delta between two versions of a text. If a difference is found, the result
27 * contains three properties:
28 * deltaText - the text to insert into s1
29 * deltaOffset - the text insertion offset
30 * deltaReplaceLength - the number of characters that shall be replaced by the inserted text
31 */
32 UpdateService.prototype.computeDelta = function(s1, s2, result) {
33 var start = 0, s1length = s1.length, s2length = s2.length;
34 while (start < s1length && start < s2length && s1.charCodeAt(start) === s2.charCodeAt(start)) {
35 start++;
36 }
37 if (start === s1length && start === s2length) {
38 return;
39 }
40 result.deltaOffset = start;
41 if (start === s1length) {
42 result.deltaText = s2.substring(start, s2length);
43 result.deltaReplaceLength = 0;
44 return;
45 } else if (start === s2length) {
46 result.deltaText = '';
47 result.deltaReplaceLength = s1length - start;
48 return;
49 }
50
51 var end1 = s1length - 1, end2 = s2length - 1;
52 while (end1 >= start && end2 >= start && s1.charCodeAt(end1) === s2.charCodeAt(end2)) {
53 end1--;
54 end2--;
55 }
56 result.deltaText = s2.substring(start, end2 + 1);
57 result.deltaReplaceLength = end1 - start + 1;
58 };
59
60 /**
61 * Invoke all completion callbacks and clear the list afterwards.
62 */
63 UpdateService.prototype.onComplete = function(xhr, textStatus) {
64 var callbacks = this._completionCallbacks;
65 this._completionCallbacks = [];
66 for (var i = 0; i < callbacks.length; i++) {
67 var callback = callbacks[i].callback;
68 var params = callbacks[i].params;
69 callback(params);
70 }
71 }
72
73 /**
74 * Add a callback to be invoked when the service call has completed.
75 */
76 UpdateService.prototype.addCompletionCallback = function(callback, params) {
77 this._completionCallbacks.push({callback: callback, params: params});
78 }
79
80 UpdateService.prototype.invoke = function(editorContext, params, deferred) {
81 if (deferred === undefined) {
82 deferred = jQuery.Deferred();
83 }
84 var knownServerState = editorContext.getServerState();
85 if (knownServerState.updateInProgress) {
86 var self = this;
87 this.addCompletionCallback(function() { self.invoke(editorContext, params, deferred) });
88 return deferred.promise();
89 }
90
91 var serverData = {
92 contentType: params.contentType
93 };
94 var currentText = editorContext.getText();
95 if (params.sendFullText || knownServerState.text === undefined) {
96 serverData.fullText = currentText;
97 } else {
98 this.computeDelta(knownServerState.text, currentText, serverData);
99 if (serverData.deltaText === undefined) {
100 if (params.forceUpdate) {
101 serverData.deltaText = '';
102 serverData.deltaOffset = editorContext.getCaretOffset();
103 serverData.deltaReplaceLength = 0;
104 } else {
105 deferred.resolve(knownServerState);
106 this.onComplete();
107 return deferred.promise();
108 }
109 }
110 serverData.requiredStateId = knownServerState.stateId;
111 }
112
113 knownServerState.updateInProgress = true;
114 var self = this;
115 self.sendRequest(editorContext, {
116 type: 'PUT',
117 data: serverData,
118
119 success: function(result) {
120 if (result.conflict) {
121 // The server has lost its session state and the resource is loaded from the server
122 if (knownServerState.text !== undefined) {
123 delete knownServerState.updateInProgress;
124 delete knownServerState.text;
125 delete knownServerState.stateId;
126 self.invoke(editorContext, params, deferred);
127 } else {
128 deferred.reject(result.conflict);
129 }
130 return false;
131 }
132 var listeners = editorContext.updateServerState(currentText, result.stateId);
133 for (var i = 0; i < listeners.length; i++) {
134 self.addCompletionCallback(listeners[i], params);
135 }
136 deferred.resolve(result);
137 },
138
139 error: function(xhr, textStatus, errorThrown) {
140 if (xhr.status == 404 && !params.loadFromServer && knownServerState.text !== undefined) {
141 // The server has lost its session state and the resource is not loaded from the server
142 delete knownServerState.updateInProgress;
143 delete knownServerState.text;
144 delete knownServerState.stateId;
145 self.invoke(editorContext, params, deferred);
146 return true;
147 }
148 deferred.reject(errorThrown);
149 },
150
151 complete: self.onComplete.bind(self)
152 }, true);
153 return deferred.promise().always(function() {
154 knownServerState.updateInProgress = false;
155 });
156 };
157
158 return UpdateService;
159}); \ No newline at end of file
diff --git a/language-web/src/main/js/xtext/services/ValidationService.js b/language-web/src/main/js/xtext/services/ValidationService.js
deleted file mode 100644
index 85c9953d..00000000
--- a/language-web/src/main/js/xtext/services/ValidationService.js
+++ /dev/null
@@ -1,33 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-2.0.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10define(['xtext/services/XtextService', 'jquery'], function(XtextService, jQuery) {
11
12 /**
13 * Service class for validation.
14 */
15 function ValidationService(serviceUrl, resourceId) {
16 this.initialize(serviceUrl, 'validate', resourceId);
17 };
18
19 ValidationService.prototype = new XtextService();
20
21 ValidationService.prototype._checkPreconditions = function(editorContext, params) {
22 return this._state === undefined;
23 }
24
25 ValidationService.prototype._onConflict = function(editorContext, cause) {
26 this.setState(undefined);
27 return {
28 suppressForcedUpdate: true
29 };
30 };
31
32 return ValidationService;
33}); \ No newline at end of file
diff --git a/language-web/src/main/js/xtext/services/XtextService.js b/language-web/src/main/js/xtext/services/XtextService.js
deleted file mode 100644
index d3a4842f..00000000
--- a/language-web/src/main/js/xtext/services/XtextService.js
+++ /dev/null
@@ -1,280 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015, 2017 itemis AG (http://www.itemis.eu) and others.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-2.0.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10define(['jquery'], function(jQuery) {
11
12 var globalState = {};
13
14 /**
15 * Generic service implementation that can serve as superclass for specialized services.
16 */
17 function XtextService() {};
18
19 /**
20 * Initialize the request metadata for this service class. Two variants:
21 * - initialize(serviceUrl, serviceType, resourceId, updateService)
22 * - initialize(xtextServices, serviceType)
23 */
24 XtextService.prototype.initialize = function() {
25 this._serviceType = arguments[1];
26 if (typeof(arguments[0]) === 'string') {
27 this._requestUrl = arguments[0] + '/' + this._serviceType;
28 var resourceId = arguments[2];
29 if (resourceId)
30 this._encodedResourceId = encodeURIComponent(resourceId);
31 this._updateService = arguments[3];
32 } else {
33 var xtextServices = arguments[0];
34 if (xtextServices.options) {
35 this._requestUrl = xtextServices.options.serviceUrl + '/' + this._serviceType;
36 var resourceId = xtextServices.options.resourceId;
37 if (resourceId)
38 this._encodedResourceId = encodeURIComponent(resourceId);
39 }
40 this._updateService = xtextServices.updateService;
41 }
42 }
43
44 XtextService.prototype.setState = function(state) {
45 this._state = state;
46 }
47
48 /**
49 * Invoke the service with default service behavior.
50 */
51 XtextService.prototype.invoke = function(editorContext, params, deferred, callbacks) {
52 if (deferred === undefined) {
53 deferred = jQuery.Deferred();
54 }
55 if (jQuery.isFunction(this._checkPreconditions) && !this._checkPreconditions(editorContext, params)) {
56 deferred.reject();
57 return deferred.promise();
58 }
59 var serverData = {
60 contentType: params.contentType
61 };
62 var initResult;
63 if (jQuery.isFunction(this._initServerData))
64 initResult = this._initServerData(serverData, editorContext, params);
65 var httpMethod = 'GET';
66 if (initResult && initResult.httpMethod)
67 httpMethod = initResult.httpMethod;
68 var self = this;
69 if (!(initResult && initResult.suppressContent)) {
70 if (params.sendFullText) {
71 serverData.fullText = editorContext.getText();
72 httpMethod = 'POST';
73 } else {
74 var knownServerState = editorContext.getServerState();
75 if (knownServerState.updateInProgress) {
76 if (self._updateService) {
77 self._updateService.addCompletionCallback(function() {
78 self.invoke(editorContext, params, deferred);
79 });
80 } else {
81 deferred.reject();
82 }
83 return deferred.promise();
84 }
85 if (knownServerState.stateId !== undefined) {
86 serverData.requiredStateId = knownServerState.stateId;
87 }
88 }
89 }
90
91 var onSuccess;
92 if (jQuery.isFunction(this._getSuccessCallback)) {
93 onSuccess = this._getSuccessCallback(editorContext, params, deferred);
94 } else {
95 onSuccess = function(result) {
96 if (result.conflict) {
97 if (self._increaseRecursionCount(editorContext)) {
98 var onConflictResult;
99 if (jQuery.isFunction(self._onConflict)) {
100 onConflictResult = self._onConflict(editorContext, result.conflict);
101 }
102 if (!(onConflictResult && onConflictResult.suppressForcedUpdate) && !params.sendFullText
103 && result.conflict == 'invalidStateId' && self._updateService) {
104 self._updateService.addCompletionCallback(function() {
105 self.invoke(editorContext, params, deferred);
106 });
107 var knownServerState = editorContext.getServerState();
108 delete knownServerState.stateId;
109 delete knownServerState.text;
110 self._updateService.invoke(editorContext, params);
111 } else {
112 self.invoke(editorContext, params, deferred);
113 }
114 } else {
115 deferred.reject();
116 }
117 return false;
118 }
119 if (jQuery.isFunction(self._processResult)) {
120 var processedResult = self._processResult(result, editorContext);
121 if (processedResult) {
122 deferred.resolve(processedResult);
123 return true;
124 }
125 }
126 deferred.resolve(result);
127 };
128 }
129
130 var onError = function(xhr, textStatus, errorThrown) {
131 if (xhr.status == 404 && !params.loadFromServer && self._increaseRecursionCount(editorContext)) {
132 var onConflictResult;
133 if (jQuery.isFunction(self._onConflict)) {
134 onConflictResult = self._onConflict(editorContext, errorThrown);
135 }
136 var knownServerState = editorContext.getServerState();
137 if (!(onConflictResult && onConflictResult.suppressForcedUpdate)
138 && knownServerState.text !== undefined && self._updateService) {
139 self._updateService.addCompletionCallback(function() {
140 self.invoke(editorContext, params, deferred);
141 });
142 delete knownServerState.stateId;
143 delete knownServerState.text;
144 self._updateService.invoke(editorContext, params);
145 return true;
146 }
147 }
148 deferred.reject(errorThrown);
149 }
150
151 self.sendRequest(editorContext, {
152 type: httpMethod,
153 data: serverData,
154 success: onSuccess,
155 error: onError
156 }, !params.sendFullText);
157 return deferred.promise().always(function() {
158 self._recursionCount = undefined;
159 });
160 }
161
162 /**
163 * Send an HTTP request to invoke the service.
164 */
165 XtextService.prototype.sendRequest = function(editorContext, settings, needsSession) {
166 var self = this;
167 self.setState('started');
168 var corsEnabled = editorContext.xtextServices.options['enableCors'];
169 if(corsEnabled) {
170 settings.crossDomain = true;
171 settings.xhrFields = {withCredentials: true};
172 }
173 var onSuccess = settings.success;
174 settings.success = function(result) {
175 var accepted = true;
176 if (jQuery.isFunction(onSuccess)) {
177 accepted = onSuccess(result);
178 }
179 if (accepted || accepted === undefined) {
180 self.setState('finished');
181 if (editorContext.xtextServices) {
182 var successListeners = editorContext.xtextServices.successListeners;
183 if (successListeners) {
184 for (var i = 0; i < successListeners.length; i++) {
185 var listener = successListeners[i];
186 if (jQuery.isFunction(listener)) {
187 listener(self._serviceType, result);
188 }
189 }
190 }
191 }
192 }
193 };
194
195 var onError = settings.error;
196 settings.error = function(xhr, textStatus, errorThrown) {
197 var resolved = false;
198 if (jQuery.isFunction(onError)) {
199 resolved = onError(xhr, textStatus, errorThrown);
200 }
201 if (!resolved) {
202 self.setState(undefined);
203 self._reportError(editorContext, textStatus, errorThrown, xhr);
204 }
205 };
206
207 settings.async = true;
208 var requestUrl = self._requestUrl;
209 if (!settings.data.resource && self._encodedResourceId) {
210 if (requestUrl.indexOf('?') >= 0)
211 requestUrl += '&resource=' + self._encodedResourceId;
212 else
213 requestUrl += '?resource=' + self._encodedResourceId;
214 }
215
216 if (needsSession && globalState._initPending) {
217 // We have to wait until the initial request has finished to make sure the client has
218 // received a valid session id
219 if (!globalState._waitingRequests)
220 globalState._waitingRequests = [];
221 globalState._waitingRequests.push({requestUrl: requestUrl, settings: settings});
222 } else {
223 if (needsSession && !globalState._initDone) {
224 globalState._initPending = true;
225 var onComplete = settings.complete;
226 settings.complete = function(xhr, textStatus) {
227 if (jQuery.isFunction(onComplete)) {
228 onComplete(xhr, textStatus);
229 }
230 delete globalState._initPending;
231 globalState._initDone = true;
232 if (globalState._waitingRequests) {
233 for (var i = 0; i < globalState._waitingRequests.length; i++) {
234 var request = globalState._waitingRequests[i];
235 jQuery.ajax(request.requestUrl, request.settings);
236 }
237 delete globalState._waitingRequests;
238 }
239 }
240 }
241 jQuery.ajax(requestUrl, settings);
242 }
243 }
244
245 /**
246 * Use this in case of a conflict before retrying the service invocation. If the number
247 * of retries exceeds the limit, an error is reported and the function returns false.
248 */
249 XtextService.prototype._increaseRecursionCount = function(editorContext) {
250 if (this._recursionCount === undefined)
251 this._recursionCount = 1;
252 else
253 this._recursionCount++;
254
255 if (this._recursionCount >= 10) {
256 this._reportError(editorContext, 'warning', 'Xtext service request failed after 10 attempts.', {});
257 return false;
258 }
259 return true;
260 },
261
262 /**
263 * Report an error to the listeners.
264 */
265 XtextService.prototype._reportError = function(editorContext, severity, message, requestData) {
266 if (editorContext.xtextServices) {
267 var errorListeners = editorContext.xtextServices.errorListeners;
268 if (errorListeners) {
269 for (var i = 0; i < errorListeners.length; i++) {
270 var listener = errorListeners[i];
271 if (jQuery.isFunction(listener)) {
272 listener(this._serviceType, severity, message, requestData);
273 }
274 }
275 }
276 }
277 }
278
279 return XtextService;
280});
diff --git a/language-web/src/main/js/xtext/xtext-codemirror.d.ts b/language-web/src/main/js/xtext/xtext-codemirror.d.ts
deleted file mode 100644
index fff850b8..00000000
--- a/language-web/src/main/js/xtext/xtext-codemirror.d.ts
+++ /dev/null
@@ -1,43 +0,0 @@
1import { Editor } from 'codemirror';
2
3export function createEditor(options: IXtextOptions): IXtextCodeMirrorEditor;
4
5export function createServices(editor: Editor, options: IXtextOptions): IXtextServices;
6
7export function removeServices(editor: Editor): void;
8
9export interface IXtextOptions {
10 baseUrl?: string;
11 contentType?: string;
12 dirtyElement?: string | Element;
13 dirtyStatusClass?: string;
14 document?: Document;
15 enableContentAssistService?: boolean;
16 enableCors?: boolean;
17 enableFormattingAction?: boolean;
18 enableFormattingService?: boolean;
19 enableGeneratorService?: boolean;
20 enableHighlightingService?: boolean;
21 enableOccurrencesService?: boolean;
22 enableSaveAction?: boolean;
23 enableValidationService?: boolean;
24 loadFromServer?: boolean;
25 mode?: string;
26 parent?: string | Element;
27 parentClass?: string;
28 resourceId?: string;
29 selectionUpdateDelay?: number;
30 sendFullText?: boolean;
31 serviceUrl?: string;
32 showErrorDialogs?: boolean;
33 syntaxDefinition?: string;
34 textUpdateDelay?: number;
35 xtextLang?: string;
36}
37
38export interface IXtextCodeMirrorEditor extends Editor {
39 xtextServices: IXtextServices;
40}
41
42export interface IXtextServices {
43}
diff --git a/language-web/src/main/js/xtext/xtext-codemirror.js b/language-web/src/main/js/xtext/xtext-codemirror.js
deleted file mode 100644
index d246172a..00000000
--- a/language-web/src/main/js/xtext/xtext-codemirror.js
+++ /dev/null
@@ -1,473 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015, 2017 itemis AG (http://www.itemis.eu) and others.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-2.0.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10/*
11 * Use `createEditor(options)` to create an Xtext editor. You can specify options either
12 * through the function parameter or through `data-editor-x` attributes, where x is an
13 * option name with camelCase converted to hyphen-separated.
14 * In addition to the options supported by CodeMirror (https://codemirror.net/doc/manual.html#config),
15 * the following options are available:
16 *
17 * baseUrl = "/" {String}
18 * The path segment where the Xtext service is found; see serviceUrl option.
19 * contentType {String}
20 * The content type included in requests to the Xtext server.
21 * dirtyElement {String | DOMElement}
22 * An element into which the dirty status class is written when the editor is marked dirty;
23 * it can be either a DOM element or an ID for a DOM element.
24 * dirtyStatusClass = 'dirty' {String}
25 * A CSS class name written into the dirtyElement when the editor is marked dirty.
26 * document {Document}
27 * The document; if not specified, the global document is used.
28 * enableContentAssistService = true {Boolean}
29 * Whether content assist should be enabled.
30 * enableCors = true {Boolean}
31 * Whether CORS should be enabled for service request.
32 * enableFormattingAction = false {Boolean}
33 * Whether the formatting action should be bound to the standard keystroke ctrl+shift+s / cmd+shift+f.
34 * enableFormattingService = true {Boolean}
35 * Whether text formatting should be enabled.
36 * enableGeneratorService = true {Boolean}
37 * Whether code generation should be enabled (must be triggered through JavaScript code).
38 * enableHighlightingService = true {Boolean}
39 * Whether semantic highlighting (computed on the server) should be enabled.
40 * enableOccurrencesService = true {Boolean}
41 * Whether marking occurrences should be enabled.
42 * enableSaveAction = false {Boolean}
43 * Whether the save action should be bound to the standard keystroke ctrl+s / cmd+s.
44 * enableValidationService = true {Boolean}
45 * Whether validation should be enabled.
46 * loadFromServer = true {Boolean}
47 * Whether to load the editor content from the server.
48 * mode {String}
49 * The name of the syntax highlighting mode to use; the mode has to be registered externally
50 * (see CodeMirror documentation).
51 * parent = 'xtext-editor' {String | DOMElement}
52 * The parent element for the view; it can be either a DOM element or an ID for a DOM element.
53 * parentClass = 'xtext-editor' {String}
54 * If the 'parent' option is not given, this option is used to find elements that match the given class name.
55 * resourceId {String}
56 * The identifier of the resource displayed in the text editor; this option is sent to the server to
57 * communicate required information on the respective resource.
58 * selectionUpdateDelay = 550 {Number}
59 * The number of milliseconds to wait after a selection change before Xtext services are invoked.
60 * sendFullText = false {Boolean}
61 * Whether the full text shall be sent to the server with each request; use this if you want
62 * the server to run in stateless mode. If the option is inactive, the server state is updated regularly.
63 * serviceUrl {String}
64 * The URL of the Xtext servlet; if no value is given, it is constructed using the baseUrl option in the form
65 * {location.protocol}//{location.host}{baseUrl}xtext-service
66 * showErrorDialogs = false {Boolean}
67 * Whether errors should be displayed in popup dialogs.
68 * syntaxDefinition {String}
69 * If the 'mode' option is not set, the default mode 'xtext/{xtextLang}' is used. Set this option to
70 * 'none' to suppress this behavior and disable syntax highlighting.
71 * textUpdateDelay = 500 {Number}
72 * The number of milliseconds to wait after a text change before Xtext services are invoked.
73 * xtextLang {String}
74 * The language name (usually the file extension configured for the language).
75 */
76define([
77 'jquery',
78 'codemirror',
79 'codemirror/addon/hint/show-hint',
80 'xtext/compatibility',
81 'xtext/ServiceBuilder',
82 'xtext/CodeMirrorEditorContext',
83 'codemirror/mode/javascript/javascript'
84], function(jQuery, CodeMirror, ShowHint, compatibility, ServiceBuilder, EditorContext) {
85
86 var exports = {};
87
88 /**
89 * Create one or more Xtext editor instances configured with the given options.
90 * The return value is either a CodeMirror editor or an array of CodeMirror editors.
91 */
92 exports.createEditor = function(options) {
93 if (!options)
94 options = {};
95
96 var query;
97 if (jQuery.type(options.parent) === 'string') {
98 query = jQuery('#' + options.parent, options.document);
99 } else if (options.parent) {
100 query = jQuery(options.parent);
101 } else if (jQuery.type(options.parentClass) === 'string') {
102 query = jQuery('.' + options.parentClass, options.document);
103 } else {
104 query = jQuery('#xtext-editor', options.document);
105 if (query.length == 0)
106 query = jQuery('.xtext-editor', options.document);
107 }
108
109 var editors = [];
110 query.each(function(index, parent) {
111 var editorOptions = ServiceBuilder.mergeParentOptions(parent, options);
112 if (!editorOptions.value)
113 editorOptions.value = jQuery(parent).text();
114 var editor = CodeMirror(function(element) {
115 jQuery(parent).empty().append(element);
116 }, editorOptions);
117
118 exports.createServices(editor, editorOptions);
119 editors[index] = editor;
120 });
121
122 if (editors.length == 1)
123 return editors[0];
124 else
125 return editors;
126 }
127
128 function CodeMirrorServiceBuilder(editor, xtextServices) {
129 this.editor = editor;
130 xtextServices.editorContext._highlightingMarkers = [];
131 xtextServices.editorContext._validationMarkers = [];
132 xtextServices.editorContext._occurrenceMarkers = [];
133 ServiceBuilder.call(this, xtextServices);
134 }
135 CodeMirrorServiceBuilder.prototype = new ServiceBuilder();
136
137 /**
138 * Configure Xtext services for the given editor. The editor does not have to be created
139 * with createEditor(options).
140 */
141 exports.createServices = function(editor, options) {
142 if (options.enableValidationService || options.enableValidationService === undefined) {
143 editor.setOption('gutters', ['annotations-gutter']);
144 }
145 var xtextServices = {
146 options: options,
147 editorContext: new EditorContext(editor)
148 };
149 var serviceBuilder = new CodeMirrorServiceBuilder(editor, xtextServices);
150 serviceBuilder.createServices();
151 xtextServices.serviceBuilder = serviceBuilder;
152 editor.xtextServices = xtextServices;
153 return xtextServices;
154 }
155
156 /**
157 * Remove all services and listeners that have been previously created with createServices(editor, options).
158 */
159 exports.removeServices = function(editor) {
160 if (!editor.xtextServices)
161 return;
162 var services = editor.xtextServices;
163 if (services.modelChangeListener)
164 editor.off('changes', services.modelChangeListener);
165 if (services.cursorActivityListener)
166 editor.off('cursorActivity', services.cursorActivityListener);
167 if (services.saveKeyMap)
168 editor.removeKeyMap(services.saveKeyMap);
169 if (services.contentAssistKeyMap)
170 editor.removeKeyMap(services.contentAssistKeyMap);
171 if (services.formatKeyMap)
172 editor.removeKeyMap(services.formatKeyMap);
173 var editorContext = services.editorContext;
174 var highlightingMarkers = editorContext._highlightingMarkers;
175 if (highlightingMarkers) {
176 for (var i = 0; i < highlightingMarkers.length; i++) {
177 highlightingMarkers[i].clear();
178 }
179 }
180 if (editorContext._validationAnnotations)
181 services.serviceBuilder._clearAnnotations(editorContext._validationAnnotations);
182 var validationMarkers = editorContext._validationMarkers;
183 if (validationMarkers) {
184 for (var i = 0; i < validationMarkers.length; i++) {
185 validationMarkers[i].clear();
186 }
187 }
188 var occurrenceMarkers = editorContext._occurrenceMarkers;
189 if (occurrenceMarkers) {
190 for (var i = 0; i < occurrenceMarkers.length; i++)  {
191 occurrenceMarkers[i].clear();
192 }
193 }
194 delete editor.xtextServices;
195 }
196
197 /**
198 * Syntax highlighting (without semantic highlighting).
199 */
200 CodeMirrorServiceBuilder.prototype.setupSyntaxHighlighting = function() {
201 var options = this.services.options;
202 // If the mode option is set, syntax highlighting has already been configured by CM
203 if (!options.mode && options.syntaxDefinition != 'none' && options.xtextLang) {
204 this.editor.setOption('mode', 'xtext/' + options.xtextLang);
205 }
206 }
207
208 /**
209 * Document update service.
210 */
211 CodeMirrorServiceBuilder.prototype.setupUpdateService = function(refreshDocument) {
212 var services = this.services;
213 var editorContext = services.editorContext;
214 var textUpdateDelay = services.options.textUpdateDelay;
215 if (!textUpdateDelay)
216 textUpdateDelay = 500;
217 services.modelChangeListener = function(event) {
218 if (!event._xtext_init)
219 editorContext.setDirty(true);
220 if (editorContext._modelChangeTimeout)
221 clearTimeout(editorContext._modelChangeTimeout);
222 editorContext._modelChangeTimeout = setTimeout(function() {
223 if (services.options.sendFullText)
224 refreshDocument();
225 else
226 services.update();
227 }, textUpdateDelay);
228 }
229 if (!services.options.resourceId || !services.options.loadFromServer)
230 services.modelChangeListener({_xtext_init: true});
231 this.editor.on('changes', services.modelChangeListener);
232 }
233
234 /**
235 * Persistence services: load, save, and revert.
236 */
237 CodeMirrorServiceBuilder.prototype.setupPersistenceServices = function() {
238 var services = this.services;
239 if (services.options.enableSaveAction) {
240 var userAgent = navigator.userAgent.toLowerCase();
241 var saveFunction = function(editor) {
242 services.saveResource();
243 };
244 services.saveKeyMap = /mac os/.test(userAgent) ? {'Cmd-S': saveFunction}: {'Ctrl-S': saveFunction};
245 this.editor.addKeyMap(services.saveKeyMap);
246 }
247 }
248
249 /**
250 * Content assist service.
251 */
252 CodeMirrorServiceBuilder.prototype.setupContentAssistService = function() {
253 var services = this.services;
254 var editorContext = services.editorContext;
255 services.contentAssistKeyMap = {'Ctrl-Space': function(editor) {
256 var params = ServiceBuilder.copy(services.options);
257 var cursor = editor.getCursor();
258 params.offset = editor.indexFromPos(cursor);
259 services.contentAssistService.invoke(editorContext, params).done(function(entries) {
260 editor.showHint({hint: function(editor, options) {
261 return {
262 list: entries.map(function(entry) {
263 var displayText;
264 if (entry.label)
265 displayText = entry.label;
266 else
267 displayText = entry.proposal;
268 if (entry.description)
269 displayText += ' (' + entry.description + ')';
270 var prefixLength = 0
271 if (entry.prefix)
272 prefixLength = entry.prefix.length
273 return {
274 text: entry.proposal,
275 displayText: displayText,
276 from: {
277 line: cursor.line,
278 ch: cursor.ch - prefixLength
279 }
280 };
281 }),
282 from: cursor,
283 to: cursor
284 };
285 }});
286 });
287 }};
288 this.editor.addKeyMap(services.contentAssistKeyMap);
289 }
290
291 /**
292 * Semantic highlighting service.
293 */
294 CodeMirrorServiceBuilder.prototype.doHighlighting = function() {
295 var services = this.services;
296 var editorContext = services.editorContext;
297 var editor = this.editor;
298 services.computeHighlighting().always(function() {
299 var highlightingMarkers = editorContext._highlightingMarkers;
300 if (highlightingMarkers) {
301 for (var i = 0; i < highlightingMarkers.length; i++) {
302 highlightingMarkers[i].clear();
303 }
304 }
305 editorContext._highlightingMarkers = [];
306 }).done(function(result) {
307 for (var i = 0; i < result.regions.length; ++i) {
308 var region = result.regions[i];
309 var from = editor.posFromIndex(region.offset);
310 var to = editor.posFromIndex(region.offset + region.length);
311 region.styleClasses.forEach(function(styleClass) {
312 var marker = editor.markText(from, to, {className: styleClass});
313 editorContext._highlightingMarkers.push(marker);
314 });
315 }
316 });
317 }
318
319 var annotationWeight = {
320 error: 30,
321 warning: 20,
322 info: 10
323 };
324 CodeMirrorServiceBuilder.prototype._getAnnotationWeight = function(annotation) {
325 if (annotationWeight[annotation] !== undefined)
326 return annotationWeight[annotation];
327 else
328 return 0;
329 }
330
331 CodeMirrorServiceBuilder.prototype._clearAnnotations = function(annotations) {
332 var editor = this.editor;
333 editor.clearGutter('annotations-gutter');
334 for (var i = 0; i < annotations.length; i++) {
335 var annotation = annotations[i];
336 if (annotation) {
337 annotations[i] = undefined;
338 }
339 }
340 }
341
342 CodeMirrorServiceBuilder.prototype._refreshAnnotations = function(annotations) {
343 var editor = this.editor;
344 for (var i = 0; i < annotations.length; i++) {
345 var annotation = annotations[i];
346 if (annotation) {
347 var classProp = ' class="xtext-annotation_' + annotation.type + '"';
348 var titleProp = annotation.description ? ' title="' + annotation.description.replace(/"/g, '&quot;') + '"' : '';
349 var element = jQuery('<div' + classProp + titleProp + '></div>').get(0);
350 editor.setGutterMarker(i, 'annotations-gutter', element);
351 }
352 }
353 }
354
355 /**
356 * Validation service.
357 */
358 CodeMirrorServiceBuilder.prototype.doValidation = function() {
359 var services = this.services;
360 var editorContext = services.editorContext;
361 var editor = this.editor;
362 var self = this;
363 services.validate().always(function() {
364 if (editorContext._validationAnnotations)
365 self._clearAnnotations(editorContext._validationAnnotations);
366 else
367 editorContext._validationAnnotations = [];
368 var validationMarkers = editorContext._validationMarkers;
369 if (validationMarkers) {
370 for (var i = 0; i < validationMarkers.length; i++) {
371 validationMarkers[i].clear();
372 }
373 }
374 editorContext._validationMarkers = [];
375 }).done(function(result) {
376 var validationAnnotations = editorContext._validationAnnotations;
377 for (var i = 0; i < result.issues.length; i++) {
378 var entry = result.issues[i];
379 var annotation = validationAnnotations[entry.line - 1];
380 var weight = self._getAnnotationWeight(entry.severity);
381 if (annotation) {
382 if (annotation.weight < weight) {
383 annotation.type = entry.severity;
384 annotation.weight = weight;
385 }
386 if (annotation.description)
387 annotation.description += '\n' + entry.description;
388 else
389 annotation.description = entry.description;
390 } else {
391 validationAnnotations[entry.line - 1] = {
392 type: entry.severity,
393 weight: weight,
394 description: entry.description
395 };
396 }
397 var from = editor.posFromIndex(entry.offset);
398 var to = editor.posFromIndex(entry.offset + entry.length);
399 var marker = editor.markText(from, to, {
400 className: 'xtext-marker_' + entry.severity,
401 title: entry.description
402 });
403 editorContext._validationMarkers.push(marker);
404 }
405 self._refreshAnnotations(validationAnnotations);
406 });
407 }
408
409 /**
410 * Occurrences service.
411 */
412 CodeMirrorServiceBuilder.prototype.setupOccurrencesService = function() {
413 var services = this.services;
414 var editorContext = services.editorContext;
415 var selectionUpdateDelay = services.options.selectionUpdateDelay;
416 if (!selectionUpdateDelay)
417 selectionUpdateDelay = 550;
418 var editor = this.editor;
419 var self = this;
420 services.cursorActivityListener = function() {
421 if (editorContext._selectionChangeTimeout) {
422 clearTimeout(editorContext._selectionChangeTimeout);
423 }
424 editorContext._selectionChangeTimeout = setTimeout(function() {
425 var params = ServiceBuilder.copy(services.options);
426 var cursor = editor.getCursor();
427 params.offset = editor.indexFromPos(cursor);
428 services.occurrencesService.invoke(editorContext, params).always(function() {
429 var occurrenceMarkers = editorContext._occurrenceMarkers;
430 if (occurrenceMarkers) {
431 for (var i = 0; i < occurrenceMarkers.length; i++)  {
432 occurrenceMarkers[i].clear();
433 }
434 }
435 editorContext._occurrenceMarkers = [];
436 }).done(function(occurrencesResult) {
437 for (var i = 0; i < occurrencesResult.readRegions.length; i++) {
438 var region = occurrencesResult.readRegions[i];
439 var from = editor.posFromIndex(region.offset);
440 var to = editor.posFromIndex(region.offset + region.length);
441 var marker = editor.markText(from, to, {className: 'xtext-marker_read'});
442 editorContext._occurrenceMarkers.push(marker);
443 }
444 for (var i = 0; i < occurrencesResult.writeRegions.length; i++) {
445 var region = occurrencesResult.writeRegions[i];
446 var from = editor.posFromIndex(region.offset);
447 var to = editor.posFromIndex(region.offset + region.length);
448 var marker = editor.markText(from, to, {className: 'xtext-marker_write'});
449 editorContext._occurrenceMarkers.push(marker);
450 }
451 });
452 }, selectionUpdateDelay);
453 }
454 editor.on('cursorActivity', services.cursorActivityListener);
455 }
456
457 /**
458 * Formatting service.
459 */
460 CodeMirrorServiceBuilder.prototype.setupFormattingService = function() {
461 var services = this.services;
462 if (services.options.enableFormattingAction) {
463 var userAgent = navigator.userAgent.toLowerCase();
464 var formatFunction = function(editor) {
465 services.format();
466 };
467 services.formatKeyMap = /mac os/.test(userAgent) ? {'Shift-Cmd-F': formatFunction}: {'Shift-Ctrl-S': formatFunction};
468 this.editor.addKeyMap(services.formatKeyMap);
469 }
470 }
471
472 return exports;
473});
diff --git a/language-web/src/main/js/editor/xtextMessages.ts b/language-web/src/main/js/xtext/xtextMessages.ts
index 68737958..68737958 100644
--- a/language-web/src/main/js/editor/xtextMessages.ts
+++ b/language-web/src/main/js/xtext/xtextMessages.ts
diff --git a/language-web/src/main/js/editor/xtextServiceResults.ts b/language-web/src/main/js/xtext/xtextServiceResults.ts
index 6c3d9daf..6c3d9daf 100644
--- a/language-web/src/main/js/editor/xtextServiceResults.ts
+++ b/language-web/src/main/js/xtext/xtextServiceResults.ts
diff --git a/language-web/tsconfig.json b/language-web/tsconfig.json
index d028a64f..f1f24a1f 100644
--- a/language-web/tsconfig.json
+++ b/language-web/tsconfig.json
@@ -3,9 +3,6 @@
3 "target": "ES2020", 3 "target": "ES2020",
4 "module": "ES2020", 4 "module": "ES2020",
5 "moduleResolution": "node", 5 "moduleResolution": "node",
6 "paths": {
7 "xtext/*": ["./src/main/js/xtext/*"],
8 },
9 "esModuleInterop": true, 6 "esModuleInterop": true,
10 "allowSyntheticDefaultImports": true, 7 "allowSyntheticDefaultImports": true,
11 "jsx": "react", 8 "jsx": "react",
diff --git a/language-web/tsconfig.sonar.json b/language-web/tsconfig.sonar.json
index 1cc74f23..ea3ecc19 100644
--- a/language-web/tsconfig.sonar.json
+++ b/language-web/tsconfig.sonar.json
@@ -3,9 +3,6 @@
3 "target": "ES2020", 3 "target": "ES2020",
4 "module": "ES2020", 4 "module": "ES2020",
5 "moduleResolution": "node", 5 "moduleResolution": "node",
6 "paths": {
7 "xtext/*": ["./src/main/js/xtext/*"]
8 },
9 "esModuleInterop": true, 6 "esModuleInterop": true,
10 "allowSyntheticDefaultImports": true, 7 "allowSyntheticDefaultImports": true,
11 "jsx": "react", 8 "jsx": "react",
diff --git a/language-web/webpack.config.js b/language-web/webpack.config.js
index 6714fa6b..801a705c 100644
--- a/language-web/webpack.config.js
+++ b/language-web/webpack.config.js
@@ -28,7 +28,6 @@ const resolveSources = sources => path.resolve(__dirname, 'src', sources);
28const mainJsSources = resolveSources('main/js'); 28const mainJsSources = resolveSources('main/js');
29const babelLoaderFilters = { 29const babelLoaderFilters = {
30 include: [mainJsSources], 30 include: [mainJsSources],
31 exclude: [resolveSources('main/js/xtext')],
32}; 31};
33const babelPresets = [ 32const babelPresets = [
34 [ 33 [
@@ -151,9 +150,6 @@ module.exports = {
151 mainJsSources, 150 mainJsSources,
152 ], 151 ],
153 extensions: ['.js', '.jsx', '.ts', '.tsx'], 152 extensions: ['.js', '.jsx', '.ts', '.tsx'],
154 alias: {
155 images: resolveSources('main/images'),
156 },
157 }, 153 },
158 devtool: devMode ? 'inline-source-map' : 'source-map', 154 devtool: devMode ? 'inline-source-map' : 'source-map',
159 optimization: { 155 optimization: {