| 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();
|
|
|