Home | History | Annotate | Download | only in sources
      1 /*
      2  * Copyright (C) 2012 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 /**
     32  * @constructor
     33  * @extends {WebInspector.DialogDelegate}
     34  * @implements {WebInspector.ViewportControl.Provider}
     35  * @param {!WebInspector.SelectionDialogContentProvider} delegate
     36  */
     37 WebInspector.FilteredItemSelectionDialog = function(delegate)
     38 {
     39     WebInspector.DialogDelegate.call(this);
     40 
     41     if (!WebInspector.FilteredItemSelectionDialog._stylesLoaded) {
     42         WebInspector.View.createStyleElement("filteredItemSelectionDialog.css");
     43         WebInspector.FilteredItemSelectionDialog._stylesLoaded = true;
     44     }
     45 
     46     this.element = document.createElement("div");
     47     this.element.className = "filtered-item-list-dialog";
     48     this.element.addEventListener("keydown", this._onKeyDown.bind(this), false);
     49 
     50     this._promptElement = this.element.createChild("input", "monospace");
     51     this._promptElement.addEventListener("input", this._onInput.bind(this), false);
     52     this._promptElement.type = "text";
     53     this._promptElement.setAttribute("spellcheck", "false");
     54 
     55     this._filteredItems = [];
     56     this._viewportControl = new WebInspector.ViewportControl(this);
     57     this._itemElementsContainer = this._viewportControl.element;
     58     this._itemElementsContainer.classList.add("container");
     59     this._itemElementsContainer.classList.add("monospace");
     60     this._itemElementsContainer.addEventListener("click", this._onClick.bind(this), false);
     61     this.element.appendChild(this._itemElementsContainer);
     62 
     63     this._delegate = delegate;
     64     this._delegate.setRefreshCallback(this._itemsLoaded.bind(this));
     65     this._itemsLoaded();
     66 }
     67 
     68 WebInspector.FilteredItemSelectionDialog.prototype = {
     69     /**
     70      * @param {!Element} element
     71      * @param {!Element} relativeToElement
     72      */
     73     position: function(element, relativeToElement)
     74     {
     75         const shadow = 10;
     76         const shadowPadding = 20; // shadow + padding
     77         var container = WebInspector.Dialog.modalHostView().element;
     78         var preferredWidth = Math.max(relativeToElement.offsetWidth * 2 / 3, 500);
     79         var width = Math.min(preferredWidth, container.offsetWidth - 2 * shadowPadding);
     80         var preferredHeight = Math.max(relativeToElement.offsetHeight * 2 / 3, 204);
     81         var height = Math.min(preferredHeight, container.offsetHeight - 2 * shadowPadding);
     82 
     83         this.element.style.width = width + "px";
     84         var box = relativeToElement.boxInWindow(window).relativeToElement(container);
     85         var positionX = box.x + Math.max((box.width - width - 2 * shadowPadding) / 2, shadow);
     86         positionX = Math.max(shadow, Math.min(container.offsetWidth - width - 2 * shadowPadding, positionX));
     87         var positionY = box.y + Math.max((box.height - height - 2 * shadowPadding) / 2, shadow);
     88         positionY = Math.max(shadow, Math.min(container.offsetHeight - height - 2 * shadowPadding, positionY));
     89         element.positionAt(positionX, positionY, container);
     90         this._dialogHeight = height;
     91 
     92         this._updateShowMatchingItems();
     93         this._viewportControl.refresh();
     94     },
     95 
     96     focus: function()
     97     {
     98         WebInspector.setCurrentFocusElement(this._promptElement);
     99         if (this._filteredItems.length && this._viewportControl.lastVisibleIndex() === -1)
    100             this._viewportControl.refresh();
    101     },
    102 
    103     willHide: function()
    104     {
    105         if (this._isHiding)
    106             return;
    107         this._isHiding = true;
    108         this._delegate.dispose();
    109         if (this._filterTimer)
    110             clearTimeout(this._filterTimer);
    111     },
    112 
    113     renderAsTwoRows: function()
    114     {
    115         this._renderAsTwoRows = true;
    116     },
    117 
    118     onEnter: function()
    119     {
    120         if (!this._delegate.itemCount())
    121             return;
    122         var selectedIndex = this._shouldShowMatchingItems() && this._selectedIndexInFiltered < this._filteredItems.length ? this._filteredItems[this._selectedIndexInFiltered] : null;
    123         this._delegate.selectItem(selectedIndex, this._promptElement.value.trim());
    124     },
    125 
    126     _itemsLoaded: function()
    127     {
    128 
    129         if (this._loadTimeout)
    130             return;
    131         this._loadTimeout = setTimeout(this._updateAfterItemsLoaded.bind(this), 0);
    132     },
    133 
    134     _updateAfterItemsLoaded: function()
    135     {
    136         delete this._loadTimeout;
    137         this._filterItems();
    138     },
    139 
    140     /**
    141      * @param {number} index
    142      * @return {!Element}
    143      */
    144     _createItemElement: function(index)
    145     {
    146         var itemElement = document.createElement("div");
    147         itemElement.className = "filtered-item-list-dialog-item " + (this._renderAsTwoRows ? "two-rows" : "one-row");
    148         itemElement._titleElement = itemElement.createChild("div", "filtered-item-list-dialog-title");
    149         itemElement._subtitleElement = itemElement.createChild("div", "filtered-item-list-dialog-subtitle");
    150         itemElement._subtitleElement.textContent = "\u200B";
    151         itemElement._index = index;
    152         this._delegate.renderItem(index, this._promptElement.value.trim(), itemElement._titleElement, itemElement._subtitleElement);
    153         return itemElement;
    154     },
    155 
    156     /**
    157      * @param {string} query
    158      */
    159     setQuery: function(query)
    160     {
    161         this._promptElement.value = query;
    162         this._scheduleFilter();
    163     },
    164 
    165     _filterItems: function()
    166     {
    167         delete this._filterTimer;
    168         if (this._scoringTimer) {
    169             clearTimeout(this._scoringTimer);
    170             delete this._scoringTimer;
    171         }
    172 
    173         var query = this._delegate.rewriteQuery(this._promptElement.value.trim());
    174         this._query = query;
    175         var queryLength = query.length;
    176         var filterRegex = query ? WebInspector.FilePathScoreFunction.filterRegex(query) : null;
    177 
    178         var oldSelectedAbsoluteIndex = this._selectedIndexInFiltered ? this._filteredItems[this._selectedIndexInFiltered] : null;
    179         var filteredItems = [];
    180         this._selectedIndexInFiltered = 0;
    181 
    182         var bestScores = [];
    183         var bestItems = [];
    184         var bestItemsToCollect = 100;
    185         var minBestScore = 0;
    186         var overflowItems = [];
    187 
    188         scoreItems.call(this, 0);
    189 
    190         /**
    191          * @param {number} a
    192          * @param {number} b
    193          * @return {number}
    194          */
    195         function compareIntegers(a, b)
    196         {
    197             return b - a;
    198         }
    199 
    200         /**
    201          * @param {number} fromIndex
    202          * @this {WebInspector.FilteredItemSelectionDialog}
    203          */
    204         function scoreItems(fromIndex)
    205         {
    206             var maxWorkItems = 1000;
    207             var workDone = 0;
    208             for (var i = fromIndex; i < this._delegate.itemCount() && workDone < maxWorkItems; ++i) {
    209                 // Filter out non-matching items quickly.
    210                 if (filterRegex && !filterRegex.test(this._delegate.itemKeyAt(i)))
    211                     continue;
    212 
    213                 // Score item.
    214                 var score = this._delegate.itemScoreAt(i, query);
    215                 if (query)
    216                     workDone++;
    217 
    218                 // Find its index in the scores array (earlier elements have bigger scores).
    219                 if (score > minBestScore || bestScores.length < bestItemsToCollect) {
    220                     var index = insertionIndexForObjectInListSortedByFunction(score, bestScores, compareIntegers, true);
    221                     bestScores.splice(index, 0, score);
    222                     bestItems.splice(index, 0, i);
    223                     if (bestScores.length > bestItemsToCollect) {
    224                         // Best list is too large -> drop last elements.
    225                         overflowItems.push(bestItems.peekLast());
    226                         bestScores.length = bestItemsToCollect;
    227                         bestItems.length = bestItemsToCollect;
    228                     }
    229                     minBestScore = bestScores.peekLast();
    230                 } else
    231                     filteredItems.push(i);
    232             }
    233 
    234             // Process everything in chunks.
    235             if (i < this._delegate.itemCount()) {
    236                 this._scoringTimer = setTimeout(scoreItems.bind(this, i), 0);
    237                 return;
    238             }
    239             delete this._scoringTimer;
    240 
    241             this._filteredItems = bestItems.concat(overflowItems).concat(filteredItems);
    242             for (var i = 0; i < this._filteredItems.length; ++i) {
    243                 if (this._filteredItems[i] === oldSelectedAbsoluteIndex) {
    244                     this._selectedIndexInFiltered = i;
    245                     break;
    246                 }
    247             }
    248             this._viewportControl.invalidate();
    249             if (!query)
    250                 this._selectedIndexInFiltered = 0;
    251             this._updateSelection(this._selectedIndexInFiltered, false);
    252         }
    253     },
    254 
    255     /**
    256      * @return {boolean}
    257      */
    258     _shouldShowMatchingItems: function()
    259     {
    260         return this._delegate.shouldShowMatchingItems(this._promptElement.value);
    261     },
    262 
    263     _onInput: function(event)
    264     {
    265         this._updateShowMatchingItems();
    266         this._scheduleFilter();
    267     },
    268 
    269     _updateShowMatchingItems: function()
    270     {
    271         var shouldShowMatchingItems = this._shouldShowMatchingItems();
    272         this._itemElementsContainer.classList.toggle("hidden", !shouldShowMatchingItems);
    273         this.element.style.height = shouldShowMatchingItems ? this._dialogHeight + "px" : "auto";
    274     },
    275 
    276     /**
    277      * @return {number}
    278      */
    279     _rowsPerViewport: function()
    280     {
    281         return Math.floor(this._viewportControl.element.clientHeight / this._rowHeight);
    282     },
    283 
    284     _onKeyDown: function(event)
    285     {
    286         var newSelectedIndex = this._selectedIndexInFiltered;
    287 
    288         switch (event.keyCode) {
    289         case WebInspector.KeyboardShortcut.Keys.Down.code:
    290             if (++newSelectedIndex >= this._filteredItems.length)
    291                 newSelectedIndex = this._filteredItems.length - 1;
    292             this._updateSelection(newSelectedIndex, true);
    293             event.consume(true);
    294             break;
    295         case WebInspector.KeyboardShortcut.Keys.Up.code:
    296             if (--newSelectedIndex < 0)
    297                 newSelectedIndex = 0;
    298             this._updateSelection(newSelectedIndex, false);
    299             event.consume(true);
    300             break;
    301         case WebInspector.KeyboardShortcut.Keys.PageDown.code:
    302             newSelectedIndex = Math.min(newSelectedIndex + this._rowsPerViewport(), this._filteredItems.length - 1);
    303             this._updateSelection(newSelectedIndex, true);
    304             event.consume(true);
    305             break;
    306         case WebInspector.KeyboardShortcut.Keys.PageUp.code:
    307             newSelectedIndex = Math.max(newSelectedIndex - this._rowsPerViewport(), 0);
    308             this._updateSelection(newSelectedIndex, false);
    309             event.consume(true);
    310             break;
    311         default:
    312         }
    313     },
    314 
    315     _scheduleFilter: function()
    316     {
    317         if (this._filterTimer)
    318             return;
    319         this._filterTimer = setTimeout(this._filterItems.bind(this), 0);
    320     },
    321 
    322     /**
    323      * @param {number} index
    324      * @param {boolean} makeLast
    325      */
    326     _updateSelection: function(index, makeLast)
    327     {
    328         if (!this._filteredItems.length)
    329             return;
    330         var element = this._viewportControl.renderedElementAt(this._selectedIndexInFiltered);
    331         if (element)
    332             element.classList.remove("selected");
    333         this._viewportControl.scrollItemIntoView(index, makeLast);
    334         this._selectedIndexInFiltered = index;
    335         element = this._viewportControl.renderedElementAt(index);
    336         if (element)
    337             element.classList.add("selected");
    338     },
    339 
    340     _onClick: function(event)
    341     {
    342         var itemElement = event.target.enclosingNodeOrSelfWithClass("filtered-item-list-dialog-item");
    343         if (!itemElement)
    344             return;
    345         this._delegate.selectItem(itemElement._index, this._promptElement.value.trim());
    346         WebInspector.Dialog.hide();
    347     },
    348 
    349     /**
    350      * @return {number}
    351      */
    352     itemCount: function()
    353     {
    354         return this._filteredItems.length;
    355     },
    356 
    357     /**
    358      * @param {number} index
    359      * @return {number}
    360      */
    361     fastHeight: function(index)
    362     {
    363         if (!this._rowHeight) {
    364             var delegateIndex = this._filteredItems[index];
    365             var element = this._createItemElement(delegateIndex);
    366             this._rowHeight = element.measurePreferredSize(this._viewportControl.contentElement()).height;
    367         }
    368         return this._rowHeight;
    369     },
    370 
    371     /**
    372      * @param {number} index
    373      * @return {!WebInspector.ViewportElement}
    374      */
    375     itemElement: function(index)
    376     {
    377         var delegateIndex = this._filteredItems[index];
    378         var element = this._createItemElement(delegateIndex);
    379         if (index === this._selectedIndexInFiltered)
    380             element.classList.add("selected");
    381         return new WebInspector.StaticViewportElement(element);
    382     },
    383 
    384     /**
    385      * @return {number}
    386      */
    387     minimumRowHeight: function()
    388     {
    389         return this.fastHeight(0);
    390     },
    391 
    392     __proto__: WebInspector.DialogDelegate.prototype
    393 }
    394 
    395 /**
    396  * @constructor
    397  */
    398 WebInspector.SelectionDialogContentProvider = function()
    399 {
    400 }
    401 
    402 WebInspector.SelectionDialogContentProvider.prototype = {
    403     /**
    404      * @param {function():void} refreshCallback
    405      */
    406     setRefreshCallback: function(refreshCallback)
    407     {
    408         this._refreshCallback = refreshCallback;
    409     },
    410 
    411     /**
    412      * @param {string} query
    413      * @return {boolean}
    414      */
    415     shouldShowMatchingItems: function(query)
    416     {
    417         return true;
    418     },
    419 
    420     /**
    421      * @return {number}
    422      */
    423     itemCount: function()
    424     {
    425         return 0;
    426     },
    427 
    428     /**
    429      * @param {number} itemIndex
    430      * @return {string}
    431      */
    432     itemKeyAt: function(itemIndex)
    433     {
    434         return "";
    435     },
    436 
    437     /**
    438      * @param {number} itemIndex
    439      * @param {string} query
    440      * @return {number}
    441      */
    442     itemScoreAt: function(itemIndex, query)
    443     {
    444         return 1;
    445     },
    446 
    447     /**
    448      * @param {number} itemIndex
    449      * @param {string} query
    450      * @param {!Element} titleElement
    451      * @param {!Element} subtitleElement
    452      */
    453     renderItem: function(itemIndex, query, titleElement, subtitleElement)
    454     {
    455     },
    456 
    457     /**
    458      * @param {!Element} element
    459      * @param {string} query
    460      * @return {boolean}
    461      */
    462     highlightRanges: function(element, query)
    463     {
    464         if (!query)
    465             return false;
    466 
    467         /**
    468          * @param {string} text
    469          * @param {string} query
    470          * @return {?Array.<!WebInspector.SourceRange>}
    471          */
    472         function rangesForMatch(text, query)
    473         {
    474             var sm = new difflib.SequenceMatcher(query, text);
    475             var opcodes = sm.get_opcodes();
    476             var ranges = [];
    477 
    478             for (var i = 0; i < opcodes.length; ++i) {
    479                 var opcode = opcodes[i];
    480                 if (opcode[0] === "equal")
    481                     ranges.push(new WebInspector.SourceRange(opcode[3], opcode[4] - opcode[3]));
    482                 else if (opcode[0] !== "insert")
    483                     return null;
    484             }
    485             return ranges;
    486         }
    487 
    488         var text = element.textContent;
    489         var ranges = rangesForMatch(text, query);
    490         if (!ranges)
    491             ranges = rangesForMatch(text.toUpperCase(), query.toUpperCase());
    492         if (ranges) {
    493             WebInspector.highlightRangesWithStyleClass(element, ranges, "highlight");
    494             return true;
    495         }
    496         return false;
    497     },
    498 
    499     /**
    500      * @param {?number} itemIndex
    501      * @param {string} promptValue
    502      */
    503     selectItem: function(itemIndex, promptValue)
    504     {
    505     },
    506 
    507     refresh: function()
    508     {
    509         this._refreshCallback();
    510     },
    511 
    512     /**
    513      * @param {string} query
    514      * @return {string}
    515      */
    516     rewriteQuery: function(query)
    517     {
    518         return query;
    519     },
    520 
    521     dispose: function()
    522     {
    523     }
    524 }
    525 
    526 /**
    527  * @constructor
    528  * @extends {WebInspector.SelectionDialogContentProvider}
    529  * @param {!WebInspector.UISourceCode} uiSourceCode
    530  * @param {function(number, number)} selectItemCallback
    531  */
    532 WebInspector.JavaScriptOutlineDialog = function(uiSourceCode, selectItemCallback)
    533 {
    534     WebInspector.SelectionDialogContentProvider.call(this);
    535 
    536     this._functionItems = [];
    537     this._selectItemCallback = selectItemCallback;
    538     this._outlineWorker = Runtime.startWorker("script_formatter_worker");
    539     this._outlineWorker.onmessage = this._didBuildOutlineChunk.bind(this);
    540     this._outlineWorker.postMessage({ method: "javaScriptOutline", params: { content: uiSourceCode.workingCopy() } });
    541 }
    542 
    543 /**
    544  * @param {!WebInspector.View} view
    545  * @param {!WebInspector.UISourceCode} uiSourceCode
    546  * @param {function(number, number)} selectItemCallback
    547  */
    548 WebInspector.JavaScriptOutlineDialog.show = function(view, uiSourceCode, selectItemCallback)
    549 {
    550     if (WebInspector.Dialog.currentInstance())
    551         return;
    552     var filteredItemSelectionDialog = new WebInspector.FilteredItemSelectionDialog(new WebInspector.JavaScriptOutlineDialog(uiSourceCode, selectItemCallback));
    553     WebInspector.Dialog.show(view.element, filteredItemSelectionDialog);
    554 }
    555 
    556 WebInspector.JavaScriptOutlineDialog.prototype = {
    557     /**
    558      * @param {!MessageEvent} event
    559      */
    560     _didBuildOutlineChunk: function(event)
    561     {
    562         var data = /** @type {!WebInspector.JavaScriptOutlineDialog.MessageEventData} */ (event.data);
    563         var chunk = data.chunk;
    564         for (var i = 0; i < chunk.length; ++i)
    565             this._functionItems.push(chunk[i]);
    566 
    567         if (data.total === data.index + 1)
    568             this.dispose();
    569 
    570         this.refresh();
    571     },
    572 
    573     /**
    574      * @return {number}
    575      */
    576     itemCount: function()
    577     {
    578         return this._functionItems.length;
    579     },
    580 
    581     /**
    582      * @param {number} itemIndex
    583      * @return {string}
    584      */
    585     itemKeyAt: function(itemIndex)
    586     {
    587         return this._functionItems[itemIndex].name;
    588     },
    589 
    590     /**
    591      * @param {number} itemIndex
    592      * @param {string} query
    593      * @return {number}
    594      */
    595     itemScoreAt: function(itemIndex, query)
    596     {
    597         var item = this._functionItems[itemIndex];
    598         return -item.line;
    599     },
    600 
    601     /**
    602      * @param {number} itemIndex
    603      * @param {string} query
    604      * @param {!Element} titleElement
    605      * @param {!Element} subtitleElement
    606      */
    607     renderItem: function(itemIndex, query, titleElement, subtitleElement)
    608     {
    609         var item = this._functionItems[itemIndex];
    610         titleElement.textContent = item.name + (item.arguments ? item.arguments : "");
    611         this.highlightRanges(titleElement, query);
    612         subtitleElement.textContent = ":" + (item.line + 1);
    613     },
    614 
    615     /**
    616      * @param {?number} itemIndex
    617      * @param {string} promptValue
    618      */
    619     selectItem: function(itemIndex, promptValue)
    620     {
    621         if (itemIndex === null)
    622             return;
    623         var lineNumber = this._functionItems[itemIndex].line;
    624         if (!isNaN(lineNumber) && lineNumber >= 0)
    625             this._selectItemCallback(lineNumber, this._functionItems[itemIndex].column);
    626     },
    627 
    628     dispose: function()
    629     {
    630         if (this._outlineWorker) {
    631             this._outlineWorker.terminate();
    632             delete this._outlineWorker;
    633         }
    634     },
    635 
    636     __proto__: WebInspector.SelectionDialogContentProvider.prototype
    637 }
    638 
    639 /**
    640  * @constructor
    641  * @extends {WebInspector.SelectionDialogContentProvider}
    642  * @param {!Map.<!WebInspector.UISourceCode, number>=} defaultScores
    643  */
    644 WebInspector.SelectUISourceCodeDialog = function(defaultScores)
    645 {
    646     WebInspector.SelectionDialogContentProvider.call(this);
    647 
    648     this._populate();
    649     this._defaultScores = defaultScores;
    650     this._scorer = new WebInspector.FilePathScoreFunction("");
    651     WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this);
    652     WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.ProjectRemoved, this._projectRemoved, this);
    653 }
    654 
    655 WebInspector.SelectUISourceCodeDialog.prototype = {
    656     _projectRemoved: function(event)
    657     {
    658         var project = /** @type {!WebInspector.Project} */ (event.data);
    659         this._populate(project);
    660         this.refresh();
    661     },
    662 
    663     /**
    664      * @param {!WebInspector.Project=} skipProject
    665      */
    666     _populate: function(skipProject)
    667     {
    668         /** @type {!Array.<!WebInspector.UISourceCode>} */
    669         this._uiSourceCodes = [];
    670         var projects = WebInspector.workspace.projects().filter(this.filterProject.bind(this));
    671         for (var i = 0; i < projects.length; ++i) {
    672             if (skipProject && projects[i] === skipProject)
    673                 continue;
    674             this._uiSourceCodes = this._uiSourceCodes.concat(projects[i].uiSourceCodes());
    675         }
    676     },
    677 
    678     /**
    679      * @param {?WebInspector.UISourceCode} uiSourceCode
    680      * @param {number=} lineNumber
    681      * @param {number=} columnNumber
    682      */
    683     uiSourceCodeSelected: function(uiSourceCode, lineNumber, columnNumber)
    684     {
    685         // Overridden by subclasses
    686     },
    687 
    688     /**
    689      * @param {!WebInspector.Project} project
    690      * @return {boolean}
    691      */
    692     filterProject: function(project)
    693     {
    694         return true;
    695         // Overridden by subclasses
    696     },
    697 
    698     /**
    699      * @return {number}
    700      */
    701     itemCount: function()
    702     {
    703         return this._uiSourceCodes.length;
    704     },
    705 
    706     /**
    707      * @param {number} itemIndex
    708      * @return {string}
    709      */
    710     itemKeyAt: function(itemIndex)
    711     {
    712         return this._uiSourceCodes[itemIndex].fullDisplayName();
    713     },
    714 
    715     /**
    716      * @param {number} itemIndex
    717      * @param {string} query
    718      * @return {number}
    719      */
    720     itemScoreAt: function(itemIndex, query)
    721     {
    722         var uiSourceCode = this._uiSourceCodes[itemIndex];
    723         var score = this._defaultScores ? (this._defaultScores.get(uiSourceCode) || 0) : 0;
    724         if (!query || query.length < 2)
    725             return score;
    726 
    727         if (this._query !== query) {
    728             this._query = query;
    729             this._scorer = new WebInspector.FilePathScoreFunction(query);
    730         }
    731 
    732         var path = uiSourceCode.fullDisplayName();
    733         return score + 10 * this._scorer.score(path, null);
    734     },
    735 
    736     /**
    737      * @param {number} itemIndex
    738      * @param {string} query
    739      * @param {!Element} titleElement
    740      * @param {!Element} subtitleElement
    741      * @return {!Array.<!Element>}
    742      */
    743     renderItem: function(itemIndex, query, titleElement, subtitleElement)
    744     {
    745         query = this.rewriteQuery(query);
    746         var uiSourceCode = this._uiSourceCodes[itemIndex];
    747         titleElement.textContent = uiSourceCode.displayName() + (this._queryLineNumberAndColumnNumber || "");
    748         subtitleElement.textContent = uiSourceCode.fullDisplayName().trimEnd(100);
    749 
    750         var indexes = [];
    751         var score = new WebInspector.FilePathScoreFunction(query).score(subtitleElement.textContent, indexes);
    752         var fileNameIndex = subtitleElement.textContent.lastIndexOf("/");
    753         var ranges = [];
    754         for (var i = 0; i < indexes.length; ++i)
    755             ranges.push({offset: indexes[i], length: 1});
    756         if (indexes[0] > fileNameIndex) {
    757             for (var i = 0; i < ranges.length; ++i)
    758                 ranges[i].offset -= fileNameIndex + 1;
    759             return WebInspector.highlightRangesWithStyleClass(titleElement, ranges, "highlight");
    760         } else {
    761             return WebInspector.highlightRangesWithStyleClass(subtitleElement, ranges, "highlight");
    762         }
    763     },
    764 
    765     /**
    766      * @param {?number} itemIndex
    767      * @param {string} promptValue
    768      */
    769     selectItem: function(itemIndex, promptValue)
    770     {
    771         var parsedExpression = promptValue.trim().match(/^([^:]*)(:\d+)?(:\d+)?$/);
    772         if (!parsedExpression)
    773             return;
    774 
    775         var lineNumber;
    776         var columnNumber;
    777         if (parsedExpression[2])
    778             lineNumber = parseInt(parsedExpression[2].substr(1), 10) - 1;
    779         if (parsedExpression[3])
    780             columnNumber = parseInt(parsedExpression[3].substr(1), 10) - 1;
    781         var uiSourceCode = itemIndex !== null ? this._uiSourceCodes[itemIndex] : null;
    782         this.uiSourceCodeSelected(uiSourceCode, lineNumber, columnNumber);
    783     },
    784 
    785     /**
    786      * @param {string} query
    787      * @return {string}
    788      */
    789     rewriteQuery: function(query)
    790     {
    791         if (!query)
    792             return query;
    793         query = query.trim();
    794         var lineNumberMatch = query.match(/^([^:]+)((?::[^:]*){0,2})$/);
    795         this._queryLineNumberAndColumnNumber = lineNumberMatch ? lineNumberMatch[2] : "";
    796         return lineNumberMatch ? lineNumberMatch[1] : query;
    797     },
    798 
    799     /**
    800      * @param {!WebInspector.Event} event
    801      */
    802     _uiSourceCodeAdded: function(event)
    803     {
    804         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
    805         if (!this.filterProject(uiSourceCode.project()))
    806             return;
    807         this._uiSourceCodes.push(uiSourceCode)
    808         this.refresh();
    809     },
    810 
    811     dispose: function()
    812     {
    813         WebInspector.workspace.removeEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this);
    814         WebInspector.workspace.removeEventListener(WebInspector.Workspace.Events.ProjectRemoved, this._projectRemoved, this);
    815     },
    816 
    817     __proto__: WebInspector.SelectionDialogContentProvider.prototype
    818 }
    819 
    820 /**
    821  * @constructor
    822  * @extends {WebInspector.SelectUISourceCodeDialog}
    823  * @param {!WebInspector.SourcesView} sourcesView
    824  * @param {!Map.<!WebInspector.UISourceCode, number>=} defaultScores
    825  */
    826 WebInspector.OpenResourceDialog = function(sourcesView, defaultScores)
    827 {
    828     WebInspector.SelectUISourceCodeDialog.call(this, defaultScores);
    829     this._sourcesView = sourcesView;
    830 }
    831 
    832 WebInspector.OpenResourceDialog.prototype = {
    833 
    834     /**
    835      * @param {?WebInspector.UISourceCode} uiSourceCode
    836      * @param {number=} lineNumber
    837      * @param {number=} columnNumber
    838      */
    839     uiSourceCodeSelected: function(uiSourceCode, lineNumber, columnNumber)
    840     {
    841         if (!uiSourceCode)
    842             uiSourceCode = this._sourcesView.currentUISourceCode();
    843         if (!uiSourceCode)
    844             return;
    845         this._sourcesView.showSourceLocation(uiSourceCode, lineNumber, columnNumber);
    846     },
    847 
    848     /**
    849      * @param {string} query
    850      * @return {boolean}
    851      */
    852     shouldShowMatchingItems: function(query)
    853     {
    854         return !query.startsWith(":");
    855     },
    856 
    857     /**
    858      * @param {!WebInspector.Project} project
    859      * @return {boolean}
    860      */
    861     filterProject: function(project)
    862     {
    863         return !project.isServiceProject();
    864     },
    865 
    866     __proto__: WebInspector.SelectUISourceCodeDialog.prototype
    867 }
    868 
    869 /**
    870  * @param {!WebInspector.SourcesView} sourcesView
    871  * @param {!Element} relativeToElement
    872  * @param {string=} query
    873  * @param {!Map.<!WebInspector.UISourceCode, number>=} defaultScores
    874  */
    875 WebInspector.OpenResourceDialog.show = function(sourcesView, relativeToElement, query, defaultScores)
    876 {
    877     if (WebInspector.Dialog.currentInstance())
    878         return;
    879 
    880     var filteredItemSelectionDialog = new WebInspector.FilteredItemSelectionDialog(new WebInspector.OpenResourceDialog(sourcesView, defaultScores));
    881     filteredItemSelectionDialog.renderAsTwoRows();
    882     WebInspector.Dialog.show(relativeToElement, filteredItemSelectionDialog);
    883     if (query)
    884         filteredItemSelectionDialog.setQuery(query);
    885 }
    886 
    887 /**
    888  * @constructor
    889  * @extends {WebInspector.SelectUISourceCodeDialog}
    890  * @param {!Array.<string>} types
    891  * @param {function(!WebInspector.UISourceCode)} callback
    892  */
    893 WebInspector.SelectUISourceCodeForProjectTypesDialog = function(types, callback)
    894 {
    895     this._types = types;
    896     WebInspector.SelectUISourceCodeDialog.call(this);
    897     this._callback = callback;
    898 }
    899 
    900 WebInspector.SelectUISourceCodeForProjectTypesDialog.prototype = {
    901     /**
    902      * @param {!WebInspector.UISourceCode} uiSourceCode
    903      * @param {number=} lineNumber
    904      * @param {number=} columnNumber
    905      */
    906     uiSourceCodeSelected: function(uiSourceCode, lineNumber, columnNumber)
    907     {
    908         this._callback(uiSourceCode);
    909     },
    910 
    911     /**
    912      * @param {!WebInspector.Project} project
    913      * @return {boolean}
    914      */
    915     filterProject: function(project)
    916     {
    917         return this._types.indexOf(project.type()) !== -1;
    918     },
    919 
    920     __proto__: WebInspector.SelectUISourceCodeDialog.prototype
    921 }
    922 
    923 /**
    924  * @param {string} name
    925  * @param {!Array.<string>} types
    926  * @param {function(!WebInspector.UISourceCode)} callback
    927  * @param {!Element} relativeToElement
    928  */
    929 WebInspector.SelectUISourceCodeForProjectTypesDialog.show = function(name, types, callback, relativeToElement)
    930 {
    931     if (WebInspector.Dialog.currentInstance())
    932         return;
    933 
    934     var filteredItemSelectionDialog = new WebInspector.FilteredItemSelectionDialog(new WebInspector.SelectUISourceCodeForProjectTypesDialog(types, callback));
    935     filteredItemSelectionDialog.setQuery(name);
    936     filteredItemSelectionDialog.renderAsTwoRows();
    937     WebInspector.Dialog.show(relativeToElement, filteredItemSelectionDialog);
    938 }
    939 
    940 /**
    941  * @typedef {{index: number, total: number, chunk: !Array.<!{selectorText: string, lineNumber: number, columnNumber: number}>}}
    942  */
    943 WebInspector.JavaScriptOutlineDialog.MessageEventData;
    944