Home | History | Annotate | Download | only in sources
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /**
      6  * @constructor
      7  * @implements {WebInspector.TabbedEditorContainerDelegate}
      8  * @implements {WebInspector.Searchable}
      9  * @implements {WebInspector.Replaceable}
     10  * @extends {WebInspector.VBox}
     11  * @param {!WebInspector.Workspace} workspace
     12  * @param {!WebInspector.SourcesPanel} sourcesPanel
     13  */
     14 WebInspector.SourcesView = function(workspace, sourcesPanel)
     15 {
     16     WebInspector.VBox.call(this);
     17     this.registerRequiredCSS("sourcesView.css");
     18     this.element.id = "sources-panel-sources-view";
     19     this.setMinimumAndPreferredSizes(50, 25, 150, 100);
     20 
     21     this._workspace = workspace;
     22     this._sourcesPanel = sourcesPanel;
     23 
     24     this._searchableView = new WebInspector.SearchableView(this);
     25     this._searchableView.setMinimalSearchQuerySize(0);
     26     this._searchableView.show(this.element);
     27 
     28     /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.UISourceCodeFrame>} */
     29     this._sourceFramesByUISourceCode = new Map();
     30 
     31     var tabbedEditorPlaceholderText = WebInspector.isMac() ? WebInspector.UIString("Hit Cmd+P to open a file") : WebInspector.UIString("Hit Ctrl+P to open a file");
     32     this._editorContainer = new WebInspector.TabbedEditorContainer(this, "previouslyViewedFiles", tabbedEditorPlaceholderText);
     33     this._editorContainer.show(this._searchableView.element);
     34     this._editorContainer.addEventListener(WebInspector.TabbedEditorContainer.Events.EditorSelected, this._editorSelected, this);
     35     this._editorContainer.addEventListener(WebInspector.TabbedEditorContainer.Events.EditorClosed, this._editorClosed, this);
     36 
     37     this._historyManager = new WebInspector.EditingLocationHistoryManager(this, this.currentSourceFrame.bind(this));
     38 
     39     this._statusBarContainerElement = this.element.createChild("div", "sources-status-bar");
     40 
     41     /**
     42      * @this {WebInspector.SourcesView}
     43      * @param {!WebInspector.SourcesView.EditorAction} EditorAction
     44      */
     45     function appendButtonForExtension(EditorAction)
     46     {
     47         this._statusBarContainerElement.appendChild(EditorAction.button(this));
     48     }
     49     var editorActions = /** @type {!Array.<!WebInspector.SourcesView.EditorAction>} */ (self.runtime.instances(WebInspector.SourcesView.EditorAction));
     50     editorActions.forEach(appendButtonForExtension.bind(this));
     51 
     52     this._scriptViewStatusBarItemsContainer = this._statusBarContainerElement.createChild("div", "inline-block");
     53     this._scriptViewStatusBarTextContainer = this._statusBarContainerElement.createChild("div", "hbox");
     54 
     55     WebInspector.startBatchUpdate();
     56     this._workspace.uiSourceCodes().forEach(this._addUISourceCode.bind(this));
     57     WebInspector.endBatchUpdate();
     58 
     59     this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this);
     60     this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
     61     this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectRemoved, this._projectRemoved.bind(this), this);
     62 
     63     function handleBeforeUnload(event)
     64     {
     65         if (event.returnValue)
     66             return;
     67         var unsavedSourceCodes = WebInspector.workspace.unsavedSourceCodes();
     68         if (!unsavedSourceCodes.length)
     69             return;
     70 
     71         event.returnValue = WebInspector.UIString("DevTools have unsaved changes that will be permanently lost.");
     72         WebInspector.inspectorView.showPanel("sources");
     73         for (var i = 0; i < unsavedSourceCodes.length; ++i)
     74             WebInspector.Revealer.reveal(unsavedSourceCodes[i]);
     75     }
     76     window.addEventListener("beforeunload", handleBeforeUnload, true);
     77 
     78     this._shortcuts = {};
     79     this.element.addEventListener("keydown", this._handleKeyDown.bind(this), false);
     80 }
     81 
     82 WebInspector.SourcesView.Events = {
     83     EditorClosed: "EditorClosed",
     84     EditorSelected: "EditorSelected",
     85 }
     86 
     87 WebInspector.SourcesView.prototype = {
     88     /**
     89      * @param {function(!Array.<!WebInspector.KeyboardShortcut.Descriptor>, function(!Event=):boolean)} registerShortcutDelegate
     90      */
     91     registerShortcuts: function(registerShortcutDelegate)
     92     {
     93         /**
     94          * @this {WebInspector.SourcesView}
     95          * @param {!Array.<!WebInspector.KeyboardShortcut.Descriptor>} shortcuts
     96          * @param {function(!Event=):boolean} handler
     97          */
     98         function registerShortcut(shortcuts, handler)
     99         {
    100             registerShortcutDelegate(shortcuts, handler);
    101             this._registerShortcuts(shortcuts, handler);
    102         }
    103 
    104         registerShortcut.call(this, WebInspector.ShortcutsScreen.SourcesPanelShortcuts.JumpToPreviousLocation, this._onJumpToPreviousLocation.bind(this));
    105         registerShortcut.call(this, WebInspector.ShortcutsScreen.SourcesPanelShortcuts.JumpToNextLocation, this._onJumpToNextLocation.bind(this));
    106         registerShortcut.call(this, WebInspector.ShortcutsScreen.SourcesPanelShortcuts.CloseEditorTab, this._onCloseEditorTab.bind(this));
    107         registerShortcut.call(this, WebInspector.ShortcutsScreen.SourcesPanelShortcuts.GoToLine, this._showGoToLineDialog.bind(this));
    108         registerShortcut.call(this, WebInspector.ShortcutsScreen.SourcesPanelShortcuts.GoToMember, this._showOutlineDialog.bind(this));
    109         registerShortcut.call(this, [WebInspector.KeyboardShortcut.makeDescriptor("o", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta | WebInspector.KeyboardShortcut.Modifiers.Shift)], this._showOutlineDialog.bind(this));
    110         registerShortcut.call(this, WebInspector.ShortcutsScreen.SourcesPanelShortcuts.ToggleBreakpoint, this._toggleBreakpoint.bind(this));
    111         registerShortcut.call(this, WebInspector.ShortcutsScreen.SourcesPanelShortcuts.Save, this._save.bind(this));
    112         registerShortcut.call(this, WebInspector.ShortcutsScreen.SourcesPanelShortcuts.SaveAll, this._saveAll.bind(this));
    113     },
    114 
    115     /**
    116      * @param {!Array.<!WebInspector.KeyboardShortcut.Descriptor>} keys
    117      * @param {function(!Event=):boolean} handler
    118      */
    119     _registerShortcuts: function(keys, handler)
    120     {
    121         for (var i = 0; i < keys.length; ++i)
    122             this._shortcuts[keys[i].key] = handler;
    123     },
    124 
    125     _handleKeyDown: function(event)
    126     {
    127         var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
    128         var handler = this._shortcuts[shortcutKey];
    129         if (handler && handler())
    130             event.consume(true);
    131     },
    132 
    133     wasShown: function()
    134     {
    135         WebInspector.VBox.prototype.wasShown.call(this);
    136         WebInspector.context.setFlavor(WebInspector.SourcesView, this);
    137     },
    138 
    139     willHide: function()
    140     {
    141         WebInspector.context.setFlavor(WebInspector.SourcesView, null);
    142         WebInspector.VBox.prototype.willHide.call(this);
    143     },
    144 
    145     /**
    146      * @return {!Element}
    147      */
    148     statusBarContainerElement: function()
    149     {
    150         return this._statusBarContainerElement;
    151     },
    152 
    153     /**
    154      * @return {!Element}
    155      */
    156     defaultFocusedElement: function()
    157     {
    158         return this._editorContainer.view.defaultFocusedElement();
    159     },
    160 
    161     /**
    162      * @return {!WebInspector.SearchableView}
    163      */
    164     searchableView: function()
    165     {
    166         return this._searchableView;
    167     },
    168 
    169     /**
    170      * @return {!WebInspector.View}
    171      */
    172     visibleView: function()
    173     {
    174         return this._editorContainer.visibleView;
    175     },
    176 
    177     /**
    178      * @return {?WebInspector.SourceFrame}
    179      */
    180     currentSourceFrame: function()
    181     {
    182         var view = this.visibleView();
    183         if (!(view instanceof WebInspector.SourceFrame))
    184             return null;
    185         return /** @type {!WebInspector.SourceFrame} */ (view);
    186     },
    187 
    188     /**
    189      * @return {?WebInspector.UISourceCode}
    190      */
    191     currentUISourceCode: function()
    192     {
    193         return this._currentUISourceCode;
    194     },
    195 
    196     /**
    197      * @param {!Event=} event
    198      */
    199     _onCloseEditorTab: function(event)
    200     {
    201         var uiSourceCode = this.currentUISourceCode();
    202         if (!uiSourceCode)
    203             return false;
    204         this._editorContainer.closeFile(uiSourceCode);
    205         return true;
    206     },
    207 
    208     /**
    209      * @param {!Event=} event
    210      */
    211     _onJumpToPreviousLocation: function(event)
    212     {
    213         this._historyManager.rollback();
    214         return true;
    215     },
    216 
    217     /**
    218      * @param {!Event=} event
    219      */
    220     _onJumpToNextLocation: function(event)
    221     {
    222         this._historyManager.rollover();
    223         return true;
    224     },
    225 
    226     /**
    227      * @param {!WebInspector.Event} event
    228      */
    229     _uiSourceCodeAdded: function(event)
    230     {
    231         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
    232         this._addUISourceCode(uiSourceCode);
    233     },
    234 
    235     /**
    236      * @param {!WebInspector.UISourceCode} uiSourceCode
    237      */
    238     _addUISourceCode: function(uiSourceCode)
    239     {
    240         if (uiSourceCode.project().isServiceProject())
    241             return;
    242         this._editorContainer.addUISourceCode(uiSourceCode);
    243         // Replace debugger script-based uiSourceCode with a network-based one.
    244         var currentUISourceCode = this._currentUISourceCode;
    245         if (currentUISourceCode && currentUISourceCode.project().isServiceProject() && currentUISourceCode !== uiSourceCode && currentUISourceCode.url === uiSourceCode.url) {
    246             this._showFile(uiSourceCode);
    247             this._editorContainer.removeUISourceCode(currentUISourceCode);
    248         }
    249     },
    250 
    251     _uiSourceCodeRemoved: function(event)
    252     {
    253         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
    254         this._removeUISourceCodes([uiSourceCode]);
    255     },
    256 
    257     /**
    258      * @param {!Array.<!WebInspector.UISourceCode>} uiSourceCodes
    259      */
    260     _removeUISourceCodes: function(uiSourceCodes)
    261     {
    262         this._editorContainer.removeUISourceCodes(uiSourceCodes);
    263         for (var i = 0; i < uiSourceCodes.length; ++i) {
    264             this._removeSourceFrame(uiSourceCodes[i]);
    265             this._historyManager.removeHistoryForSourceCode(uiSourceCodes[i]);
    266         }
    267     },
    268 
    269     _projectRemoved: function(event)
    270     {
    271         var project = event.data;
    272         var uiSourceCodes = project.uiSourceCodes();
    273         this._removeUISourceCodes(uiSourceCodes);
    274         if (project.type() === WebInspector.projectTypes.Network)
    275             this._editorContainer.reset();
    276     },
    277 
    278     _updateScriptViewStatusBarItems: function()
    279     {
    280         this._scriptViewStatusBarItemsContainer.removeChildren();
    281         this._scriptViewStatusBarTextContainer.removeChildren();
    282         var sourceFrame = this.currentSourceFrame();
    283         if (!sourceFrame)
    284             return;
    285 
    286         var statusBarItems = sourceFrame.statusBarItems() || [];
    287         for (var i = 0; i < statusBarItems.length; ++i)
    288             this._scriptViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
    289         var statusBarText = sourceFrame.statusBarText();
    290         if (statusBarText)
    291             this._scriptViewStatusBarTextContainer.appendChild(statusBarText);
    292     },
    293 
    294     /**
    295      * @param {!WebInspector.UISourceCode} uiSourceCode
    296      * @param {number=} lineNumber 0-based
    297      * @param {number=} columnNumber
    298      * @param {boolean=} omitFocus
    299      * @param {boolean=} omitHighlight
    300      */
    301     showSourceLocation: function(uiSourceCode, lineNumber, columnNumber, omitFocus, omitHighlight)
    302     {
    303         this._historyManager.updateCurrentState();
    304         var sourceFrame = this._showFile(uiSourceCode);
    305         if (typeof lineNumber === "number")
    306             sourceFrame.revealPosition(lineNumber, columnNumber, !omitHighlight);
    307         this._historyManager.pushNewState();
    308         if (!omitFocus)
    309             sourceFrame.focus();
    310         WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
    311             action: WebInspector.UserMetrics.UserActionNames.OpenSourceLink,
    312             url: uiSourceCode.originURL(),
    313             lineNumber: lineNumber
    314         });
    315     },
    316 
    317     /**
    318      * @param {!WebInspector.UISourceCode} uiSourceCode
    319      * @return {!WebInspector.SourceFrame}
    320      */
    321     _showFile: function(uiSourceCode)
    322     {
    323         var sourceFrame = this._getOrCreateSourceFrame(uiSourceCode);
    324         if (this._currentUISourceCode === uiSourceCode)
    325             return sourceFrame;
    326 
    327         this._currentUISourceCode = uiSourceCode;
    328         this._editorContainer.showFile(uiSourceCode);
    329         this._updateScriptViewStatusBarItems();
    330         return sourceFrame;
    331     },
    332 
    333     /**
    334      * @param {!WebInspector.UISourceCode} uiSourceCode
    335      * @return {!WebInspector.UISourceCodeFrame}
    336      */
    337     _createSourceFrame: function(uiSourceCode)
    338     {
    339         var sourceFrame;
    340         switch (uiSourceCode.contentType()) {
    341         case WebInspector.resourceTypes.Script:
    342             sourceFrame = new WebInspector.JavaScriptSourceFrame(this._sourcesPanel, uiSourceCode);
    343             break;
    344         case WebInspector.resourceTypes.Document:
    345             sourceFrame = new WebInspector.JavaScriptSourceFrame(this._sourcesPanel, uiSourceCode);
    346             break;
    347         case WebInspector.resourceTypes.Stylesheet:
    348             sourceFrame = new WebInspector.CSSSourceFrame(uiSourceCode);
    349             break;
    350         default:
    351             sourceFrame = new WebInspector.UISourceCodeFrame(uiSourceCode);
    352         break;
    353         }
    354         sourceFrame.setHighlighterType(uiSourceCode.highlighterType());
    355         this._sourceFramesByUISourceCode.set(uiSourceCode, sourceFrame);
    356         this._historyManager.trackSourceFrameCursorJumps(sourceFrame);
    357         return sourceFrame;
    358     },
    359 
    360     /**
    361      * @param {!WebInspector.UISourceCode} uiSourceCode
    362      * @return {!WebInspector.UISourceCodeFrame}
    363      */
    364     _getOrCreateSourceFrame: function(uiSourceCode)
    365     {
    366         return this._sourceFramesByUISourceCode.get(uiSourceCode) || this._createSourceFrame(uiSourceCode);
    367     },
    368 
    369     /**
    370      * @param {!WebInspector.SourceFrame} sourceFrame
    371      * @param {!WebInspector.UISourceCode} uiSourceCode
    372      * @return {boolean}
    373      */
    374     _sourceFrameMatchesUISourceCode: function(sourceFrame, uiSourceCode)
    375     {
    376         switch (uiSourceCode.contentType()) {
    377         case WebInspector.resourceTypes.Script:
    378         case WebInspector.resourceTypes.Document:
    379             return sourceFrame instanceof WebInspector.JavaScriptSourceFrame;
    380         case WebInspector.resourceTypes.Stylesheet:
    381             return sourceFrame instanceof WebInspector.CSSSourceFrame;
    382         default:
    383             return !(sourceFrame instanceof WebInspector.JavaScriptSourceFrame);
    384         }
    385     },
    386 
    387     /**
    388      * @param {!WebInspector.UISourceCode} uiSourceCode
    389      */
    390     _recreateSourceFrameIfNeeded: function(uiSourceCode)
    391     {
    392         var oldSourceFrame = this._sourceFramesByUISourceCode.get(uiSourceCode);
    393         if (!oldSourceFrame)
    394             return;
    395         if (this._sourceFrameMatchesUISourceCode(oldSourceFrame, uiSourceCode)) {
    396             oldSourceFrame.setHighlighterType(uiSourceCode.highlighterType());
    397         } else {
    398             this._editorContainer.removeUISourceCode(uiSourceCode);
    399             this._removeSourceFrame(uiSourceCode);
    400         }
    401     },
    402 
    403     /**
    404      * @param {!WebInspector.UISourceCode} uiSourceCode
    405      * @return {!WebInspector.UISourceCodeFrame}
    406      */
    407     viewForFile: function(uiSourceCode)
    408     {
    409         return this._getOrCreateSourceFrame(uiSourceCode);
    410     },
    411 
    412     /**
    413      * @param {!WebInspector.UISourceCode} uiSourceCode
    414      */
    415     _removeSourceFrame: function(uiSourceCode)
    416     {
    417         var sourceFrame = this._sourceFramesByUISourceCode.get(uiSourceCode);
    418         if (!sourceFrame)
    419             return;
    420         this._sourceFramesByUISourceCode.remove(uiSourceCode);
    421         sourceFrame.dispose();
    422     },
    423 
    424     clearCurrentExecutionLine: function()
    425     {
    426         if (this._executionSourceFrame)
    427             this._executionSourceFrame.clearExecutionLine();
    428         delete this._executionSourceFrame;
    429     },
    430 
    431     setExecutionLine: function(uiLocation)
    432     {
    433         var sourceFrame = this._getOrCreateSourceFrame(uiLocation.uiSourceCode);
    434         sourceFrame.setExecutionLine(uiLocation.lineNumber);
    435         this._executionSourceFrame = sourceFrame;
    436     },
    437 
    438     _editorClosed: function(event)
    439     {
    440         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
    441         this._historyManager.removeHistoryForSourceCode(uiSourceCode);
    442 
    443         var wasSelected = false;
    444         if (this._currentUISourceCode === uiSourceCode) {
    445             delete this._currentUISourceCode;
    446             wasSelected = true;
    447         }
    448 
    449         // SourcesNavigator does not need to update on EditorClosed.
    450         this._updateScriptViewStatusBarItems();
    451         this._searchableView.resetSearch();
    452 
    453         var data = {};
    454         data.uiSourceCode = uiSourceCode;
    455         data.wasSelected = wasSelected;
    456         this.dispatchEventToListeners(WebInspector.SourcesView.Events.EditorClosed, data);
    457     },
    458 
    459     _editorSelected: function(event)
    460     {
    461         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data.currentFile);
    462         var shouldUseHistoryManager = uiSourceCode !== this._currentUISourceCode && event.data.userGesture;
    463         if (shouldUseHistoryManager)
    464             this._historyManager.updateCurrentState();
    465         var sourceFrame = this._showFile(uiSourceCode);
    466         if (shouldUseHistoryManager)
    467             this._historyManager.pushNewState();
    468 
    469         this._searchableView.setReplaceable(!!sourceFrame && sourceFrame.canEditSource());
    470         this._searchableView.resetSearch();
    471 
    472         this.dispatchEventToListeners(WebInspector.SourcesView.Events.EditorSelected, uiSourceCode);
    473     },
    474 
    475     /**
    476      * @param {!WebInspector.UISourceCode} uiSourceCode
    477      */
    478     sourceRenamed: function(uiSourceCode)
    479     {
    480         this._recreateSourceFrameIfNeeded(uiSourceCode);
    481     },
    482 
    483     searchCanceled: function()
    484     {
    485         if (this._searchView)
    486             this._searchView.searchCanceled();
    487 
    488         delete this._searchView;
    489         delete this._searchQuery;
    490     },
    491 
    492     /**
    493      * @param {string} query
    494      * @param {boolean} shouldJump
    495      * @param {boolean=} jumpBackwards
    496      */
    497     performSearch: function(query, shouldJump, jumpBackwards)
    498     {
    499         this._searchableView.updateSearchMatchesCount(0);
    500 
    501         var sourceFrame = this.currentSourceFrame();
    502         if (!sourceFrame)
    503             return;
    504 
    505         this._searchView = sourceFrame;
    506         this._searchQuery = query;
    507 
    508         /**
    509          * @param {!WebInspector.View} view
    510          * @param {number} searchMatches
    511          * @this {WebInspector.SourcesView}
    512          */
    513         function finishedCallback(view, searchMatches)
    514         {
    515             if (!searchMatches)
    516                 return;
    517 
    518             this._searchableView.updateSearchMatchesCount(searchMatches);
    519         }
    520 
    521         /**
    522          * @param {number} currentMatchIndex
    523          * @this {WebInspector.SourcesView}
    524          */
    525         function currentMatchChanged(currentMatchIndex)
    526         {
    527             this._searchableView.updateCurrentMatchIndex(currentMatchIndex);
    528         }
    529 
    530         /**
    531          * @this {WebInspector.SourcesView}
    532          */
    533         function searchResultsChanged()
    534         {
    535             this.performSearch(query, false, false);
    536         }
    537 
    538         this._searchView.performSearch(query, shouldJump, !!jumpBackwards, finishedCallback.bind(this), currentMatchChanged.bind(this), searchResultsChanged.bind(this));
    539     },
    540 
    541     jumpToNextSearchResult: function()
    542     {
    543         if (!this._searchView)
    544             return;
    545 
    546         if (this._searchView !== this.currentSourceFrame()) {
    547             this.performSearch(this._searchQuery, true);
    548             return;
    549         }
    550 
    551         this._searchView.jumpToNextSearchResult();
    552     },
    553 
    554     jumpToPreviousSearchResult: function()
    555     {
    556         if (!this._searchView)
    557             return;
    558 
    559         if (this._searchView !== this.currentSourceFrame()) {
    560             this.performSearch(this._searchQuery, true);
    561             if (this._searchView)
    562                 this._searchView.jumpToLastSearchResult();
    563             return;
    564         }
    565 
    566         this._searchView.jumpToPreviousSearchResult();
    567     },
    568 
    569     /**
    570      * @param {string} text
    571      */
    572     replaceSelectionWith: function(text)
    573     {
    574         var sourceFrame = this.currentSourceFrame();
    575         if (!sourceFrame) {
    576             console.assert(sourceFrame);
    577             return;
    578         }
    579         sourceFrame.replaceSelectionWith(text);
    580     },
    581 
    582     /**
    583      * @param {string} query
    584      * @param {string} text
    585      */
    586     replaceAllWith: function(query, text)
    587     {
    588         var sourceFrame = this.currentSourceFrame();
    589         if (!sourceFrame) {
    590             console.assert(sourceFrame);
    591             return;
    592         }
    593         sourceFrame.replaceAllWith(query, text);
    594     },
    595 
    596     /**
    597      * @param {!Event=} event
    598      * @return {boolean}
    599      */
    600     _showOutlineDialog: function(event)
    601     {
    602         var uiSourceCode = this._editorContainer.currentFile();
    603         if (!uiSourceCode)
    604             return false;
    605 
    606         switch (uiSourceCode.contentType()) {
    607         case WebInspector.resourceTypes.Document:
    608         case WebInspector.resourceTypes.Script:
    609             WebInspector.JavaScriptOutlineDialog.show(this, uiSourceCode, this.showSourceLocation.bind(this, uiSourceCode));
    610             return true;
    611         case WebInspector.resourceTypes.Stylesheet:
    612             WebInspector.StyleSheetOutlineDialog.show(this, uiSourceCode, this.showSourceLocation.bind(this, uiSourceCode));
    613             return true;
    614         default:
    615             // We don't want default browser shortcut to be executed, so pretend to handle this event.
    616             return true;
    617         }
    618     },
    619 
    620     /**
    621      * @param {string=} query
    622      */
    623     showOpenResourceDialog: function(query)
    624     {
    625         var uiSourceCodes = this._editorContainer.historyUISourceCodes();
    626         /** @type {!Map.<!WebInspector.UISourceCode, number>} */
    627         var defaultScores = new Map();
    628         for (var i = 1; i < uiSourceCodes.length; ++i) // Skip current element
    629             defaultScores.set(uiSourceCodes[i], uiSourceCodes.length - i);
    630         WebInspector.OpenResourceDialog.show(this, this.element, query, defaultScores);
    631     },
    632 
    633     /**
    634      * @param {!Event=} event
    635      * @return {boolean}
    636      */
    637     _showGoToLineDialog: function(event)
    638     {
    639         if (this._currentUISourceCode)
    640             this.showOpenResourceDialog(":");
    641         return true;
    642     },
    643 
    644     /**
    645      * @return {boolean}
    646      */
    647     _save: function()
    648     {
    649         this._saveSourceFrame(this.currentSourceFrame());
    650         return true;
    651     },
    652 
    653     /**
    654      * @return {boolean}
    655      */
    656     _saveAll: function()
    657     {
    658         var sourceFrames = this._editorContainer.fileViews();
    659         sourceFrames.forEach(this._saveSourceFrame.bind(this));
    660         return true;
    661     },
    662 
    663     /**
    664      * @param {?WebInspector.SourceFrame} sourceFrame
    665      */
    666     _saveSourceFrame: function(sourceFrame)
    667     {
    668         if (!sourceFrame)
    669             return;
    670         if (!(sourceFrame instanceof WebInspector.UISourceCodeFrame))
    671             return;
    672         var uiSourceCodeFrame = /** @type {!WebInspector.UISourceCodeFrame} */ (sourceFrame);
    673         uiSourceCodeFrame.commitEditing();
    674     },
    675     /**
    676      * @return {boolean}
    677      */
    678     _toggleBreakpoint: function()
    679     {
    680         var sourceFrame = this.currentSourceFrame();
    681         if (!sourceFrame)
    682             return false;
    683 
    684         if (sourceFrame instanceof WebInspector.JavaScriptSourceFrame) {
    685             var javaScriptSourceFrame = /** @type {!WebInspector.JavaScriptSourceFrame} */ (sourceFrame);
    686             javaScriptSourceFrame.toggleBreakpointOnCurrentLine();
    687             return true;
    688         }
    689         return false;
    690     },
    691 
    692     /**
    693      * @param {boolean} active
    694      */
    695     toggleBreakpointsActiveState: function(active)
    696     {
    697         this._editorContainer.view.element.classList.toggle("breakpoints-deactivated", !active);
    698     },
    699 
    700     __proto__: WebInspector.VBox.prototype
    701 }
    702 
    703 /**
    704  * @interface
    705  */
    706 WebInspector.SourcesView.EditorAction = function()
    707 {
    708 }
    709 
    710 WebInspector.SourcesView.EditorAction.prototype = {
    711     /**
    712      * @param {!WebInspector.SourcesView} sourcesView
    713      * @return {!Element}
    714      */
    715     button: function(sourcesView) { }
    716 }
    717 
    718 /**
    719  * @constructor
    720  * @implements {WebInspector.ActionDelegate}
    721  */
    722 WebInspector.SourcesView.SwitchFileActionDelegate = function()
    723 {
    724 }
    725 
    726 /**
    727  * @param {!WebInspector.UISourceCode} currentUISourceCode
    728  * @return {?WebInspector.UISourceCode}
    729  */
    730 WebInspector.SourcesView.SwitchFileActionDelegate._nextFile = function(currentUISourceCode)
    731 {
    732     /**
    733      * @param {string} name
    734      * @return {string}
    735      */
    736     function fileNamePrefix(name)
    737     {
    738         var lastDotIndex = name.lastIndexOf(".");
    739         var namePrefix = name.substr(0, lastDotIndex !== -1 ? lastDotIndex : name.length);
    740         return namePrefix.toLowerCase();
    741     }
    742 
    743     var uiSourceCodes = currentUISourceCode.project().uiSourceCodes();
    744     var candidates = [];
    745     var path = currentUISourceCode.parentPath();
    746     var name = currentUISourceCode.name();
    747     var namePrefix = fileNamePrefix(name);
    748     for (var i = 0; i < uiSourceCodes.length; ++i) {
    749         var uiSourceCode = uiSourceCodes[i];
    750         if (path !== uiSourceCode.parentPath())
    751             continue;
    752         if (fileNamePrefix(uiSourceCode.name()) === namePrefix)
    753             candidates.push(uiSourceCode.name());
    754     }
    755     candidates.sort(String.naturalOrderComparator);
    756     var index = mod(candidates.indexOf(name) + 1, candidates.length);
    757     var fullPath = (path ? path + "/" : "") + candidates[index];
    758     var nextUISourceCode = currentUISourceCode.project().uiSourceCode(fullPath);
    759     return nextUISourceCode !== currentUISourceCode ? nextUISourceCode : null;
    760 },
    761 
    762 
    763 WebInspector.SourcesView.SwitchFileActionDelegate.prototype = {
    764     /**
    765      * @return {boolean}
    766      */
    767     handleAction: function()
    768     {
    769         var sourcesView = WebInspector.context.flavor(WebInspector.SourcesView);
    770         if (!sourcesView)
    771             return false;
    772         var currentUISourceCode = sourcesView.currentUISourceCode();
    773         if (!currentUISourceCode)
    774             return true;
    775         var nextUISourceCode = WebInspector.SourcesView.SwitchFileActionDelegate._nextFile(currentUISourceCode);
    776         if (!nextUISourceCode)
    777             return true;
    778         sourcesView.showSourceLocation(nextUISourceCode);
    779         return true;
    780     }
    781 }
    782