Index: Source/devtools/front_end/languages/LanguageService.js |
diff --git a/Source/devtools/front_end/languages/LanguageService.js b/Source/devtools/front_end/languages/LanguageService.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6f63439d31ff2be682a16c627747dfca6f94011e |
--- /dev/null |
+++ b/Source/devtools/front_end/languages/LanguageService.js |
@@ -0,0 +1,373 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+/** |
+ * @interface |
+ */ |
+WebInspector.LanguageService = function(){}; |
+WebInspector.LanguageService.prototype = { |
+ /** |
+ * @return {string} |
+ */ |
+ name: function() { throw new Error("Not implemented"); }, |
+ |
+ /** |
+ * @return {!Array<string>} |
+ */ |
+ capabilities: function() { return []; } |
+} |
+ |
+/** |
+ * The language service capabilities list determines what actions are added |
+ * to the LanguageService and LanguageService.Handles objects, in addition |
+ * to what methods are searched for on providers to fulfill those capabilities |
+ */ |
+WebInspector.LanguageService.Capabilities = { |
+ Transpile: "transpile", |
+ PopulateContextMenu: "populateContextMenu", |
+ Completions: "completions", |
+ DebuggerCompletions: "debuggerCompletions", |
+}; |
+ |
+/** |
+ * @constructor |
+ * @extends {WebInspector.Object} |
+ */ |
+WebInspector.LanguageServiceManager = function() { |
+ this._override = {}; |
+ this._servicesByMime = {}; |
+ |
+ for (var key in WebInspector.LanguageService.Capabilities) { |
+ var actionName = WebInspector.LanguageService.Capabilities[key]; |
+ this._servicesByMime[Symbol.for(key)] = {}; |
+ WebInspector.LanguageService.prototype[actionName] = function() {}; |
+ } |
+ |
+ var _this = this; |
+ function makeHandlesChecker(key) { |
+ var actionName = WebInspector.LanguageService.Capabilities[key]; |
+ return function(mime) { |
+ var override = _this._override[actionName]; |
+ if (typeof override !== "undefined") { |
+ return override; |
+ } |
+ var service = _this._servicesByMime[Symbol.for(key)][mime]; |
+ return service; |
+ }; |
+ } |
+ |
+ function makeCapabilityAction(key) { |
+ var actionName = WebInspector.LanguageService.Capabilities[key]; |
+ return function(mime) { |
+ var override = _this._override[actionName]; |
+ if (typeof override !== "undefined") { |
+ if (typeof override === "boolean") { |
+ if (override) { |
+ return Promise.resolve(); |
+ } else { |
+ return Promise.reject("No language service present to handle \""+(key)+"\" for mime \""+(mime)+"\"."); |
+ } |
+ } |
+ return override[actionName].apply(override, Array.prototype.slice.call(arguments, 1)); |
+ } |
+ if (!_this.handles[actionName](mime)) { |
+ return Promise.reject("No language service present to handle \""+(key)+"\" for mime \""+(mime)+"\"."); |
+ } |
+ var service = _this._servicesByMime[Symbol.for(key)][mime]; |
+ return service[actionName].apply(service, Array.prototype.slice.call(arguments, 1)); |
+ }; |
+ } |
+ |
+ for (var key in WebInspector.LanguageService.Capabilities) { |
+ _this.handles[WebInspector.LanguageService.Capabilities[key]] = makeHandlesChecker(key); |
+ _this[WebInspector.LanguageService.Capabilities[key]] = makeCapabilityAction(key); |
+ } |
+}; |
+ |
+WebInspector.LanguageServiceManager.Events = { |
+ ServiceAdded: "onServiceAdded", |
+ InferredMimeUpdated: "onInferredMimeUpdated" |
+}; |
+ |
+/** |
+ * Stub external API here to satisfy closure compiler - actual implementations |
+ * are done in the ctor above. Not sure if there's a better way to type this, while |
+ * still making it simple to 'declare' a new optional member to a service |
+ */ |
+WebInspector.LanguageServiceManager.prototype = { |
+ |
+ handles: { |
+ /** |
+ * @param {string} mime |
+ * @return {?WebInspector.LanguageService} |
+ */ |
+ transpile: function(mime) {return null;}, |
+ |
+ /** |
+ * @param {string} mime |
+ * @return {?WebInspector.LanguageService} |
+ */ |
+ populateContextMenu: function(mime) {return null;}, |
+ |
+ /** |
+ * @param {string} mime |
+ * @return {?WebInspector.LanguageService} |
+ */ |
+ completions: function(mime) {return null;}, |
+ |
+ /** |
+ * @param {string} mime |
+ * @return {?WebInspector.LanguageService} |
+ */ |
+ debuggerCompletions: function(mime) {return null;} |
+ }, |
+ |
+ /** |
+ * @param {!Array<string>} mimes |
+ * @param {!WebInspector.LanguageService} service |
+ */ |
+ register: function(mimes, service) { |
+ for (var index = 0; index < mimes.length; index++) { |
+ var mime = mimes[index]; |
+ for (var key in WebInspector.LanguageService.Capabilities) { |
+ var capability = WebInspector.LanguageService.Capabilities[key]; |
+ if (service.capabilities().indexOf(capability) !== -1) { |
+ this._servicesByMime[Symbol.for(key)][mime] = service; |
+ } |
+ } |
+ |
+ this._servicesByMime[mime] = service; |
+ } |
+ this._onServiceAdded(service, mimes); |
+ }, |
+ |
+ _onServiceAdded(service, mimes) { |
+ this.dispatchEventToListeners(WebInspector.LanguageServiceManager.Events.ServiceAdded, {service, mimes}); |
+ }, |
+ |
+ /** |
+ * @param {string} capability |
+ * @param {!WebInspector.LanguageService=} override |
+ */ |
+ disableContextualHandling: function(capability, override) { |
+ this._override[capability] = override || false; |
+ }, |
+ |
+ /** |
+ * @param {string} capability |
+ */ |
+ enableContextualHandling: function(capability) { |
+ delete this._override[capability]; |
+ }, |
+ |
+ /** |
+ * @param {string=} capability |
+ * @return {!Array<!WebInspector.LanguageService>} |
+ */ |
+ registered: function(capability) { |
+ return [...new Set(Object.keys(capability ? this._servicesByMime[Symbol.for(capability)]||{} : this._servicesByMime).map((function(k){ return capability ? this._servicesByMime[Symbol.for(capability)][k] : this._servicesByMime[k]; }).bind(this)))]; |
+ }, |
+ |
+ /** |
+ * @param {string} mime |
+ * @return {?WebInspector.LanguageService} |
+ */ |
+ for: function(mime) { |
+ return this._servicesByMime[mime]; |
+ }, |
+ |
+ /** |
+ * @param {string} mime |
+ * @param {string} input |
+ * @param {?{source: string, line: number, column: number}=} location |
+ * @return {!Promise<string>} |
+ */ |
+ transpile: function(mime, input, location) {return Promise.reject();}, |
+ |
+ /** |
+ * @param {string} mime |
+ * @param {{source: string, line: number, column: number}} location |
+ * @return {!Promise<!Array<{text: string, callback: !Function}>>} |
+ */ |
+ populateContextMenu: function(mime, location) {return Promise.reject();}, |
+ |
+ /** |
+ * @param {string} mime |
+ * @param {{source: string, line: number, column: number}} location |
+ * @param {string} prefix |
+ * @return {!Promise<!Array<{text: string, details: !Promise<{detail: string, description: string}>}>>} |
+ */ |
+ completions: function(mime, location, prefix) {return Promise.reject();}, |
+ |
+ /** |
+ * @param {string} mime |
+ * @param {string} content |
+ * @param {number} cursorOffset |
+ * @param {string} prefix |
+ * @param {{source: string, line: number, column: number}=} context |
+ * @return {!Promise<!Array<{text: string, details: !Promise<{detail: string, description: string}>}>>} |
+ */ |
+ debuggerCompletions: function(mime, content, cursorOffset, prefix, context) {return Promise.reject();}, |
+ |
+ updateInferredMime: function() { |
+ this.dispatchEventToListeners(WebInspector.LanguageServiceManager.Events.InferredMimeUpdated); |
+ }, |
+ |
+ __proto__: WebInspector.Object.prototype |
+}; |
+ |
+/** |
+ * @constructor |
+ * @implements {WebInspector.LanguageService} |
+ */ |
+WebInspector.LanguageService.CallbackDelegate = function(name, capabilities) { |
+ this._name = name; |
+ this._capabilities = []; |
+ |
+ for (var key in WebInspector.LanguageService.Capabilities) { |
+ var actionName = WebInspector.LanguageService.Capabilities[key]; |
+ if (!!capabilities[actionName]) { |
+ this.appendActionHandler(actionName, capabilities[actionName]); |
+ } |
+ } |
+}; |
+ |
+WebInspector.LanguageService.CallbackDelegate.prototype = { |
+ /** |
+ * @override |
+ * @return {string} |
+ */ |
+ name: function() { |
+ return this._name; |
+ }, |
+ |
+ /** |
+ * Given an action and a function expecting a callback for |
+ * its final argument, modifies it to return a promise and |
+ * appends it to the language service |
+ * |
+ * @param {string} capability |
+ * @param {!Function} func |
+ */ |
+ appendActionHandler: function(capability, func) { |
+ var _this = this; |
+ this._capabilities.push(capability); |
+ this[capability] = function() { |
+ var args = arguments; |
+ return new Promise(function(resolve, reject) { |
+ func.call(_this, ...args, resolve); |
+ }); |
+ }; |
+ }, |
+ |
+ /** |
+ * @override |
+ * @return {!Array<string>} |
+ */ |
+ capabilities: function() { |
+ return this._capabilities; |
+ } |
+}; |
+ |
+/** |
+ * @constructor |
+ * @param {!Element} selectElement |
+ * @param {!Array<string>} capabilities |
+ */ |
+WebInspector.LanguageServiceModel = function(selectElement, capabilities) { |
+ this._capabilities = capabilities; |
+ this._selectElement = selectElement; |
+ var newOption = createElement("option"); |
+ newOption.__type = WebInspector.LanguageServiceModel.OptionType.Inferred; |
+ newOption.text = WebInspector.UIString("%s (inferred)", WebInspector.UIString("javascript")); |
+ this._selectElement.appendChild(newOption); |
+ |
+ var jsOption = createElement("option"); |
+ jsOption.__type = WebInspector.LanguageServiceModel.OptionType.Builtin; |
+ jsOption.text = WebInspector.UIString("javascript"); |
+ this._selectElement.appendChild(jsOption); |
+ |
+ WebInspector.languageService.registered().forEach((function(service) { |
+ this.addOption(service); |
+ }).bind(this)); |
+ |
+ WebInspector.languageService.addEventListener(WebInspector.LanguageServiceManager.Events.ServiceAdded, this._onServiceAdded, this); |
+ WebInspector.languageService.addEventListener(WebInspector.LanguageServiceManager.Events.InferredMimeUpdated, this._onViewSelected, this); |
+ this._selectElement.addEventListener("change", this._languageServiceChanged.bind(this), false); |
+}; |
+ |
+WebInspector.LanguageServiceModel.prototype = { |
+ /** |
+ * @param {!WebInspector.LanguageService} service |
+ */ |
+ addOption: function(service) { |
+ var newOption = createElement("option"); |
+ newOption.__service = service; |
+ newOption.__type = WebInspector.LanguageServiceModel.OptionType.External; |
+ newOption.text = service.name(); |
+ this._selectElement.appendChild(newOption); |
+ }, |
+ |
+ _onServiceAdded: function(evt) { |
+ var service = /** @type {!WebInspector.LanguageService} */(evt.data.service); |
+ this.addOption(service); |
+ }, |
+ |
+ _onViewSelected: function(evt) { |
+ var mime = WebInspector.ResourceType.fromActivePanel(); |
+ var service = WebInspector.languageService.for(mime); |
+ var name = WebInspector.UIString("javascript"); |
+ if (service) { |
+ name = service.name(); |
+ } |
+ var opts = this._selectElement.options; |
+ for (var i = 0; i < opts.length; i++) { |
+ var option = opts[i]; |
+ if (option.__type === WebInspector.LanguageServiceModel.OptionType.Inferred) { |
+ option.text = WebInspector.UIString("%s (inferred)", name); |
+ } |
+ } |
+ }, |
+ |
+ _languageServiceChanged: function() { |
+ var option = this._selectedOption(); |
+ switch(option.__type) { |
+ case WebInspector.LanguageServiceModel.OptionType.Inferred: |
+ this._capabilities.forEach(function(c) { |
+ WebInspector.languageService.enableContextualHandling(c); |
+ }); |
+ break; |
+ case WebInspector.LanguageServiceModel.OptionType.Builtin: |
+ this._capabilities.forEach(function(c) { |
+ WebInspector.languageService.disableContextualHandling(c); |
+ }); |
+ break; |
+ case WebInspector.LanguageServiceModel.OptionType.External: |
+ this._capabilities.forEach(function(c) { |
+ WebInspector.languageService.disableContextualHandling(c, option.__service); |
+ }); |
+ break; |
+ default: |
+ break; |
+ } |
+ }, |
+ |
+ /** |
+ * @return {?Element} |
+ */ |
+ _selectedOption: function() |
+ { |
+ if (this._selectElement.selectedIndex >= 0) |
+ return this._selectElement[this._selectElement.selectedIndex]; |
+ return null; |
+ } |
+}; |
+ |
+WebInspector.LanguageServiceModel.OptionType = { |
+ Inferred: "Inferred", |
+ Builtin: "Builtin", |
+ External: "External" |
+}; |
+ |
+WebInspector.languageService = new WebInspector.LanguageServiceManager(); |