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