Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1279)

Unified Diff: Source/devtools/front_end/languages/LanguageService.js

Issue 1264133002: Devtools: [WIP] Implement enhanced devtools extension language APIs Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Modify override dropdown to apply to console completions & transpile Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..a4614a24227292f84c1531c5163b97c502fa6ba0
--- /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}
+ */
+ get name() {},
pfeldman 2015/08/13 21:15:46 We don't use getters for the new code.
wes 2015/08/14 00:55:05 Oh, okay. I had seen them around elsewhere and was
pfeldman 2015/08/17 21:15:51 I know, we are slowly getting rid of them wherever
+
+ /**
+ * @param {string} capability
+ * @return {boolean}
+ */
+ handlesCapability: function(capability) { return false; }
pfeldman 2015/08/13 21:15:46 capabilities: function getter instead?
wes 2015/08/14 00:55:05 Possible. I can't really think of a use-case where
+}
+
+/**
+ * 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",
pfeldman 2015/08/13 21:15:46 4 space indent everywhere.
wes 2015/08/14 00:55:05 Ach. At least I was internally consistent to the f
pfeldman 2015/08/17 21:15:51 Nope, we don't have a linter :( We use the Blink c
+ 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.handlesCapability(capability)) {
+ 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(k => capability ? this._servicesByMime[Symbol.for(capability)][k] : this._servicesByMime[k]))];
+ },
+
+ /**
+ * @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;
+
+ 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}
+ */
+ get name() {
+ 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[capability] = function() {
+ var args = arguments;
+ return new Promise(function(resolve, reject) {
+ func.call(_this, ...args, resolve);
+ });
+ };
+ },
+
+ /**
+ * @override
+ * @param {string} capability
+ * @return {boolean}
+ */
+ handlesCapability: function(capability) {
+ return !!this[capability];
+ }
+};
+
+/**
+ * @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(service => {
+ this.addOption(service);
+ });
+
+ 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(c => {
+ WebInspector.languageService.enableContextualHandling(c);
+ });
+ break;
+ case WebInspector.LanguageServiceModel.OptionType.Builtin:
+ this._capabilities.forEach(c => {
+ WebInspector.languageService.disableContextualHandling(c);
+ });
+ break;
+ case WebInspector.LanguageServiceModel.OptionType.External:
+ this._capabilities.forEach(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();

Powered by Google App Engine
This is Rietveld 408576698