diff options
author | Kristóf Marussy <marussy@mit.bme.hu> | 2021-06-29 11:37:14 +0200 |
---|---|---|
committer | Kristóf Marussy <marussy@mit.bme.hu> | 2021-06-29 11:37:14 +0200 |
commit | 272c7c5dd04feb54806b92d88dc1a5029cddc397 (patch) | |
tree | decf6950c10782d124c5a3b6055a09d52f9e5791 /language-web/src/main | |
parent | Fix derived state computer idempotency (diff) | |
download | refinery-272c7c5dd04feb54806b92d88dc1a5029cddc397.tar.gz refinery-272c7c5dd04feb54806b92d88dc1a5029cddc397.tar.zst refinery-272c7c5dd04feb54806b92d88dc1a5029cddc397.zip |
Webpack build for frontend
Diffstat (limited to 'language-web/src/main')
24 files changed, 1906 insertions, 78 deletions
diff --git a/language-web/src/main/webapp/style.css b/language-web/src/main/css/index.scss index f3a6b7f9..e788bbd1 100644 --- a/language-web/src/main/webapp/style.css +++ b/language-web/src/main/css/index.scss | |||
@@ -1,3 +1,8 @@ | |||
1 | @import 'codemirror/lib/codemirror'; | ||
2 | @import 'codemirror/addon/hint/show-hint'; | ||
3 | |||
4 | @import 'xtext/xtext-codemirror'; | ||
5 | |||
1 | body { | 6 | body { |
2 | width: 100%; | 7 | width: 100%; |
3 | height: 100%; | 8 | height: 100%; |
@@ -37,8 +42,8 @@ a:hover { | |||
37 | } | 42 | } |
38 | 43 | ||
39 | .header h1 { | 44 | .header h1 { |
40 | font-size: 36px; | 45 | font-size: 36px; |
41 | margin: 0; | 46 | margin: 0; |
42 | } | 47 | } |
43 | 48 | ||
44 | .content { | 49 | .content { |
diff --git a/language-web/src/main/css/xtext/xtext-codemirror.css b/language-web/src/main/css/xtext/xtext-codemirror.css new file mode 100644 index 00000000..831b6daf --- /dev/null +++ b/language-web/src/main/css/xtext/xtext-codemirror.css | |||
@@ -0,0 +1,58 @@ | |||
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/html/index.html b/language-web/src/main/html/index.html new file mode 100644 index 00000000..915234d3 --- /dev/null +++ b/language-web/src/main/html/index.html | |||
@@ -0,0 +1,48 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html> | ||
3 | <head> | ||
4 | <meta charset="utf-8"> | ||
5 | <title>Example Web Editor</title> | ||
6 | </head> | ||
7 | <body> | ||
8 | <div class="container"> | ||
9 | <div class="header"> | ||
10 | <h1>Example Problem Web Editor</h1> | ||
11 | </div> | ||
12 | <div class="content"> | ||
13 | <div class="xtext-editor"><pre>class Family { | ||
14 | contains Person[] members | ||
15 | } | ||
16 | |||
17 | class Person { | ||
18 | Person[] children opposite parent | ||
19 | Person[0..1] parent opposite children | ||
20 | int age | ||
21 | TaxStatus taxStatus | ||
22 | } | ||
23 | |||
24 | enum TaxStatus { | ||
25 | child, student, adult, retired | ||
26 | } | ||
27 | |||
28 | % A child cannot have any dependents. | ||
29 | error invalidTaxStatus(Person p) :- | ||
30 | taxStatus(p, child), children(p, _q). | ||
31 | |||
32 | Family('family'). | ||
33 | members('family', anne). | ||
34 | members('family', bob). | ||
35 | members('family', ciri). | ||
36 | children(anne, ciri). | ||
37 | ?children(bob, ciri). | ||
38 | taxStatus(anne, adult). | ||
39 | age(anne, 35). | ||
40 | bobAge: 27. | ||
41 | age(bob, bobAge). | ||
42 | !age(ciri, bobAge). | ||
43 | |||
44 | scope Family = 1, Person += 5..10.</pre></div> | ||
45 | </div> | ||
46 | </div> | ||
47 | </body> | ||
48 | </html> | ||
diff --git a/language-web/src/main/images/error_an.gif b/language-web/src/main/images/error_an.gif new file mode 100644 index 00000000..e014ce90 --- /dev/null +++ b/language-web/src/main/images/error_an.gif | |||
Binary files differ | |||
diff --git a/language-web/src/main/images/info_an.gif b/language-web/src/main/images/info_an.gif new file mode 100644 index 00000000..d62ad9dd --- /dev/null +++ b/language-web/src/main/images/info_an.gif | |||
Binary files differ | |||
diff --git a/language-web/src/main/images/warning_an.gif b/language-web/src/main/images/warning_an.gif new file mode 100644 index 00000000..9ef66dd7 --- /dev/null +++ b/language-web/src/main/images/warning_an.gif | |||
Binary files differ | |||
diff --git a/language-web/src/main/webapp/xtext-resources/generated/mode-problem.js b/language-web/src/main/js-gen/mode-problem.js index d962a0bb..d962a0bb 100644 --- a/language-web/src/main/webapp/xtext-resources/generated/mode-problem.js +++ b/language-web/src/main/js-gen/mode-problem.js | |||
diff --git a/language-web/src/main/js/index.js b/language-web/src/main/js/index.js new file mode 100644 index 00000000..b4cd450e --- /dev/null +++ b/language-web/src/main/js/index.js | |||
@@ -0,0 +1,12 @@ | |||
1 | import xtext from 'xtext/xtext-codemirror'; | ||
2 | import 'mode-problem'; | ||
3 | |||
4 | import '../css/index.scss'; | ||
5 | |||
6 | xtext.createEditor({ | ||
7 | baseUrl: '/', | ||
8 | xtextLang: 'problem', | ||
9 | enableFormattingAction: true, | ||
10 | sendFullText: false, | ||
11 | indentUnit: 2, | ||
12 | }); | ||
diff --git a/language-web/src/main/js/xtext/CodeMirrorEditorContext.js b/language-web/src/main/js/xtext/CodeMirrorEditorContext.js new file mode 100644 index 00000000..b829c680 --- /dev/null +++ b/language-web/src/main/js/xtext/CodeMirrorEditorContext.js | |||
@@ -0,0 +1,111 @@ | |||
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/ServiceBuilder.js b/language-web/src/main/js/xtext/ServiceBuilder.js new file mode 100644 index 00000000..38b08ecc --- /dev/null +++ b/language-web/src/main/js/xtext/ServiceBuilder.js | |||
@@ -0,0 +1,274 @@ | |||
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 | ], function(jQuery, XtextService, LoadResourceService, SaveResourceService, HighlightingService, | ||
23 | ValidationService, UpdateService, ContentAssistService, HoverService, OccurrencesService, | ||
24 | FormattingService) { | ||
25 | |||
26 | /** | ||
27 | * Builder class for the Xtext services. | ||
28 | */ | ||
29 | function ServiceBuilder(xtextServices) { | ||
30 | this.services = xtextServices; | ||
31 | }; | ||
32 | |||
33 | /** | ||
34 | * Create all the available Xtext services depending on the configuration. | ||
35 | */ | ||
36 | ServiceBuilder.prototype.createServices = function() { | ||
37 | var services = this.services; | ||
38 | var options = services.options; | ||
39 | var editorContext = services.editorContext; | ||
40 | editorContext.xtextServices = services; | ||
41 | var self = this; | ||
42 | if (!options.serviceUrl) { | ||
43 | if (!options.baseUrl) | ||
44 | options.baseUrl = '/'; | ||
45 | else if (options.baseUrl.charAt(0) != '/') | ||
46 | options.baseUrl = '/' + options.baseUrl; | ||
47 | options.serviceUrl = window.location.protocol + '//' + window.location.host + options.baseUrl + 'xtext-service'; | ||
48 | } | ||
49 | if (options.resourceId) { | ||
50 | if (!options.xtextLang) | ||
51 | options.xtextLang = options.resourceId.split(/[?#]/)[0].split('.').pop(); | ||
52 | if (options.loadFromServer === undefined) | ||
53 | options.loadFromServer = true; | ||
54 | if (options.loadFromServer && this.setupPersistenceServices) { | ||
55 | services.loadResourceService = new LoadResourceService(options.serviceUrl, options.resourceId, false); | ||
56 | services.loadResource = function(addParams) { | ||
57 | return services.loadResourceService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options)); | ||
58 | } | ||
59 | services.saveResourceService = new SaveResourceService(options.serviceUrl, options.resourceId); | ||
60 | services.saveResource = function(addParams) { | ||
61 | return services.saveResourceService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options)); | ||
62 | } | ||
63 | services.revertResourceService = new LoadResourceService(options.serviceUrl, options.resourceId, true); | ||
64 | services.revertResource = function(addParams) { | ||
65 | return services.revertResourceService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options)); | ||
66 | } | ||
67 | this.setupPersistenceServices(); | ||
68 | services.loadResource(); | ||
69 | } | ||
70 | } else { | ||
71 | if (options.loadFromServer === undefined) | ||
72 | options.loadFromServer = false; | ||
73 | if (options.xtextLang) { | ||
74 | var randomId = Math.floor(Math.random() * 2147483648).toString(16); | ||
75 | options.resourceId = randomId + '.' + options.xtextLang; | ||
76 | } | ||
77 | } | ||
78 | |||
79 | if (this.setupSyntaxHighlighting) { | ||
80 | this.setupSyntaxHighlighting(); | ||
81 | } | ||
82 | if (options.enableHighlightingService || options.enableHighlightingService === undefined) { | ||
83 | services.highlightingService = new HighlightingService(options.serviceUrl, options.resourceId); | ||
84 | services.computeHighlighting = function(addParams) { | ||
85 | return services.highlightingService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options)); | ||
86 | } | ||
87 | } | ||
88 | if (options.enableValidationService || options.enableValidationService === undefined) { | ||
89 | services.validationService = new ValidationService(options.serviceUrl, options.resourceId); | ||
90 | services.validate = function(addParams) { | ||
91 | return services.validationService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options)); | ||
92 | } | ||
93 | } | ||
94 | if (this.setupUpdateService) { | ||
95 | function refreshDocument() { | ||
96 | if (services.highlightingService && self.doHighlighting) { | ||
97 | services.highlightingService.setState(undefined); | ||
98 | self.doHighlighting(); | ||
99 | } | ||
100 | if (services.validationService && self.doValidation) { | ||
101 | services.validationService.setState(undefined); | ||
102 | self.doValidation(); | ||
103 | } | ||
104 | } | ||
105 | if (!options.sendFullText) { | ||
106 | services.updateService = new UpdateService(options.serviceUrl, options.resourceId); | ||
107 | services.update = function(addParams) { | ||
108 | return services.updateService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options)); | ||
109 | } | ||
110 | if (services.saveResourceService) | ||
111 | services.saveResourceService._updateService = services.updateService; | ||
112 | editorContext.addServerStateListener(refreshDocument); | ||
113 | } | ||
114 | this.setupUpdateService(refreshDocument); | ||
115 | } | ||
116 | if ((options.enableContentAssistService || options.enableContentAssistService === undefined) | ||
117 | && this.setupContentAssistService) { | ||
118 | services.contentAssistService = new ContentAssistService(options.serviceUrl, options.resourceId, services.updateService); | ||
119 | services.getContentAssist = function(addParams) { | ||
120 | return services.contentAssistService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options)); | ||
121 | } | ||
122 | this.setupContentAssistService(); | ||
123 | } | ||
124 | if ((options.enableHoverService || options.enableHoverService === undefined) | ||
125 | && this.setupHoverService) { | ||
126 | services.hoverService = new HoverService(options.serviceUrl, options.resourceId, services.updateService); | ||
127 | services.getHoverInfo = function(addParams) { | ||
128 | return services.hoverService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options)); | ||
129 | } | ||
130 | this.setupHoverService(); | ||
131 | } | ||
132 | if ((options.enableOccurrencesService || options.enableOccurrencesService === undefined) | ||
133 | && this.setupOccurrencesService) { | ||
134 | services.occurrencesService = new OccurrencesService(options.serviceUrl, options.resourceId, services.updateService); | ||
135 | services.getOccurrences = function(addParams) { | ||
136 | return services.occurrencesService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options)); | ||
137 | } | ||
138 | this.setupOccurrencesService(); | ||
139 | } | ||
140 | if ((options.enableFormattingService || options.enableFormattingService === undefined) | ||
141 | && this.setupFormattingService) { | ||
142 | services.formattingService = new FormattingService(options.serviceUrl, options.resourceId, services.updateService); | ||
143 | services.format = function(addParams) { | ||
144 | return services.formattingService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options)); | ||
145 | } | ||
146 | this.setupFormattingService(); | ||
147 | } | ||
148 | if (options.enableGeneratorService || options.enableGeneratorService === undefined) { | ||
149 | services.generatorService = new XtextService(); | ||
150 | services.generatorService.initialize(services, 'generate'); | ||
151 | services.generatorService._initServerData = function(serverData, editorContext, params) { | ||
152 | if (params.allArtifacts) | ||
153 | serverData.allArtifacts = params.allArtifacts; | ||
154 | else if (params.artifactId) | ||
155 | serverData.artifact = params.artifactId; | ||
156 | if (params.includeContent !== undefined) | ||
157 | serverData.includeContent = params.includeContent; | ||
158 | } | ||
159 | services.generate = function(addParams) { | ||
160 | return services.generatorService.invoke(editorContext, ServiceBuilder.mergeOptions(addParams, options)); | ||
161 | } | ||
162 | } | ||
163 | |||
164 | if (options.dirtyElement) { | ||
165 | var doc = options.document || document; | ||
166 | var dirtyElement; | ||
167 | if (typeof(options.dirtyElement) === 'string') | ||
168 | dirtyElement = jQuery('#' + options.dirtyElement, doc); | ||
169 | else | ||
170 | dirtyElement = jQuery(options.dirtyElement); | ||
171 | var dirtyStatusClass = options.dirtyStatusClass; | ||
172 | if (!dirtyStatusClass) | ||
173 | dirtyStatusClass = 'dirty'; | ||
174 | editorContext.addDirtyStateListener(function(dirty) { | ||
175 | if (dirty) | ||
176 | dirtyElement.addClass(dirtyStatusClass); | ||
177 | else | ||
178 | dirtyElement.removeClass(dirtyStatusClass); | ||
179 | }); | ||
180 | } | ||
181 | |||
182 | services.successListeners = []; | ||
183 | services.errorListeners = [function(serviceType, severity, message, requestData) { | ||
184 | if (options.showErrorDialogs) | ||
185 | window.alert('Xtext service \'' + serviceType + '\' failed: ' + message); | ||
186 | else | ||
187 | console.log('Xtext service \'' + serviceType + '\' failed: ' + message); | ||
188 | }]; | ||
189 | } | ||
190 | |||
191 | /** | ||
192 | * Change the resource associated with this service builder. | ||
193 | */ | ||
194 | ServiceBuilder.prototype.changeResource = function(resourceId) { | ||
195 | var services = this.services; | ||
196 | var options = services.options; | ||
197 | options.resourceId = resourceId; | ||
198 | for (var p in services) { | ||
199 | if (services.hasOwnProperty(p)) { | ||
200 | var service = services[p]; | ||
201 | if (service._serviceType && jQuery.isFunction(service.initialize)) | ||
202 | services[p].initialize(options.serviceUrl, service._serviceType, resourceId, services.updateService); | ||
203 | } | ||
204 | } | ||
205 | var knownServerState = services.editorContext.getServerState(); | ||
206 | delete knownServerState.stateId; | ||
207 | delete knownServerState.text; | ||
208 | if (options.loadFromServer && jQuery.isFunction(services.loadResource)) { | ||
209 | services.loadResource(); | ||
210 | } | ||
211 | } | ||
212 | |||
213 | /** | ||
214 | * Create a copy of the given object. | ||
215 | */ | ||
216 | ServiceBuilder.copy = function(obj) { | ||
217 | var copy = {}; | ||
218 | for (var p in obj) { | ||
219 | if (obj.hasOwnProperty(p)) | ||
220 | copy[p] = obj[p]; | ||
221 | } | ||
222 | return copy; | ||
223 | } | ||
224 | |||
225 | /** | ||
226 | * Translate an HTML attribute name to a JS option name. | ||
227 | */ | ||
228 | ServiceBuilder.optionName = function(name) { | ||
229 | var prefix = 'data-editor-'; | ||
230 | if (name.substring(0, prefix.length) === prefix) { | ||
231 | var key = name.substring(prefix.length); | ||
232 | key = key.replace(/-([a-z])/ig, function(all, character) { | ||
233 | return character.toUpperCase(); | ||
234 | }); | ||
235 | return key; | ||
236 | } | ||
237 | return undefined; | ||
238 | } | ||
239 | |||
240 | /** | ||
241 | * Copy all default options into the given set of additional options. | ||
242 | */ | ||
243 | ServiceBuilder.mergeOptions = function(options, defaultOptions) { | ||
244 | if (options) { | ||
245 | for (var p in defaultOptions) { | ||
246 | if (defaultOptions.hasOwnProperty(p)) | ||
247 | options[p] = defaultOptions[p]; | ||
248 | } | ||
249 | return options; | ||
250 | } else { | ||
251 | return ServiceBuilder.copy(defaultOptions); | ||
252 | } | ||
253 | } | ||
254 | |||
255 | /** | ||
256 | * Merge all properties of the given parent element with the given default options. | ||
257 | */ | ||
258 | ServiceBuilder.mergeParentOptions = function(parent, defaultOptions) { | ||
259 | var options = ServiceBuilder.copy(defaultOptions); | ||
260 | for (var attr, j = 0, attrs = parent.attributes, l = attrs.length; j < l; j++) { | ||
261 | attr = attrs.item(j); | ||
262 | var key = ServiceBuilder.optionName(attr.nodeName); | ||
263 | if (key) { | ||
264 | var value = attr.nodeValue; | ||
265 | if (value === 'true' || value === 'false') | ||
266 | value = value === 'true'; | ||
267 | options[key] = value; | ||
268 | } | ||
269 | } | ||
270 | return options; | ||
271 | } | ||
272 | |||
273 | return ServiceBuilder; | ||
274 | }); \ No newline at end of file | ||
diff --git a/language-web/src/main/js/xtext/compatibility.js b/language-web/src/main/js/xtext/compatibility.js new file mode 100644 index 00000000..c877fc56 --- /dev/null +++ b/language-web/src/main/js/xtext/compatibility.js | |||
@@ -0,0 +1,63 @@ | |||
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 new file mode 100644 index 00000000..1686570d --- /dev/null +++ b/language-web/src/main/js/xtext/services/ContentAssistService.js | |||
@@ -0,0 +1,132 @@ | |||
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 new file mode 100644 index 00000000..f59099ee --- /dev/null +++ b/language-web/src/main/js/xtext/services/FormattingService.js | |||
@@ -0,0 +1,52 @@ | |||
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 new file mode 100644 index 00000000..5a5ac8ba --- /dev/null +++ b/language-web/src/main/js/xtext/services/HighlightingService.js | |||
@@ -0,0 +1,33 @@ | |||
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 new file mode 100644 index 00000000..03c5a52b --- /dev/null +++ b/language-web/src/main/js/xtext/services/HoverService.js | |||
@@ -0,0 +1,59 @@ | |||
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 new file mode 100644 index 00000000..b5a315c3 --- /dev/null +++ b/language-web/src/main/js/xtext/services/LoadResourceService.js | |||
@@ -0,0 +1,42 @@ | |||
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 new file mode 100644 index 00000000..2e2d0b1a --- /dev/null +++ b/language-web/src/main/js/xtext/services/OccurrencesService.js | |||
@@ -0,0 +1,39 @@ | |||
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 new file mode 100644 index 00000000..66cdaff5 --- /dev/null +++ b/language-web/src/main/js/xtext/services/SaveResourceService.js | |||
@@ -0,0 +1,32 @@ | |||
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 new file mode 100644 index 00000000..b78d846d --- /dev/null +++ b/language-web/src/main/js/xtext/services/UpdateService.js | |||
@@ -0,0 +1,159 @@ | |||
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 new file mode 100644 index 00000000..85c9953d --- /dev/null +++ b/language-web/src/main/js/xtext/services/ValidationService.js | |||
@@ -0,0 +1,33 @@ | |||
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 new file mode 100644 index 00000000..d3a4842f --- /dev/null +++ b/language-web/src/main/js/xtext/services/XtextService.js | |||
@@ -0,0 +1,280 @@ | |||
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.js b/language-web/src/main/js/xtext/xtext-codemirror.js new file mode 100644 index 00000000..4d50718c --- /dev/null +++ b/language-web/src/main/js/xtext/xtext-codemirror.js | |||
@@ -0,0 +1,472 @@ | |||
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 | to: cursor | ||
283 | }; | ||
284 | }}); | ||
285 | }); | ||
286 | }}; | ||
287 | this.editor.addKeyMap(services.contentAssistKeyMap); | ||
288 | } | ||
289 | |||
290 | /** | ||
291 | * Semantic highlighting service. | ||
292 | */ | ||
293 | CodeMirrorServiceBuilder.prototype.doHighlighting = function() { | ||
294 | var services = this.services; | ||
295 | var editorContext = services.editorContext; | ||
296 | var editor = this.editor; | ||
297 | services.computeHighlighting().always(function() { | ||
298 | var highlightingMarkers = editorContext._highlightingMarkers; | ||
299 | if (highlightingMarkers) { | ||
300 | for (var i = 0; i < highlightingMarkers.length; i++) { | ||
301 | highlightingMarkers[i].clear(); | ||
302 | } | ||
303 | } | ||
304 | editorContext._highlightingMarkers = []; | ||
305 | }).done(function(result) { | ||
306 | for (var i = 0; i < result.regions.length; ++i) { | ||
307 | var region = result.regions[i]; | ||
308 | var from = editor.posFromIndex(region.offset); | ||
309 | var to = editor.posFromIndex(region.offset + region.length); | ||
310 | region.styleClasses.forEach(function(styleClass) { | ||
311 | var marker = editor.markText(from, to, {className: styleClass}); | ||
312 | editorContext._highlightingMarkers.push(marker); | ||
313 | }); | ||
314 | } | ||
315 | }); | ||
316 | } | ||
317 | |||
318 | var annotationWeight = { | ||
319 | error: 30, | ||
320 | warning: 20, | ||
321 | info: 10 | ||
322 | }; | ||
323 | CodeMirrorServiceBuilder.prototype._getAnnotationWeight = function(annotation) { | ||
324 | if (annotationWeight[annotation] !== undefined) | ||
325 | return annotationWeight[annotation]; | ||
326 | else | ||
327 | return 0; | ||
328 | } | ||
329 | |||
330 | CodeMirrorServiceBuilder.prototype._clearAnnotations = function(annotations) { | ||
331 | var editor = this.editor; | ||
332 | for (var i = 0; i < annotations.length; i++) { | ||
333 | var annotation = annotations[i]; | ||
334 | if (annotation) { | ||
335 | editor.setGutterMarker(i, 'annotations-gutter', null); | ||
336 | annotations[i] = undefined; | ||
337 | } | ||
338 | } | ||
339 | } | ||
340 | |||
341 | CodeMirrorServiceBuilder.prototype._refreshAnnotations = function(annotations) { | ||
342 | var editor = this.editor; | ||
343 | for (var i = 0; i < annotations.length; i++) { | ||
344 | var annotation = annotations[i]; | ||
345 | if (annotation) { | ||
346 | var classProp = ' class="xtext-annotation_' + annotation.type + '"'; | ||
347 | var titleProp = annotation.description ? ' title="' + annotation.description.replace(/"/g, '"') + '"' : ''; | ||
348 | var element = jQuery('<div' + classProp + titleProp + '></div>').get(0); | ||
349 | editor.setGutterMarker(i, 'annotations-gutter', element); | ||
350 | } | ||
351 | } | ||
352 | } | ||
353 | |||
354 | /** | ||
355 | * Validation service. | ||
356 | */ | ||
357 | CodeMirrorServiceBuilder.prototype.doValidation = function() { | ||
358 | var services = this.services; | ||
359 | var editorContext = services.editorContext; | ||
360 | var editor = this.editor; | ||
361 | var self = this; | ||
362 | services.validate().always(function() { | ||
363 | if (editorContext._validationAnnotations) | ||
364 | self._clearAnnotations(editorContext._validationAnnotations); | ||
365 | else | ||
366 | editorContext._validationAnnotations = []; | ||
367 | var validationMarkers = editorContext._validationMarkers; | ||
368 | if (validationMarkers) { | ||
369 | for (var i = 0; i < validationMarkers.length; i++) { | ||
370 | validationMarkers[i].clear(); | ||
371 | } | ||
372 | } | ||
373 | editorContext._validationMarkers = []; | ||
374 | }).done(function(result) { | ||
375 | var validationAnnotations = editorContext._validationAnnotations; | ||
376 | for (var i = 0; i < result.issues.length; i++) { | ||
377 | var entry = result.issues[i]; | ||
378 | var annotation = validationAnnotations[entry.line - 1]; | ||
379 | var weight = self._getAnnotationWeight(entry.severity); | ||
380 | if (annotation) { | ||
381 | if (annotation.weight < weight) { | ||
382 | annotation.type = entry.severity; | ||
383 | annotation.weight = weight; | ||
384 | } | ||
385 | if (annotation.description) | ||
386 | annotation.description += '\n' + entry.description; | ||
387 | else | ||
388 | annotation.description = entry.description; | ||
389 | } else { | ||
390 | validationAnnotations[entry.line - 1] = { | ||
391 | type: entry.severity, | ||
392 | weight: weight, | ||
393 | description: entry.description | ||
394 | }; | ||
395 | } | ||
396 | var from = editor.posFromIndex(entry.offset); | ||
397 | var to = editor.posFromIndex(entry.offset + entry.length); | ||
398 | var marker = editor.markText(from, to, { | ||
399 | className: 'xtext-marker_' + entry.severity, | ||
400 | title: entry.description | ||
401 | }); | ||
402 | editorContext._validationMarkers.push(marker); | ||
403 | } | ||
404 | self._refreshAnnotations(validationAnnotations); | ||
405 | }); | ||
406 | } | ||
407 | |||
408 | /** | ||
409 | * Occurrences service. | ||
410 | */ | ||
411 | CodeMirrorServiceBuilder.prototype.setupOccurrencesService = function() { | ||
412 | var services = this.services; | ||
413 | var editorContext = services.editorContext; | ||
414 | var selectionUpdateDelay = services.options.selectionUpdateDelay; | ||
415 | if (!selectionUpdateDelay) | ||
416 | selectionUpdateDelay = 550; | ||
417 | var editor = this.editor; | ||
418 | var self = this; | ||
419 | services.cursorActivityListener = function() { | ||
420 | if (editorContext._selectionChangeTimeout) { | ||
421 | clearTimeout(editorContext._selectionChangeTimeout); | ||
422 | } | ||
423 | editorContext._selectionChangeTimeout = setTimeout(function() { | ||
424 | var params = ServiceBuilder.copy(services.options); | ||
425 | var cursor = editor.getCursor(); | ||
426 | params.offset = editor.indexFromPos(cursor); | ||
427 | services.occurrencesService.invoke(editorContext, params).always(function() { | ||
428 | var occurrenceMarkers = editorContext._occurrenceMarkers; | ||
429 | if (occurrenceMarkers) { | ||
430 | for (var i = 0; i < occurrenceMarkers.length; i++) { | ||
431 | occurrenceMarkers[i].clear(); | ||
432 | } | ||
433 | } | ||
434 | editorContext._occurrenceMarkers = []; | ||
435 | }).done(function(occurrencesResult) { | ||
436 | for (var i = 0; i < occurrencesResult.readRegions.length; i++) { | ||
437 | var region = occurrencesResult.readRegions[i]; | ||
438 | var from = editor.posFromIndex(region.offset); | ||
439 | var to = editor.posFromIndex(region.offset + region.length); | ||
440 | var marker = editor.markText(from, to, {className: 'xtext-marker_read'}); | ||
441 | editorContext._occurrenceMarkers.push(marker); | ||
442 | } | ||
443 | for (var i = 0; i < occurrencesResult.writeRegions.length; i++) { | ||
444 | var region = occurrencesResult.writeRegions[i]; | ||
445 | var from = editor.posFromIndex(region.offset); | ||
446 | var to = editor.posFromIndex(region.offset + region.length); | ||
447 | var marker = editor.markText(from, to, {className: 'xtext-marker_write'}); | ||
448 | editorContext._occurrenceMarkers.push(marker); | ||
449 | } | ||
450 | }); | ||
451 | }, selectionUpdateDelay); | ||
452 | } | ||
453 | editor.on('cursorActivity', services.cursorActivityListener); | ||
454 | } | ||
455 | |||
456 | /** | ||
457 | * Formatting service. | ||
458 | */ | ||
459 | CodeMirrorServiceBuilder.prototype.setupFormattingService = function() { | ||
460 | var services = this.services; | ||
461 | if (services.options.enableFormattingAction) { | ||
462 | var userAgent = navigator.userAgent.toLowerCase(); | ||
463 | var formatFunction = function(editor) { | ||
464 | services.format(); | ||
465 | }; | ||
466 | services.formatKeyMap = /mac os/.test(userAgent) ? {'Shift-Cmd-F': formatFunction}: {'Shift-Ctrl-S': formatFunction}; | ||
467 | this.editor.addKeyMap(services.formatKeyMap); | ||
468 | } | ||
469 | } | ||
470 | |||
471 | return exports; | ||
472 | }); | ||
diff --git a/language-web/src/main/webapp/index.html b/language-web/src/main/webapp/index.html deleted file mode 100644 index 3e068f68..00000000 --- a/language-web/src/main/webapp/index.html +++ /dev/null | |||
@@ -1,56 +0,0 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | ||
3 | <meta http-equiv="Content-Language" content="en-us"> | ||
4 | <title>Example Web Editor</title> | ||
5 | <link rel="stylesheet" type="text/css" href="webjars/codemirror/5.52.2/lib/codemirror.css" /> | ||
6 | <link rel="stylesheet" type="text/css" href="webjars/codemirror/5.52.2/addon/hint/show-hint.css" /> | ||
7 | <link rel="stylesheet" type="text/css" href="xtext/2.26.0.M1/xtext-codemirror.css" /> | ||
8 | <link rel="stylesheet" type="text/css" href="style.css" /> | ||
9 | <body> | ||
10 | <div class="container"> | ||
11 | <div class="header"> | ||
12 | <h1>Example Problem Web Editor</h1> | ||
13 | </div> | ||
14 | <div class="content"> | ||
15 | <div class="xtext-editor" | ||
16 | data-editor-xtext-lang="problem" | ||
17 | data-editor-enable-formatting-action="true" | ||
18 | data-editor-send-full-text="false" | ||
19 | ><pre>class Family { | ||
20 | contains Person[] members | ||
21 | } | ||
22 | |||
23 | class Person { | ||
24 | Person[] children opposite parent | ||
25 | Person[0..1] parent opposite children | ||
26 | int age | ||
27 | TaxStatus taxStatus | ||
28 | } | ||
29 | |||
30 | enum TaxStatus { | ||
31 | child, student, adult, retired | ||
32 | } | ||
33 | |||
34 | % A child cannot have any dependents. | ||
35 | error invalidTaxStatus(Person p) :- | ||
36 | taxStatus(p, child), children(p, _q). | ||
37 | |||
38 | Family('family'). | ||
39 | members('family', anne). | ||
40 | members('family', bob). | ||
41 | members('family', ciri). | ||
42 | children(anne, ciri). | ||
43 | ?children(bob, ciri). | ||
44 | taxStatus(anne, adult). | ||
45 | age(anne, 35). | ||
46 | bobAge: 27. | ||
47 | age(bob, bobAge). | ||
48 | !age(ciri, bobAge). | ||
49 | |||
50 | scope Family = 1, Person += 5..10. | ||
51 | </pre></div> | ||
52 | </div> | ||
53 | </div> | ||
54 | <script type="text/javascript" src="webjars/requirejs/2.3.6/require.min.js"></script> | ||
55 | <script type="text/javascript" src="script.js"></script> | ||
56 | </body> | ||
diff --git a/language-web/src/main/webapp/script.js b/language-web/src/main/webapp/script.js deleted file mode 100644 index 4b466fed..00000000 --- a/language-web/src/main/webapp/script.js +++ /dev/null | |||
@@ -1,20 +0,0 @@ | |||
1 | var baseUrl = window.location.pathname; | ||
2 | var fileIndex = baseUrl.indexOf("index.html"); | ||
3 | if (fileIndex > 0) | ||
4 | baseUrl = baseUrl.slice(0, fileIndex) | ||
5 | require.config({ | ||
6 | baseUrl: baseUrl, | ||
7 | paths: { | ||
8 | "text": "webjars/requirejs-text/2.26.0.M1/text", | ||
9 | "jquery": "webjars/jquery/3.6.0/jquery.min", | ||
10 | "xtext/xtext-codemirror": "xtext/2.26.0.M1/xtext-codemirror", | ||
11 | }, | ||
12 | packages: [{ | ||
13 | name: "codemirror", | ||
14 | location: "webjars/codemirror/5.52.2", | ||
15 | main: "lib/codemirror" | ||
16 | }] | ||
17 | }); | ||
18 | require(["xtext-resources/generated/mode-problem", "xtext/xtext-codemirror"], function(mode, xtext) { | ||
19 | xtext.createEditor({baseUrl: baseUrl, indentUnit: 2,}); | ||
20 | }); | ||