/******************************************************************************* * Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. * * SPDX-License-Identifier: EPL-2.0 *******************************************************************************/ define(['xtext/services/XtextService', 'jquery'], function(XtextService, jQuery) { /** * Service class for updating the server-side representation of a resource. * This service only makes sense with a stateful server, where an update request is sent * after each modification. This can greatly improve response times compared to the * stateless alternative, where the full text content is sent with each service request. */ function UpdateService(serviceUrl, resourceId) { this.initialize(serviceUrl, 'update', resourceId, this); this._completionCallbacks = []; }; UpdateService.prototype = new XtextService(); /** * Compute a delta between two versions of a text. If a difference is found, the result * contains three properties: * deltaText - the text to insert into s1 * deltaOffset - the text insertion offset * deltaReplaceLength - the number of characters that shall be replaced by the inserted text */ UpdateService.prototype.computeDelta = function(s1, s2, result) { var start = 0, s1length = s1.length, s2length = s2.length; while (start < s1length && start < s2length && s1.charCodeAt(start) === s2.charCodeAt(start)) { start++; } if (start === s1length && start === s2length) { return; } result.deltaOffset = start; if (start === s1length) { result.deltaText = s2.substring(start, s2length); result.deltaReplaceLength = 0; return; } else if (start === s2length) { result.deltaText = ''; result.deltaReplaceLength = s1length - start; return; } var end1 = s1length - 1, end2 = s2length - 1; while (end1 >= start && end2 >= start && s1.charCodeAt(end1) === s2.charCodeAt(end2)) { end1--; end2--; } result.deltaText = s2.substring(start, end2 + 1); result.deltaReplaceLength = end1 - start + 1; }; /** * Invoke all completion callbacks and clear the list afterwards. */ UpdateService.prototype.onComplete = function(xhr, textStatus) { var callbacks = this._completionCallbacks; this._completionCallbacks = []; for (var i = 0; i < callbacks.length; i++) { var callback = callbacks[i].callback; var params = callbacks[i].params; callback(params); } } /** * Add a callback to be invoked when the service call has completed. */ UpdateService.prototype.addCompletionCallback = function(callback, params) { this._completionCallbacks.push({callback: callback, params: params}); } UpdateService.prototype.invoke = function(editorContext, params, deferred) { if (deferred === undefined) { deferred = jQuery.Deferred(); } var knownServerState = editorContext.getServerState(); if (knownServerState.updateInProgress) { var self = this; this.addCompletionCallback(function() { self.invoke(editorContext, params, deferred) }); return deferred.promise(); } var serverData = { contentType: params.contentType }; var currentText = editorContext.getText(); if (params.sendFullText || knownServerState.text === undefined) { serverData.fullText = currentText; } else { this.computeDelta(knownServerState.text, currentText, serverData); if (serverData.deltaText === undefined) { if (params.forceUpdate) { serverData.deltaText = ''; serverData.deltaOffset = editorContext.getCaretOffset(); serverData.deltaReplaceLength = 0; } else { deferred.resolve(knownServerState); this.onComplete(); return deferred.promise(); } } serverData.requiredStateId = knownServerState.stateId; } knownServerState.updateInProgress = true; var self = this; self.sendRequest(editorContext, { type: 'PUT', data: serverData, success: function(result) { if (result.conflict) { // The server has lost its session state and the resource is loaded from the server if (knownServerState.text !== undefined) { delete knownServerState.updateInProgress; delete knownServerState.text; delete knownServerState.stateId; self.invoke(editorContext, params, deferred); } else { deferred.reject(result.conflict); } return false; } var listeners = editorContext.updateServerState(currentText, result.stateId); for (var i = 0; i < listeners.length; i++) { self.addCompletionCallback(listeners[i], params); } deferred.resolve(result); }, error: function(xhr, textStatus, errorThrown) { if (xhr.status == 404 && !params.loadFromServer && knownServerState.text !== undefined) { // The server has lost its session state and the resource is not loaded from the server delete knownServerState.updateInProgress; delete knownServerState.text; delete knownServerState.stateId; self.invoke(editorContext, params, deferred); return true; } deferred.reject(errorThrown); }, complete: self.onComplete.bind(self) }, true); return deferred.promise().always(function() { knownServerState.updateInProgress = false; }); }; return UpdateService; });