diff options
Diffstat (limited to 'language-web/src/main')
31 files changed, 287 insertions, 2088 deletions
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 | ||
45 | import { problemLanguageSupport } from '../language/problemLanguageSupport'; | ||
45 | import { getLogger } from '../logging'; | 46 | import { getLogger } from '../logging'; |
46 | import { problemLanguageSupport } from './problemLanguageSupport'; | ||
47 | import type { ThemeStore } from '../theme/ThemeStore'; | 47 | import type { ThemeStore } from '../theme/ThemeStore'; |
48 | import { XtextClient } from './XtextClient'; | 48 | import { XtextClient } from '../xtext/XtextClient'; |
49 | 49 | ||
50 | const log = getLogger('EditorStore'); | 50 | const 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 | |||
10 | define([], 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 @@ | |||
1 | import type { | ||
2 | Completion, | ||
3 | CompletionContext, | ||
4 | CompletionResult, | ||
5 | } from '@codemirror/autocomplete'; | ||
6 | import type { ChangeSet, Transaction } from '@codemirror/state'; | ||
7 | |||
8 | import { getLogger } from '../logging'; | ||
9 | import type { UpdateService } from './UpdateService'; | ||
10 | |||
11 | const log = getLogger('xtext.ContentAssistService'); | ||
12 | |||
13 | export 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 | |||
10 | define([ | ||
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 @@ | |||
1 | import { | 1 | import { |
2 | Completion, | ||
3 | CompletionContext, | ||
4 | CompletionResult, | ||
5 | } from '@codemirror/autocomplete'; | ||
6 | import type { Diagnostic } from '@codemirror/lint'; | ||
7 | import { | ||
8 | ChangeDesc, | 2 | ChangeDesc, |
9 | ChangeSet, | 3 | ChangeSet, |
10 | Transaction, | 4 | Transaction, |
11 | } from '@codemirror/state'; | 5 | } from '@codemirror/state'; |
12 | import { nanoid } from 'nanoid'; | 6 | import { nanoid } from 'nanoid'; |
13 | 7 | ||
14 | import type { EditorStore } from './EditorStore'; | 8 | import type { EditorStore } from '../editor/EditorStore'; |
15 | import { getLogger } from '../logging'; | 9 | import { getLogger } from '../logging'; |
10 | import type { XtextWebSocketClient } from './XtextWebSocketClient'; | ||
11 | import { PendingTask } from '../utils/PendingTask'; | ||
16 | import { Timer } from '../utils/Timer'; | 12 | import { Timer } from '../utils/Timer'; |
17 | import { | 13 | import { |
18 | IContentAssistEntry, | 14 | IContentAssistEntry, |
19 | isContentAssistResult, | 15 | isContentAssistResult, |
20 | isDocumentStateResult, | 16 | isDocumentStateResult, |
21 | isInvalidStateIdConflictResult, | 17 | isInvalidStateIdConflictResult, |
22 | isValidationResult, | ||
23 | } from './xtextServiceResults'; | 18 | } from './xtextServiceResults'; |
24 | import { XtextWebSocketClient } from './XtextWebSocketClient'; | ||
25 | import { PendingTask } from '../utils/PendingTask'; | ||
26 | 19 | ||
27 | const UPDATE_TIMEOUT_MS = 500; | 20 | const UPDATE_TIMEOUT_MS = 500; |
28 | 21 | ||
29 | const WAIT_FOR_UPDATE_TIMEOUT_MS = 1000; | 22 | const WAIT_FOR_UPDATE_TIMEOUT_MS = 1000; |
30 | 23 | ||
31 | const log = getLogger('XtextClient'); | 24 | const log = getLogger('xtext.UpdateService'); |
32 | 25 | ||
33 | export class XtextClient { | 26 | export interface IAbortSignal { |
34 | resourceName: string; | 27 | aborted: boolean; |
28 | } | ||
35 | 29 | ||
36 | webSocketClient: XtextWebSocketClient; | 30 | export 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 @@ | |||
1 | import type { Diagnostic } from '@codemirror/lint'; | ||
2 | |||
3 | import type { EditorStore } from '../editor/EditorStore'; | ||
4 | import { getLogger } from '../logging'; | ||
5 | import type { UpdateService } from './UpdateService'; | ||
6 | import { isValidationResult } from './xtextServiceResults'; | ||
7 | |||
8 | const log = getLogger('xtext.ValidationService'); | ||
9 | |||
10 | export 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 @@ | |||
1 | import type { | ||
2 | CompletionContext, | ||
3 | CompletionResult, | ||
4 | } from '@codemirror/autocomplete'; | ||
5 | import type { Transaction } from '@codemirror/state'; | ||
6 | |||
7 | import type { EditorStore } from '../editor/EditorStore'; | ||
8 | import { ContentAssistService } from './ContentAssistService'; | ||
9 | import { getLogger } from '../logging'; | ||
10 | import { UpdateService } from './UpdateService'; | ||
11 | import { ValidationService } from './ValidationService'; | ||
12 | import { XtextWebSocketClient } from './XtextWebSocketClient'; | ||
13 | |||
14 | const log = getLogger('xtext.XtextClient'); | ||
15 | |||
16 | export 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 | |||
10 | define([], 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 | |||
10 | define(['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 | |||
10 | define(['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 | |||
10 | define(['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 | |||
10 | define(['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 | |||
10 | define(['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 | |||
10 | define(['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 | |||
10 | define(['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 | |||
10 | define(['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 | |||
10 | define(['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 | |||
10 | define(['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 @@ | |||
1 | import { Editor } from 'codemirror'; | ||
2 | |||
3 | export function createEditor(options: IXtextOptions): IXtextCodeMirrorEditor; | ||
4 | |||
5 | export function createServices(editor: Editor, options: IXtextOptions): IXtextServices; | ||
6 | |||
7 | export function removeServices(editor: Editor): void; | ||
8 | |||
9 | export 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 | |||
38 | export interface IXtextCodeMirrorEditor extends Editor { | ||
39 | xtextServices: IXtextServices; | ||
40 | } | ||
41 | |||
42 | export 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 | */ | ||
76 | define([ | ||
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, '"') + '"' : ''; | ||
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 | |||