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    var xhr = new XMLHttpRequest();
42    xhr.open("GET", "filteredItemSelectionDialog.css", false);
43    xhr.send(null);
44
45    this.element = document.createElement("div");
46    this.element.className = "filtered-item-list-dialog";
47    this.element.addEventListener("keydown", this._onKeyDown.bind(this), false);
48    var styleElement = this.element.createChild("style");
49    styleElement.type = "text/css";
50    styleElement.textContent = xhr.responseText;
51
52    this._promptElement = this.element.createChild("input", "monospace");
53    this._promptElement.addEventListener("input", this._onInput.bind(this), false);
54    this._promptElement.type = "text";
55    this._promptElement.setAttribute("spellcheck", "false");
56
57    this._filteredItems = [];
58    this._viewportControl = new WebInspector.ViewportControl(this);
59    this._itemElementsContainer = this._viewportControl.element;
60    this._itemElementsContainer.classList.add("container");
61    this._itemElementsContainer.classList.add("monospace");
62    this._itemElementsContainer.addEventListener("click", this._onClick.bind(this), false);
63    this.element.appendChild(this._itemElementsContainer);
64
65    this._delegate = delegate;
66    this._delegate.setRefreshCallback(this._itemsLoaded.bind(this));
67    this._itemsLoaded();
68
69    this._shouldShowMatchingItems = true;
70}
71
72WebInspector.FilteredItemSelectionDialog.prototype = {
73    /**
74     * @param {!Element} element
75     * @param {!Element} relativeToElement
76     */
77    position: function(element, relativeToElement)
78    {
79        const minWidth = 500;
80        const minHeight = 204;
81        var width = Math.max(relativeToElement.offsetWidth * 2 / 3, minWidth);
82        var height = Math.max(relativeToElement.offsetHeight * 2 / 3, minHeight);
83
84        this.element.style.width = width + "px";
85
86        const shadowPadding = 20; // shadow + padding
87        element.positionAt(
88            relativeToElement.totalOffsetLeft() + Math.max((relativeToElement.offsetWidth - width - 2 * shadowPadding) / 2, shadowPadding),
89            relativeToElement.totalOffsetTop() + Math.max((relativeToElement.offsetHeight - height - 2 * shadowPadding) / 2, shadowPadding));
90        this._dialogHeight = height;
91
92        this._updateShowMatchingItems();
93    },
94
95    focus: function()
96    {
97        WebInspector.setCurrentFocusElement(this._promptElement);
98        if (this._filteredItems.length && this._viewportControl.lastVisibleIndex() === -1)
99            this._viewportControl.refresh();
100    },
101
102    willHide: function()
103    {
104        if (this._isHiding)
105            return;
106        this._isHiding = true;
107        this._delegate.dispose();
108        if (this._filterTimer)
109            clearTimeout(this._filterTimer);
110    },
111
112    renderAsTwoRows: function()
113    {
114        this._renderAsTwoRows = true;
115    },
116
117    onEnter: function()
118    {
119        if (!this._delegate.itemCount())
120            return;
121        this._delegate.selectItem(this._filteredItems[this._selectedIndexInFiltered], this._promptElement.value.trim());
122    },
123
124    _itemsLoaded: function()
125    {
126
127        if (this._loadTimeout)
128            return;
129        this._loadTimeout = setTimeout(this._updateAfterItemsLoaded.bind(this), 0);
130    },
131
132    _updateAfterItemsLoaded: function()
133    {
134        delete this._loadTimeout;
135        this._filterItems();
136    },
137
138    /**
139     * @param {number} index
140     * @return {!Element}
141     */
142    _createItemElement: function(index)
143    {
144        var itemElement = document.createElement("div");
145        itemElement.className = "filtered-item-list-dialog-item " + (this._renderAsTwoRows ? "two-rows" : "one-row");
146        itemElement._titleElement = itemElement.createChild("span");
147        itemElement._titleSuffixElement = itemElement.createChild("span");
148        itemElement._subtitleElement = itemElement.createChild("div", "filtered-item-list-dialog-subtitle");
149        itemElement._subtitleElement.textContent = "\u200B";
150        itemElement._index = index;
151        this._delegate.renderItem(index, this._promptElement.value.trim(), itemElement._titleElement, itemElement._subtitleElement);
152        return itemElement;
153    },
154
155    /**
156     * @param {string} query
157     */
158    setQuery: function(query)
159    {
160        this._promptElement.value = query;
161        this._scheduleFilter();
162    },
163
164    _filterItems: function()
165    {
166        delete this._filterTimer;
167        if (this._scoringTimer) {
168            clearTimeout(this._scoringTimer);
169            delete this._scoringTimer;
170        }
171
172        var query = this._delegate.rewriteQuery(this._promptElement.value.trim());
173        this._query = query;
174        var queryLength = query.length;
175        var filterRegex = query ? WebInspector.FilePathScoreFunction.filterRegex(query) : null;
176
177        var oldSelectedAbsoluteIndex = this._selectedIndexInFiltered ? this._filteredItems[this._selectedIndexInFiltered] : null;
178        var filteredItems = [];
179        this._selectedIndexInFiltered = 0;
180
181        var bestScores = [];
182        var bestItems = [];
183        var bestItemsToCollect = 100;
184        var minBestScore = 0;
185        var overflowItems = [];
186
187        scoreItems.call(this, 0);
188
189        /**
190         * @param {number} a
191         * @param {number} b
192         * @return {number}
193         */
194        function compareIntegers(a, b)
195        {
196            return b - a;
197        }
198
199        /**
200         * @param {number} fromIndex
201         * @this {WebInspector.FilteredItemSelectionDialog}
202         */
203        function scoreItems(fromIndex)
204        {
205            var maxWorkItems = 1000;
206            var workDone = 0;
207            for (var i = fromIndex; i < this._delegate.itemCount() && workDone < maxWorkItems; ++i) {
208                // Filter out non-matching items quickly.
209                if (filterRegex && !filterRegex.test(this._delegate.itemKeyAt(i)))
210                    continue;
211
212                // Score item.
213                var score = this._delegate.itemScoreAt(i, query);
214                if (query)
215                    workDone++;
216
217                // Find its index in the scores array (earlier elements have bigger scores).
218                if (score > minBestScore || bestScores.length < bestItemsToCollect) {
219                    var index = insertionIndexForObjectInListSortedByFunction(score, bestScores, compareIntegers, true);
220                    bestScores.splice(index, 0, score);
221                    bestItems.splice(index, 0, i);
222                    if (bestScores.length > bestItemsToCollect) {
223                        // Best list is too large -> drop last elements.
224                        overflowItems.push(bestItems.peekLast());
225                        bestScores.length = bestItemsToCollect;
226                        bestItems.length = bestItemsToCollect;
227                    }
228                    minBestScore = bestScores.peekLast();
229                } else
230                    filteredItems.push(i);
231            }
232
233            // Process everything in chunks.
234            if (i < this._delegate.itemCount()) {
235                this._scoringTimer = setTimeout(scoreItems.bind(this, i), 0);
236                return;
237            }
238            delete this._scoringTimer;
239
240            this._filteredItems = bestItems.concat(overflowItems).concat(filteredItems);
241            for (var i = 0; i < this._filteredItems.length; ++i) {
242                if (this._filteredItems[i] === oldSelectedAbsoluteIndex) {
243                    this._selectedIndexInFiltered = i;
244                    break;
245                }
246            }
247            this._viewportControl.refresh();
248            if (!query)
249                this._selectedIndexInFiltered = 0;
250            this._updateSelection(this._selectedIndexInFiltered, false);
251        }
252    },
253
254    _onInput: function(event)
255    {
256        this._shouldShowMatchingItems = this._delegate.shouldShowMatchingItems(this._promptElement.value);
257        this._updateShowMatchingItems();
258        this._scheduleFilter();
259    },
260
261    _updateShowMatchingItems: function()
262    {
263        this._itemElementsContainer.enableStyleClass("hidden", !this._shouldShowMatchingItems);
264        this.element.style.height = this._shouldShowMatchingItems ? this._dialogHeight + "px" : "auto";
265    },
266
267    _onKeyDown: function(event)
268    {
269        var newSelectedIndex = this._selectedIndexInFiltered;
270
271        switch (event.keyCode) {
272        case WebInspector.KeyboardShortcut.Keys.Down.code:
273            if (++newSelectedIndex >= this._filteredItems.length)
274                newSelectedIndex = this._filteredItems.length - 1;
275            this._updateSelection(newSelectedIndex, true);
276            event.consume(true);
277            break;
278        case WebInspector.KeyboardShortcut.Keys.Up.code:
279            if (--newSelectedIndex < 0)
280                newSelectedIndex = 0;
281            this._updateSelection(newSelectedIndex, false);
282            event.consume(true);
283            break;
284        case WebInspector.KeyboardShortcut.Keys.PageDown.code:
285            newSelectedIndex = Math.min(newSelectedIndex + this._viewportControl.rowsPerViewport(), this._filteredItems.length - 1);
286            this._updateSelection(newSelectedIndex, true);
287            event.consume(true);
288            break;
289        case WebInspector.KeyboardShortcut.Keys.PageUp.code:
290            newSelectedIndex = Math.max(newSelectedIndex - this._viewportControl.rowsPerViewport(), 0);
291            this._updateSelection(newSelectedIndex, false);
292            event.consume(true);
293            break;
294        default:
295        }
296    },
297
298    _scheduleFilter: function()
299    {
300        if (this._filterTimer)
301            return;
302        this._filterTimer = setTimeout(this._filterItems.bind(this), 0);
303    },
304
305    /**
306     * @param {number} index
307     * @param {boolean} makeLast
308     */
309    _updateSelection: function(index, makeLast)
310    {
311        var element = this._viewportControl.renderedElementAt(this._selectedIndexInFiltered);
312        if (element)
313            element.classList.remove("selected");
314        this._viewportControl.scrollItemIntoView(index, makeLast);
315        this._selectedIndexInFiltered = index;
316        element = this._viewportControl.renderedElementAt(index);
317        if (element)
318            element.classList.add("selected");
319    },
320
321    _onClick: function(event)
322    {
323        var itemElement = event.target.enclosingNodeOrSelfWithClass("filtered-item-list-dialog-item");
324        if (!itemElement)
325            return;
326        this._delegate.selectItem(itemElement._index, this._promptElement.value.trim());
327        WebInspector.Dialog.hide();
328    },
329
330    /**
331     * @return {number}
332     */
333    itemCount: function()
334    {
335        return this._filteredItems.length;
336    },
337
338    /**
339     * @param {number} index
340     * @return {!Element}
341     */
342    itemElement: function(index)
343    {
344        var delegateIndex = this._filteredItems[index];
345        var element = this._createItemElement(delegateIndex);
346        if (index === this._selectedIndexInFiltered)
347            element.classList.add("selected");
348        return element;
349    },
350
351    __proto__: WebInspector.DialogDelegate.prototype
352}
353
354/**
355 * @constructor
356 */
357WebInspector.SelectionDialogContentProvider = function()
358{
359}
360
361WebInspector.SelectionDialogContentProvider.prototype = {
362    /**
363     * @param {function():void} refreshCallback
364     */
365    setRefreshCallback: function(refreshCallback)
366    {
367        this._refreshCallback = refreshCallback;
368    },
369
370    /**
371     * @param {string} query
372     * @return {boolean}
373     */
374    shouldShowMatchingItems: function(query)
375    {
376        return true;
377    },
378
379    /**
380     * @return {number}
381     */
382    itemCount: function()
383    {
384        return 0;
385    },
386
387    /**
388     * @param {number} itemIndex
389     * @return {string}
390     */
391    itemKeyAt: function(itemIndex)
392    {
393        return "";
394    },
395
396    /**
397     * @param {number} itemIndex
398     * @param {string} query
399     * @return {number}
400     */
401    itemScoreAt: function(itemIndex, query)
402    {
403        return 1;
404    },
405
406    /**
407     * @param {number} itemIndex
408     * @param {string} query
409     * @param {!Element} titleElement
410     * @param {!Element} subtitleElement
411     */
412    renderItem: function(itemIndex, query, titleElement, subtitleElement)
413    {
414    },
415
416    /**
417     * @param {!Element} element
418     * @param {string} query
419     * @return {boolean}
420     */
421    highlightRanges: function(element, query)
422    {
423        if (!query)
424            return false;
425
426        /**
427         * @param {string} text
428         * @param {string} query
429         * @return {?Array.<!WebInspector.SourceRange>}
430         */
431        function rangesForMatch(text, query)
432        {
433            var sm = new difflib.SequenceMatcher(query, text);
434            var opcodes = sm.get_opcodes();
435            var ranges = [];
436
437            for (var i = 0; i < opcodes.length; ++i) {
438                var opcode = opcodes[i];
439                if (opcode[0] === "equal")
440                    ranges.push(new WebInspector.SourceRange(opcode[3], opcode[4] - opcode[3]));
441                else if (opcode[0] !== "insert")
442                    return null;
443            }
444            return ranges;
445        }
446
447        var text = element.textContent;
448        var ranges = rangesForMatch(text, query);
449        if (!ranges)
450            ranges = rangesForMatch(text.toUpperCase(), query.toUpperCase());
451        if (ranges) {
452            WebInspector.highlightRangesWithStyleClass(element, ranges, "highlight");
453            return true;
454        }
455        return false;
456    },
457
458    /**
459     * @param {number} itemIndex
460     * @param {string} promptValue
461     */
462    selectItem: function(itemIndex, promptValue)
463    {
464    },
465
466    refresh: function()
467    {
468        this._refreshCallback();
469    },
470
471    /**
472     * @param {string} query
473     * @return {string}
474     */
475    rewriteQuery: function(query)
476    {
477        return query;
478    },
479
480    dispose: function()
481    {
482    }
483}
484
485/**
486 * @constructor
487 * @extends {WebInspector.SelectionDialogContentProvider}
488 * @param {!WebInspector.View} view
489 * @param {!WebInspector.ContentProvider} contentProvider
490 * @param {function(number, number)} selectItemCallback
491 */
492WebInspector.JavaScriptOutlineDialog = function(view, contentProvider, selectItemCallback)
493{
494    WebInspector.SelectionDialogContentProvider.call(this);
495
496    this._functionItems = [];
497    this._view = view;
498    this._selectItemCallback = selectItemCallback;
499    contentProvider.requestContent(this._contentAvailable.bind(this));
500}
501
502/**
503 * @param {!WebInspector.View} view
504 * @param {!WebInspector.ContentProvider} contentProvider
505 * @param {function(number, number)} selectItemCallback
506 */
507WebInspector.JavaScriptOutlineDialog.show = function(view, contentProvider, selectItemCallback)
508{
509    if (WebInspector.Dialog.currentInstance())
510        return null;
511    var filteredItemSelectionDialog = new WebInspector.FilteredItemSelectionDialog(new WebInspector.JavaScriptOutlineDialog(view, contentProvider, selectItemCallback));
512    WebInspector.Dialog.show(view.element, filteredItemSelectionDialog);
513}
514
515WebInspector.JavaScriptOutlineDialog.prototype = {
516    /**
517     * @param {?string} content
518     */
519    _contentAvailable: function(content)
520    {
521        this._outlineWorker = new Worker("ScriptFormatterWorker.js");
522        this._outlineWorker.onmessage = this._didBuildOutlineChunk.bind(this);
523        const method = "outline";
524        this._outlineWorker.postMessage({ method: method, params: { content: content } });
525    },
526
527    _didBuildOutlineChunk: function(event)
528    {
529        var data = event.data;
530        var chunk = data["chunk"];
531        for (var i = 0; i < chunk.length; ++i)
532            this._functionItems.push(chunk[i]);
533
534        if (data.total === data.index)
535            this.dispose();
536
537        this.refresh();
538    },
539
540    /**
541     * @return {number}
542     */
543    itemCount: function()
544    {
545        return this._functionItems.length;
546    },
547
548    /**
549     * @param {number} itemIndex
550     * @return {string}
551     */
552    itemKeyAt: function(itemIndex)
553    {
554        return this._functionItems[itemIndex].name;
555    },
556
557    /**
558     * @param {number} itemIndex
559     * @param {string} query
560     * @return {number}
561     */
562    itemScoreAt: function(itemIndex, query)
563    {
564        var item = this._functionItems[itemIndex];
565        return -item.line;
566    },
567
568    /**
569     * @param {number} itemIndex
570     * @param {string} query
571     * @param {!Element} titleElement
572     * @param {!Element} subtitleElement
573     */
574    renderItem: function(itemIndex, query, titleElement, subtitleElement)
575    {
576        var item = this._functionItems[itemIndex];
577        titleElement.textContent = item.name + (item.arguments ? item.arguments : "");
578        this.highlightRanges(titleElement, query);
579        subtitleElement.textContent = ":" + (item.line + 1);
580    },
581
582    /**
583     * @param {number} itemIndex
584     * @param {string} promptValue
585     */
586    selectItem: function(itemIndex, promptValue)
587    {
588        var lineNumber = this._functionItems[itemIndex].line;
589        if (!isNaN(lineNumber) && lineNumber >= 0)
590            this._selectItemCallback(lineNumber, this._functionItems[itemIndex].column);
591    },
592
593    dispose: function()
594    {
595        if (this._outlineWorker) {
596            this._outlineWorker.terminate();
597            delete this._outlineWorker;
598        }
599    },
600
601    __proto__: WebInspector.SelectionDialogContentProvider.prototype
602}
603
604/**
605 * @constructor
606 * @extends {WebInspector.SelectionDialogContentProvider}
607 * @param {!Map.<!WebInspector.UISourceCode, number>=} defaultScores
608 */
609WebInspector.SelectUISourceCodeDialog = function(defaultScores)
610{
611    WebInspector.SelectionDialogContentProvider.call(this);
612
613    /** @type {!Array.<!WebInspector.UISourceCode>} */
614    this._uiSourceCodes = [];
615    var projects = WebInspector.workspace.projects().filter(this.filterProject.bind(this));
616    for (var i = 0; i < projects.length; ++i)
617        this._uiSourceCodes = this._uiSourceCodes.concat(projects[i].uiSourceCodes());
618    this._defaultScores = defaultScores;
619    this._scorer = new WebInspector.FilePathScoreFunction("");
620    WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this);
621}
622
623WebInspector.SelectUISourceCodeDialog.prototype = {
624    /**
625     * @param {?WebInspector.UISourceCode} uiSourceCode
626     * @param {number=} lineNumber
627     */
628    uiSourceCodeSelected: function(uiSourceCode, lineNumber)
629    {
630        // Overridden by subclasses
631    },
632
633    /**
634     * @param {!WebInspector.Project} project
635     */
636    filterProject: function(project)
637    {
638        return true;
639        // Overridden by subclasses
640    },
641
642    /**
643     * @return {number}
644     */
645    itemCount: function()
646    {
647        return this._uiSourceCodes.length;
648    },
649
650    /**
651     * @param {number} itemIndex
652     * @return {string}
653     */
654    itemKeyAt: function(itemIndex)
655    {
656        return this._uiSourceCodes[itemIndex].fullDisplayName();
657    },
658
659    /**
660     * @param {number} itemIndex
661     * @param {string} query
662     * @return {number}
663     */
664    itemScoreAt: function(itemIndex, query)
665    {
666        var uiSourceCode = this._uiSourceCodes[itemIndex];
667        var score = this._defaultScores ? (this._defaultScores.get(uiSourceCode) || 0) : 0;
668        if (!query || query.length < 2)
669            return score;
670
671        if (this._query !== query) {
672            this._query = query;
673            this._scorer = new WebInspector.FilePathScoreFunction(query);
674        }
675
676        var path = uiSourceCode.fullDisplayName();
677        return score + 10 * this._scorer.score(path, null);
678    },
679
680    /**
681     * @param {number} itemIndex
682     * @param {string} query
683     * @param {!Element} titleElement
684     * @param {!Element} subtitleElement
685     */
686    renderItem: function(itemIndex, query, titleElement, subtitleElement)
687    {
688        query = this.rewriteQuery(query);
689        var uiSourceCode = this._uiSourceCodes[itemIndex];
690        titleElement.textContent = uiSourceCode.displayName() + (this._queryLineNumber ? this._queryLineNumber : "");
691        subtitleElement.textContent = uiSourceCode.fullDisplayName().trimEnd(100);
692
693        var indexes = [];
694        var score = new WebInspector.FilePathScoreFunction(query).score(subtitleElement.textContent, indexes);
695        var fileNameIndex = subtitleElement.textContent.lastIndexOf("/");
696        var ranges = [];
697        for (var i = 0; i < indexes.length; ++i)
698            ranges.push({offset: indexes[i], length: 1});
699        if (indexes[0] > fileNameIndex) {
700            for (var i = 0; i < ranges.length; ++i)
701                ranges[i].offset -= fileNameIndex + 1;
702            return WebInspector.highlightRangesWithStyleClass(titleElement, ranges, "highlight");
703        } else {
704            return WebInspector.highlightRangesWithStyleClass(subtitleElement, ranges, "highlight");
705        }
706    },
707
708    /**
709     * @param {number} itemIndex
710     * @param {string} promptValue
711     */
712    selectItem: function(itemIndex, promptValue)
713    {
714        if (/^:\d+$/.test(promptValue.trimRight())) {
715            var lineNumber = parseInt(promptValue.trimRight().substring(1), 10) - 1;
716            if (!isNaN(lineNumber) && lineNumber >= 0)
717                this.uiSourceCodeSelected(null, lineNumber);
718            return;
719        }
720        var lineNumberMatch = promptValue.match(/[^:]+\:([\d]*)$/);
721        var lineNumber = lineNumberMatch ? Math.max(parseInt(lineNumberMatch[1], 10) - 1, 0) : undefined;
722        this.uiSourceCodeSelected(this._uiSourceCodes[itemIndex], lineNumber);
723    },
724
725    /**
726     * @param {string} query
727     * @return {string}
728     */
729    rewriteQuery: function(query)
730    {
731        if (!query)
732            return query;
733        query = query.trim();
734        var lineNumberMatch = query.match(/([^:]+)(\:[\d]*)$/);
735        this._queryLineNumber = lineNumberMatch ? lineNumberMatch[2] : "";
736        return lineNumberMatch ? lineNumberMatch[1] : query;
737    },
738
739    /**
740     * @param {!WebInspector.Event} event
741     */
742    _uiSourceCodeAdded: function(event)
743    {
744        var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
745        if (!this.filterProject(uiSourceCode.project()))
746            return;
747        this._uiSourceCodes.push(uiSourceCode)
748        this.refresh();
749    },
750
751    dispose: function()
752    {
753        WebInspector.workspace.removeEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this);
754    },
755
756    __proto__: WebInspector.SelectionDialogContentProvider.prototype
757}
758
759/**
760 * @constructor
761 * @extends {WebInspector.SelectUISourceCodeDialog}
762 * @param {!WebInspector.SourcesPanel} panel
763 * @param {!Map.<!WebInspector.UISourceCode, number>=} defaultScores
764 */
765WebInspector.OpenResourceDialog = function(panel, defaultScores)
766{
767    WebInspector.SelectUISourceCodeDialog.call(this, defaultScores);
768    this._panel = panel;
769}
770
771WebInspector.OpenResourceDialog.prototype = {
772
773    /**
774     * @param {?WebInspector.UISourceCode} uiSourceCode
775     * @param {number=} lineNumber
776     */
777    uiSourceCodeSelected: function(uiSourceCode, lineNumber)
778    {
779        if (!uiSourceCode)
780            uiSourceCode = this._panel.currentUISourceCode();
781        if (!uiSourceCode)
782            return;
783        this._panel.showUISourceCode(uiSourceCode, lineNumber);
784    },
785
786    /**
787     * @param {string} query
788     * @return {boolean}
789     */
790    shouldShowMatchingItems: function(query)
791    {
792        return !query.startsWith(":");
793    },
794
795    /**
796     * @param {!WebInspector.Project} project
797     */
798    filterProject: function(project)
799    {
800        return !project.isServiceProject();
801    },
802
803    __proto__: WebInspector.SelectUISourceCodeDialog.prototype
804}
805
806/**
807 * @param {!WebInspector.SourcesPanel} panel
808 * @param {!Element} relativeToElement
809 * @param {string=} name
810 * @param {!Map.<!WebInspector.UISourceCode, number>=} defaultScores
811 */
812WebInspector.OpenResourceDialog.show = function(panel, relativeToElement, name, defaultScores)
813{
814    if (WebInspector.Dialog.currentInstance())
815        return;
816
817    var filteredItemSelectionDialog = new WebInspector.FilteredItemSelectionDialog(new WebInspector.OpenResourceDialog(panel, defaultScores));
818    filteredItemSelectionDialog.renderAsTwoRows();
819    if (name)
820        filteredItemSelectionDialog.setQuery(name);
821    WebInspector.Dialog.show(relativeToElement, filteredItemSelectionDialog);
822}
823
824/**
825 * @constructor
826 * @extends {WebInspector.SelectUISourceCodeDialog}
827 * @param {string} type
828 * @param {function(!WebInspector.UISourceCode)} callback
829 */
830WebInspector.SelectUISourceCodeForProjectTypeDialog = function(type, callback)
831{
832    this._type = type;
833    WebInspector.SelectUISourceCodeDialog.call(this);
834    this._callback = callback;
835}
836
837WebInspector.SelectUISourceCodeForProjectTypeDialog.prototype = {
838    /**
839     * @param {!WebInspector.UISourceCode} uiSourceCode
840     * @param {number=} lineNumber
841     */
842    uiSourceCodeSelected: function(uiSourceCode, lineNumber)
843    {
844        this._callback(uiSourceCode);
845    },
846
847    /**
848     * @param {!WebInspector.Project} project
849     */
850    filterProject: function(project)
851    {
852        return project.type() === this._type;
853    },
854
855    __proto__: WebInspector.SelectUISourceCodeDialog.prototype
856}
857
858/**
859 * @param {string} type
860 * @param {function(!WebInspector.UISourceCode)} callback
861 * @param {!Element} relativeToElement
862 */
863WebInspector.SelectUISourceCodeForProjectTypeDialog.show = function(name, type, callback, relativeToElement)
864{
865    if (WebInspector.Dialog.currentInstance())
866        return;
867
868    var filteredItemSelectionDialog = new WebInspector.FilteredItemSelectionDialog(new WebInspector.SelectUISourceCodeForProjectTypeDialog(type, callback));
869    filteredItemSelectionDialog.setQuery(name);
870    filteredItemSelectionDialog.renderAsTwoRows();
871    WebInspector.Dialog.show(relativeToElement, filteredItemSelectionDialog);
872}
873