Home | History | Annotate | Download | only in sources
      1 /*
      2  * Copyright (C) 2011 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  * 1. Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *
     11  * 2. Redistributions in binary form must reproduce the above
     12  * copyright notice, this list of conditions and the following disclaimer
     13  * in the documentation and/or other materials provided with the
     14  * distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS
     17  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     18  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     19  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC.
     20  * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     21  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     22  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 /**
     30  * @interface
     31  */
     32 WebInspector.TabbedEditorContainerDelegate = function() { }
     33 
     34 WebInspector.TabbedEditorContainerDelegate.prototype = {
     35     /**
     36      * @param {!WebInspector.UISourceCode} uiSourceCode
     37      * @return {!WebInspector.SourceFrame}
     38      */
     39     viewForFile: function(uiSourceCode) { },
     40 }
     41 
     42 /**
     43  * @constructor
     44  * @extends {WebInspector.Object}
     45  * @param {!WebInspector.TabbedEditorContainerDelegate} delegate
     46  * @param {string} settingName
     47  * @param {string} placeholderText
     48  */
     49 WebInspector.TabbedEditorContainer = function(delegate, settingName, placeholderText)
     50 {
     51     WebInspector.Object.call(this);
     52     this._delegate = delegate;
     53 
     54     this._tabbedPane = new WebInspector.TabbedPane();
     55     this._tabbedPane.setPlaceholderText(placeholderText);
     56     this._tabbedPane.setTabDelegate(new WebInspector.EditorContainerTabDelegate(this));
     57 
     58     this._tabbedPane.closeableTabs = true;
     59     this._tabbedPane.element.id = "sources-editor-container-tabbed-pane";
     60 
     61     this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabClosed, this._tabClosed, this);
     62     this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
     63 
     64     this._tabIds = new Map();
     65     this._files = {};
     66 
     67     this._previouslyViewedFilesSetting = WebInspector.settings.createSetting(settingName, []);
     68     this._history = WebInspector.TabbedEditorContainer.History.fromObject(this._previouslyViewedFilesSetting.get());
     69 }
     70 
     71 WebInspector.TabbedEditorContainer.Events = {
     72     EditorSelected: "EditorSelected",
     73     EditorClosed: "EditorClosed"
     74 }
     75 
     76 WebInspector.TabbedEditorContainer._tabId = 0;
     77 
     78 WebInspector.TabbedEditorContainer.maximalPreviouslyViewedFilesCount = 30;
     79 
     80 WebInspector.TabbedEditorContainer.prototype = {
     81     /**
     82      * @return {!WebInspector.View}
     83      */
     84     get view()
     85     {
     86         return this._tabbedPane;
     87     },
     88 
     89     /**
     90      * @type {!WebInspector.SourceFrame}
     91      */
     92     get visibleView()
     93     {
     94         return this._tabbedPane.visibleView;
     95     },
     96 
     97     /**
     98      * @return {!Array.<!WebInspector.SourceFrame>}
     99      */
    100     fileViews: function()
    101     {
    102         return /** @type {!Array.<!WebInspector.SourceFrame>} */ (this._tabbedPane.tabViews());
    103     },
    104 
    105     /**
    106      * @param {!Element} parentElement
    107      */
    108     show: function(parentElement)
    109     {
    110         this._tabbedPane.show(parentElement);
    111     },
    112 
    113     /**
    114      * @param {!WebInspector.UISourceCode} uiSourceCode
    115      */
    116     showFile: function(uiSourceCode)
    117     {
    118         this._innerShowFile(uiSourceCode, true);
    119     },
    120 
    121     /**
    122      * @param {!WebInspector.UISourceCode} uiSourceCode
    123      */
    124     closeFile: function(uiSourceCode)
    125     {
    126         var tabId = this._tabIds.get(uiSourceCode);
    127         if (!tabId)
    128             return;
    129         this._closeTabs([tabId]);
    130     },
    131 
    132     /**
    133      * @return {!Array.<!WebInspector.UISourceCode>}
    134      */
    135     historyUISourceCodes: function()
    136     {
    137         // FIXME: there should be a way to fetch UISourceCode for its uri.
    138         var uriToUISourceCode = {};
    139         for (var id in this._files) {
    140             var uiSourceCode = this._files[id];
    141             uriToUISourceCode[uiSourceCode.uri()] = uiSourceCode;
    142         }
    143 
    144         var result = [];
    145         var uris = this._history._urls();
    146         for (var i = 0; i < uris.length; ++i) {
    147             var uiSourceCode = uriToUISourceCode[uris[i]];
    148             if (uiSourceCode)
    149                 result.push(uiSourceCode);
    150         }
    151         return result;
    152     },
    153 
    154     _addViewListeners: function()
    155     {
    156         if (!this._currentView)
    157             return;
    158         this._currentView.addEventListener(WebInspector.SourceFrame.Events.ScrollChanged, this._scrollChanged, this);
    159         this._currentView.addEventListener(WebInspector.SourceFrame.Events.SelectionChanged, this._selectionChanged, this);
    160     },
    161 
    162     _removeViewListeners: function()
    163     {
    164         if (!this._currentView)
    165             return;
    166         this._currentView.removeEventListener(WebInspector.SourceFrame.Events.ScrollChanged, this._scrollChanged, this);
    167         this._currentView.removeEventListener(WebInspector.SourceFrame.Events.SelectionChanged, this._selectionChanged, this);
    168     },
    169 
    170     /**
    171      * @param {!WebInspector.Event} event
    172      */
    173     _scrollChanged: function(event)
    174     {
    175         var lineNumber = /** @type {number} */ (event.data);
    176         this._history.updateScrollLineNumber(this._currentFile.uri(), lineNumber);
    177         this._history.save(this._previouslyViewedFilesSetting);
    178     },
    179 
    180     /**
    181      * @param {!WebInspector.Event} event
    182      */
    183     _selectionChanged: function(event)
    184     {
    185         var range = /** @type {!WebInspector.TextRange} */ (event.data);
    186         this._history.updateSelectionRange(this._currentFile.uri(), range);
    187         this._history.save(this._previouslyViewedFilesSetting);
    188     },
    189 
    190     /**
    191      * @param {!WebInspector.UISourceCode} uiSourceCode
    192      * @param {boolean=} userGesture
    193      */
    194     _innerShowFile: function(uiSourceCode, userGesture)
    195     {
    196         if (this._currentFile === uiSourceCode)
    197             return;
    198 
    199         this._removeViewListeners();
    200         this._currentFile = uiSourceCode;
    201 
    202         var tabId = this._tabIds.get(uiSourceCode) || this._appendFileTab(uiSourceCode, userGesture);
    203 
    204         this._tabbedPane.selectTab(tabId, userGesture);
    205         if (userGesture)
    206             this._editorSelectedByUserAction();
    207 
    208         this._currentView = this.visibleView;
    209         this._addViewListeners();
    210 
    211         var eventData = { currentFile: this._currentFile, userGesture: userGesture };
    212         this.dispatchEventToListeners(WebInspector.TabbedEditorContainer.Events.EditorSelected, eventData);
    213     },
    214 
    215     /**
    216      * @param {!WebInspector.UISourceCode} uiSourceCode
    217      * @return {string}
    218      */
    219     _titleForFile: function(uiSourceCode)
    220     {
    221         var maxDisplayNameLength = 30;
    222         var title = uiSourceCode.displayName(true).trimMiddle(maxDisplayNameLength);
    223         if (uiSourceCode.isDirty() || uiSourceCode.hasUnsavedCommittedChanges())
    224             title += "*";
    225         return title;
    226     },
    227 
    228     /**
    229      * @param {string} id
    230      * @param {string} nextTabId
    231      */
    232     _maybeCloseTab: function(id, nextTabId)
    233     {
    234         var uiSourceCode = this._files[id];
    235         var shouldPrompt = uiSourceCode.isDirty() && uiSourceCode.project().canSetFileContent();
    236         // FIXME: this should be replaced with common Save/Discard/Cancel dialog.
    237         if (!shouldPrompt || confirm(WebInspector.UIString("Are you sure you want to close unsaved file: %s?", uiSourceCode.name()))) {
    238             uiSourceCode.resetWorkingCopy();
    239             if (nextTabId)
    240                 this._tabbedPane.selectTab(nextTabId, true);
    241             this._tabbedPane.closeTab(id, true);
    242             return true;
    243         }
    244         return false;
    245     },
    246 
    247     /**
    248      * @param {!Array.<string>} ids
    249      */
    250     _closeTabs: function(ids)
    251     {
    252         var dirtyTabs = [];
    253         var cleanTabs = [];
    254         for (var i = 0; i < ids.length; ++i) {
    255             var id = ids[i];
    256             var uiSourceCode = this._files[id];
    257             if (uiSourceCode.isDirty())
    258                 dirtyTabs.push(id);
    259             else
    260                 cleanTabs.push(id);
    261         }
    262         if (dirtyTabs.length)
    263             this._tabbedPane.selectTab(dirtyTabs[0], true);
    264         this._tabbedPane.closeTabs(cleanTabs, true);
    265         for (var i = 0; i < dirtyTabs.length; ++i) {
    266             var nextTabId = i + 1 < dirtyTabs.length ? dirtyTabs[i + 1] : null;
    267             if (!this._maybeCloseTab(dirtyTabs[i], nextTabId))
    268                 break;
    269         }
    270     },
    271 
    272     /**
    273      * @param {!WebInspector.UISourceCode} uiSourceCode
    274      */
    275     addUISourceCode: function(uiSourceCode)
    276     {
    277         var uri = uiSourceCode.uri();
    278         if (this._userSelectedFiles)
    279             return;
    280 
    281         var index = this._history.index(uri)
    282         if (index === -1)
    283             return;
    284 
    285         var tabId = this._tabIds.get(uiSourceCode) || this._appendFileTab(uiSourceCode, false);
    286 
    287         // Select tab if this file was the last to be shown.
    288         if (!index) {
    289             this._innerShowFile(uiSourceCode, false);
    290             return;
    291         }
    292 
    293         if (!this._currentFile)
    294             return;
    295         var currentProjectType = this._currentFile.project().type();
    296         var addedProjectType = uiSourceCode.project().type();
    297         var snippetsProjectType = WebInspector.projectTypes.Snippets;
    298         if (this._history.index(this._currentFile.uri()) && currentProjectType === snippetsProjectType && addedProjectType !== snippetsProjectType)
    299             this._innerShowFile(uiSourceCode, false);
    300     },
    301 
    302     /**
    303      * @param {!WebInspector.UISourceCode} uiSourceCode
    304      */
    305     removeUISourceCode: function(uiSourceCode)
    306     {
    307         this.removeUISourceCodes([uiSourceCode]);
    308     },
    309 
    310     /**
    311      * @param {!Array.<!WebInspector.UISourceCode>} uiSourceCodes
    312      */
    313     removeUISourceCodes: function(uiSourceCodes)
    314     {
    315         var tabIds = [];
    316         for (var i = 0; i < uiSourceCodes.length; ++i) {
    317             var uiSourceCode = uiSourceCodes[i];
    318             var tabId = this._tabIds.get(uiSourceCode);
    319             if (tabId)
    320                 tabIds.push(tabId);
    321         }
    322         this._tabbedPane.closeTabs(tabIds);
    323     },
    324 
    325     /**
    326      * @param {!WebInspector.UISourceCode} uiSourceCode
    327      */
    328     _editorClosedByUserAction: function(uiSourceCode)
    329     {
    330         this._userSelectedFiles = true;
    331         this._history.remove(uiSourceCode.uri());
    332         this._updateHistory();
    333     },
    334 
    335     _editorSelectedByUserAction: function()
    336     {
    337         this._userSelectedFiles = true;
    338         this._updateHistory();
    339     },
    340 
    341     _updateHistory: function()
    342     {
    343         var tabIds = this._tabbedPane.lastOpenedTabIds(WebInspector.TabbedEditorContainer.maximalPreviouslyViewedFilesCount);
    344 
    345         /**
    346          * @param {string} tabId
    347          * @this {WebInspector.TabbedEditorContainer}
    348          */
    349         function tabIdToURI(tabId)
    350         {
    351             return this._files[tabId].uri();
    352         }
    353 
    354         this._history.update(tabIds.map(tabIdToURI.bind(this)));
    355         this._history.save(this._previouslyViewedFilesSetting);
    356     },
    357 
    358     /**
    359      * @param {!WebInspector.UISourceCode} uiSourceCode
    360      * @return {string}
    361      */
    362     _tooltipForFile: function(uiSourceCode)
    363     {
    364         return uiSourceCode.originURL();
    365     },
    366 
    367     /**
    368      * @param {!WebInspector.UISourceCode} uiSourceCode
    369      * @param {boolean=} userGesture
    370      * @return {string}
    371      */
    372     _appendFileTab: function(uiSourceCode, userGesture)
    373     {
    374         var view = this._delegate.viewForFile(uiSourceCode);
    375         var title = this._titleForFile(uiSourceCode);
    376         var tooltip = this._tooltipForFile(uiSourceCode);
    377 
    378         var tabId = this._generateTabId();
    379         this._tabIds.set(uiSourceCode, tabId);
    380         this._files[tabId] = uiSourceCode;
    381 
    382         var savedSelectionRange = this._history.selectionRange(uiSourceCode.uri());
    383         if (savedSelectionRange)
    384             view.setSelection(savedSelectionRange);
    385         var savedScrollLineNumber = this._history.scrollLineNumber(uiSourceCode.uri());
    386         if (savedScrollLineNumber)
    387             view.scrollToLine(savedScrollLineNumber);
    388 
    389         this._tabbedPane.appendTab(tabId, title, view, tooltip, userGesture);
    390 
    391         this._updateFileTitle(uiSourceCode);
    392         this._addUISourceCodeListeners(uiSourceCode);
    393         return tabId;
    394     },
    395 
    396     /**
    397      * @param {!WebInspector.Event} event
    398      */
    399     _tabClosed: function(event)
    400     {
    401         var tabId = /** @type {string} */ (event.data.tabId);
    402         var userGesture = /** @type {boolean} */ (event.data.isUserGesture);
    403 
    404         var uiSourceCode = this._files[tabId];
    405         if (this._currentFile === uiSourceCode) {
    406             this._removeViewListeners();
    407             delete this._currentView;
    408             delete this._currentFile;
    409         }
    410         this._tabIds.remove(uiSourceCode);
    411         delete this._files[tabId];
    412 
    413         this._removeUISourceCodeListeners(uiSourceCode);
    414 
    415         this.dispatchEventToListeners(WebInspector.TabbedEditorContainer.Events.EditorClosed, uiSourceCode);
    416 
    417         if (userGesture)
    418             this._editorClosedByUserAction(uiSourceCode);
    419     },
    420 
    421     /**
    422      * @param {!WebInspector.Event} event
    423      */
    424     _tabSelected: function(event)
    425     {
    426         var tabId = /** @type {string} */ (event.data.tabId);
    427         var userGesture = /** @type {boolean} */ (event.data.isUserGesture);
    428 
    429         var uiSourceCode = this._files[tabId];
    430         this._innerShowFile(uiSourceCode, userGesture);
    431     },
    432 
    433     /**
    434      * @param {!WebInspector.UISourceCode} uiSourceCode
    435      */
    436     _addUISourceCodeListeners: function(uiSourceCode)
    437     {
    438         uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._uiSourceCodeTitleChanged, this);
    439         uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._uiSourceCodeWorkingCopyChanged, this);
    440         uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._uiSourceCodeWorkingCopyCommitted, this);
    441         uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.SavedStateUpdated, this._uiSourceCodeSavedStateUpdated, this);
    442     },
    443 
    444     /**
    445      * @param {!WebInspector.UISourceCode} uiSourceCode
    446      */
    447     _removeUISourceCodeListeners: function(uiSourceCode)
    448     {
    449         uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._uiSourceCodeTitleChanged, this);
    450         uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._uiSourceCodeWorkingCopyChanged, this);
    451         uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._uiSourceCodeWorkingCopyCommitted, this);
    452         uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.SavedStateUpdated, this._uiSourceCodeSavedStateUpdated, this);
    453     },
    454 
    455     /**
    456      * @param {!WebInspector.UISourceCode} uiSourceCode
    457      */
    458     _updateFileTitle: function(uiSourceCode)
    459     {
    460         var tabId = this._tabIds.get(uiSourceCode);
    461         if (tabId) {
    462             var title = this._titleForFile(uiSourceCode);
    463             this._tabbedPane.changeTabTitle(tabId, title);
    464             if (uiSourceCode.hasUnsavedCommittedChanges())
    465                 this._tabbedPane.setTabIcon(tabId, "editor-container-unsaved-committed-changes-icon", WebInspector.UIString("Changes to this file were not saved to file system."));
    466             else
    467                 this._tabbedPane.setTabIcon(tabId, "");
    468         }
    469     },
    470 
    471     _uiSourceCodeTitleChanged: function(event)
    472     {
    473         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.target);
    474         this._updateFileTitle(uiSourceCode);
    475         this._updateHistory();
    476     },
    477 
    478     _uiSourceCodeWorkingCopyChanged: function(event)
    479     {
    480         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.target);
    481         this._updateFileTitle(uiSourceCode);
    482     },
    483 
    484     _uiSourceCodeWorkingCopyCommitted: function(event)
    485     {
    486         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.target);
    487         this._updateFileTitle(uiSourceCode);
    488     },
    489 
    490     _uiSourceCodeSavedStateUpdated: function(event)
    491     {
    492         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.target);
    493         this._updateFileTitle(uiSourceCode);
    494     },
    495 
    496     reset: function()
    497     {
    498         delete this._userSelectedFiles;
    499     },
    500 
    501     /**
    502      * @return {string}
    503      */
    504     _generateTabId: function()
    505     {
    506         return "tab_" + (WebInspector.TabbedEditorContainer._tabId++);
    507     },
    508 
    509     /**
    510      * @return {!WebInspector.UISourceCode} uiSourceCode
    511      */
    512     currentFile: function()
    513     {
    514         return this._currentFile;
    515     },
    516 
    517     __proto__: WebInspector.Object.prototype
    518 }
    519 
    520 /**
    521  * @constructor
    522  * @param {string} url
    523  * @param {!WebInspector.TextRange=} selectionRange
    524  * @param {number=} scrollLineNumber
    525  */
    526 WebInspector.TabbedEditorContainer.HistoryItem = function(url, selectionRange, scrollLineNumber)
    527 {
    528     /** @const */ this.url = url;
    529     /** @const */ this._isSerializable = url.length < WebInspector.TabbedEditorContainer.HistoryItem.serializableUrlLengthLimit;
    530     this.selectionRange = selectionRange;
    531     this.scrollLineNumber = scrollLineNumber;
    532 }
    533 
    534 WebInspector.TabbedEditorContainer.HistoryItem.serializableUrlLengthLimit = 4096;
    535 
    536 /**
    537  * @param {!Object} serializedHistoryItem
    538  * @return {!WebInspector.TabbedEditorContainer.HistoryItem}
    539  */
    540 WebInspector.TabbedEditorContainer.HistoryItem.fromObject = function (serializedHistoryItem)
    541 {
    542     var selectionRange = serializedHistoryItem.selectionRange ? WebInspector.TextRange.fromObject(serializedHistoryItem.selectionRange) : undefined;
    543     return new WebInspector.TabbedEditorContainer.HistoryItem(serializedHistoryItem.url, selectionRange, serializedHistoryItem.scrollLineNumber);
    544 }
    545 
    546 WebInspector.TabbedEditorContainer.HistoryItem.prototype = {
    547     /**
    548      * @return {?Object}
    549      */
    550     serializeToObject: function()
    551     {
    552         if (!this._isSerializable)
    553             return null;
    554         var serializedHistoryItem = {};
    555         serializedHistoryItem.url = this.url;
    556         serializedHistoryItem.selectionRange = this.selectionRange;
    557         serializedHistoryItem.scrollLineNumber = this.scrollLineNumber;
    558         return serializedHistoryItem;
    559     }
    560 }
    561 
    562 /**
    563  * @constructor
    564  * @param {!Array.<!WebInspector.TabbedEditorContainer.HistoryItem>} items
    565  */
    566 WebInspector.TabbedEditorContainer.History = function(items)
    567 {
    568     this._items = items;
    569     this._rebuildItemIndex();
    570 }
    571 
    572 /**
    573  * @param {!Array.<!Object>} serializedHistory
    574  * @return {!WebInspector.TabbedEditorContainer.History}
    575  */
    576 WebInspector.TabbedEditorContainer.History.fromObject = function(serializedHistory)
    577 {
    578     var items = [];
    579     for (var i = 0; i < serializedHistory.length; ++i)
    580         items.push(WebInspector.TabbedEditorContainer.HistoryItem.fromObject(serializedHistory[i]));
    581     return new WebInspector.TabbedEditorContainer.History(items);
    582 }
    583 
    584 WebInspector.TabbedEditorContainer.History.prototype = {
    585     /**
    586      * @param {string} url
    587      * @return {number}
    588      */
    589     index: function(url)
    590     {
    591         var index = this._itemsIndex[url];
    592         if (typeof index === "number")
    593             return index;
    594         return -1;
    595     },
    596 
    597     _rebuildItemIndex: function()
    598     {
    599         this._itemsIndex = {};
    600         for (var i = 0; i < this._items.length; ++i) {
    601             console.assert(!this._itemsIndex.hasOwnProperty(this._items[i].url));
    602             this._itemsIndex[this._items[i].url] = i;
    603         }
    604     },
    605 
    606     /**
    607      * @param {string} url
    608      * @return {!WebInspector.TextRange|undefined}
    609      */
    610     selectionRange: function(url)
    611     {
    612         var index = this.index(url);
    613         return index !== -1 ? this._items[index].selectionRange : undefined;
    614     },
    615 
    616     /**
    617      * @param {string} url
    618      * @param {!WebInspector.TextRange=} selectionRange
    619      */
    620     updateSelectionRange: function(url, selectionRange)
    621     {
    622         if (!selectionRange)
    623             return;
    624         var index = this.index(url);
    625         if (index === -1)
    626             return;
    627         this._items[index].selectionRange = selectionRange;
    628     },
    629 
    630     /**
    631      * @param {string} url
    632      * @return {number|undefined}
    633      */
    634     scrollLineNumber: function(url)
    635     {
    636         var index = this.index(url);
    637         return index !== -1 ? this._items[index].scrollLineNumber : undefined;
    638     },
    639 
    640     /**
    641      * @param {string} url
    642      * @param {number} scrollLineNumber
    643      */
    644     updateScrollLineNumber: function(url, scrollLineNumber)
    645     {
    646         var index = this.index(url);
    647         if (index === -1)
    648             return;
    649         this._items[index].scrollLineNumber = scrollLineNumber;
    650     },
    651 
    652     /**
    653      * @param {!Array.<string>} urls
    654      */
    655     update: function(urls)
    656     {
    657         for (var i = urls.length - 1; i >= 0; --i) {
    658             var index = this.index(urls[i]);
    659             var item;
    660             if (index !== -1) {
    661                 item = this._items[index];
    662                 this._items.splice(index, 1);
    663             } else
    664                 item = new WebInspector.TabbedEditorContainer.HistoryItem(urls[i]);
    665             this._items.unshift(item);
    666             this._rebuildItemIndex();
    667         }
    668     },
    669 
    670     /**
    671      * @param {string} url
    672      */
    673     remove: function(url)
    674     {
    675         var index = this.index(url);
    676         if (index !== -1) {
    677             this._items.splice(index, 1);
    678             this._rebuildItemIndex();
    679         }
    680     },
    681 
    682     /**
    683      * @param {!WebInspector.Setting} setting
    684      */
    685     save: function(setting)
    686     {
    687         setting.set(this._serializeToObject());
    688     },
    689 
    690     /**
    691      * @return {!Array.<!Object>}
    692      */
    693     _serializeToObject: function()
    694     {
    695         var serializedHistory = [];
    696         for (var i = 0; i < this._items.length; ++i) {
    697             var serializedItem = this._items[i].serializeToObject();
    698             if (serializedItem)
    699                 serializedHistory.push(serializedItem);
    700             if (serializedHistory.length === WebInspector.TabbedEditorContainer.maximalPreviouslyViewedFilesCount)
    701                 break;
    702         }
    703         return serializedHistory;
    704     },
    705 
    706 
    707     /**
    708      * @return {!Array.<string>}
    709      */
    710     _urls: function()
    711     {
    712         var result = [];
    713         for (var i = 0; i < this._items.length; ++i)
    714             result.push(this._items[i].url);
    715         return result;
    716     }
    717 }
    718 
    719 /**
    720  * @constructor
    721  * @implements {WebInspector.TabbedPaneTabDelegate}
    722  * @param {!WebInspector.TabbedEditorContainer} editorContainer
    723  */
    724 WebInspector.EditorContainerTabDelegate = function(editorContainer)
    725 {
    726     this._editorContainer = editorContainer;
    727 }
    728 
    729 WebInspector.EditorContainerTabDelegate.prototype = {
    730     /**
    731      * @param {!WebInspector.TabbedPane} tabbedPane
    732      * @param {!Array.<string>} ids
    733      */
    734     closeTabs: function(tabbedPane, ids)
    735     {
    736         this._editorContainer._closeTabs(ids);
    737     }
    738 }
    739