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 */
37WebInspector.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
68WebInspector.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 */
398WebInspector.SelectionDialogContentProvider = function()
399{
400}
401
402WebInspector.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 */
532WebInspector.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 */
548WebInspector.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
556WebInspector.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 */
644WebInspector.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
655WebInspector.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 */
826WebInspector.OpenResourceDialog = function(sourcesView, defaultScores)
827{
828    WebInspector.SelectUISourceCodeDialog.call(this, defaultScores);
829    this._sourcesView = sourcesView;
830}
831
832WebInspector.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 */
875WebInspector.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 */
893WebInspector.SelectUISourceCodeForProjectTypesDialog = function(types, callback)
894{
895    this._types = types;
896    WebInspector.SelectUISourceCodeDialog.call(this);
897    this._callback = callback;
898}
899
900WebInspector.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 */
929WebInspector.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 */
943WebInspector.JavaScriptOutlineDialog.MessageEventData;
944