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         if (!this._currentFile)
    288             return;
    289 
    290         // Select tab if this file was the last to be shown.
    291         if (!index) {
    292             this._innerShowFile(uiSourceCode, false);
    293             return;
    294         }
    295 
    296         var currentProjectType = this._currentFile.project().type();
    297         var addedProjectType = uiSourceCode.project().type();
    298         var snippetsProjectType = WebInspector.projectTypes.Snippets;
    299         if (this._history.index(this._currentFile.uri()) && currentProjectType === snippetsProjectType && addedProjectType !== snippetsProjectType)
    300             this._innerShowFile(uiSourceCode, false);
    301     },
    302 
    303     /**
    304      * @param {!WebInspector.UISourceCode} uiSourceCode
    305      */
    306     removeUISourceCode: function(uiSourceCode)
    307     {
    308         this.removeUISourceCodes([uiSourceCode]);
    309     },
    310 
    311     /**
    312      * @param {!Array.<!WebInspector.UISourceCode>} uiSourceCodes
    313      */
    314     removeUISourceCodes: function(uiSourceCodes)
    315     {
    316         var tabIds = [];
    317         for (var i = 0; i < uiSourceCodes.length; ++i) {
    318             var uiSourceCode = uiSourceCodes[i];
    319             var tabId = this._tabIds.get(uiSourceCode);
    320             if (tabId)
    321                 tabIds.push(tabId);
    322         }
    323         this._tabbedPane.closeTabs(tabIds);
    324     },
    325 
    326     /**
    327      * @param {!WebInspector.UISourceCode} uiSourceCode
    328      */
    329     _editorClosedByUserAction: function(uiSourceCode)
    330     {
    331         this._userSelectedFiles = true;
    332         this._history.remove(uiSourceCode.uri());
    333         this._updateHistory();
    334     },
    335 
    336     _editorSelectedByUserAction: function()
    337     {
    338         this._userSelectedFiles = true;
    339         this._updateHistory();
    340     },
    341 
    342     _updateHistory: function()
    343     {
    344         var tabIds = this._tabbedPane.lastOpenedTabIds(WebInspector.TabbedEditorContainer.maximalPreviouslyViewedFilesCount);
    345 
    346         /**
    347          * @param {string} tabId
    348          * @this {WebInspector.TabbedEditorContainer}
    349          */
    350         function tabIdToURI(tabId)
    351         {
    352             return this._files[tabId].uri();
    353         }
    354 
    355         this._history.update(tabIds.map(tabIdToURI.bind(this)));
    356         this._history.save(this._previouslyViewedFilesSetting);
    357     },
    358 
    359     /**
    360      * @param {!WebInspector.UISourceCode} uiSourceCode
    361      * @return {string}
    362      */
    363     _tooltipForFile: function(uiSourceCode)
    364     {
    365         return uiSourceCode.originURL();
    366     },
    367 
    368     /**
    369      * @param {!WebInspector.UISourceCode} uiSourceCode
    370      * @param {boolean=} userGesture
    371      * @return {string}
    372      */
    373     _appendFileTab: function(uiSourceCode, userGesture)
    374     {
    375         var view = this._delegate.viewForFile(uiSourceCode);
    376         var title = this._titleForFile(uiSourceCode);
    377         var tooltip = this._tooltipForFile(uiSourceCode);
    378 
    379         var tabId = this._generateTabId();
    380         this._tabIds.put(uiSourceCode, tabId);
    381         this._files[tabId] = uiSourceCode;
    382 
    383         var savedSelectionRange = this._history.selectionRange(uiSourceCode.uri());
    384         if (savedSelectionRange)
    385             view.setSelection(savedSelectionRange);
    386         var savedScrollLineNumber = this._history.scrollLineNumber(uiSourceCode.uri());
    387         if (savedScrollLineNumber)
    388             view.scrollToLine(savedScrollLineNumber);
    389 
    390         this._tabbedPane.appendTab(tabId, title, view, tooltip, userGesture);
    391 
    392         this._updateFileTitle(uiSourceCode);
    393         this._addUISourceCodeListeners(uiSourceCode);
    394         return tabId;
    395     },
    396 
    397     /**
    398      * @param {!WebInspector.Event} event
    399      */
    400     _tabClosed: function(event)
    401     {
    402         var tabId = /** @type {string} */ (event.data.tabId);
    403         var userGesture = /** @type {boolean} */ (event.data.isUserGesture);
    404 
    405         var uiSourceCode = this._files[tabId];
    406         if (this._currentFile === uiSourceCode) {
    407             this._removeViewListeners();
    408             delete this._currentView;
    409             delete this._currentFile;
    410         }
    411         this._tabIds.remove(uiSourceCode);
    412         delete this._files[tabId];
    413 
    414         this._removeUISourceCodeListeners(uiSourceCode);
    415 
    416         this.dispatchEventToListeners(WebInspector.TabbedEditorContainer.Events.EditorClosed, uiSourceCode);
    417 
    418         if (userGesture)
    419             this._editorClosedByUserAction(uiSourceCode);
    420     },
    421 
    422     /**
    423      * @param {!WebInspector.Event} event
    424      */
    425     _tabSelected: function(event)
    426     {
    427         var tabId = /** @type {string} */ (event.data.tabId);
    428         var userGesture = /** @type {boolean} */ (event.data.isUserGesture);
    429 
    430         var uiSourceCode = this._files[tabId];
    431         this._innerShowFile(uiSourceCode, userGesture);
    432     },
    433 
    434     /**
    435      * @param {!WebInspector.UISourceCode} uiSourceCode
    436      */
    437     _addUISourceCodeListeners: function(uiSourceCode)
    438     {
    439         uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._uiSourceCodeTitleChanged, this);
    440         uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._uiSourceCodeWorkingCopyChanged, this);
    441         uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._uiSourceCodeWorkingCopyCommitted, this);
    442         uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.SavedStateUpdated, this._uiSourceCodeSavedStateUpdated, this);
    443     },
    444 
    445     /**
    446      * @param {!WebInspector.UISourceCode} uiSourceCode
    447      */
    448     _removeUISourceCodeListeners: function(uiSourceCode)
    449     {
    450         uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._uiSourceCodeTitleChanged, this);
    451         uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._uiSourceCodeWorkingCopyChanged, this);
    452         uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._uiSourceCodeWorkingCopyCommitted, this);
    453         uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.SavedStateUpdated, this._uiSourceCodeSavedStateUpdated, this);
    454     },
    455 
    456     /**
    457      * @param {!WebInspector.UISourceCode} uiSourceCode
    458      */
    459     _updateFileTitle: function(uiSourceCode)
    460     {
    461         var tabId = this._tabIds.get(uiSourceCode);
    462         if (tabId) {
    463             var title = this._titleForFile(uiSourceCode);
    464             this._tabbedPane.changeTabTitle(tabId, title);
    465             if (uiSourceCode.hasUnsavedCommittedChanges())
    466                 this._tabbedPane.setTabIcon(tabId, "editor-container-unsaved-committed-changes-icon", WebInspector.UIString("Changes to this file were not saved to file system."));
    467             else
    468                 this._tabbedPane.setTabIcon(tabId, "");
    469         }
    470     },
    471 
    472     _uiSourceCodeTitleChanged: function(event)
    473     {
    474         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.target);
    475         this._updateFileTitle(uiSourceCode);
    476         this._updateHistory();
    477     },
    478 
    479     _uiSourceCodeWorkingCopyChanged: function(event)
    480     {
    481         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.target);
    482         this._updateFileTitle(uiSourceCode);
    483     },
    484 
    485     _uiSourceCodeWorkingCopyCommitted: function(event)
    486     {
    487         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.target);
    488         this._updateFileTitle(uiSourceCode);
    489     },
    490 
    491     _uiSourceCodeSavedStateUpdated: function(event)
    492     {
    493         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.target);
    494         this._updateFileTitle(uiSourceCode);
    495     },
    496 
    497     reset: function()
    498     {
    499         delete this._userSelectedFiles;
    500     },
    501 
    502     /**
    503      * @return {string}
    504      */
    505     _generateTabId: function()
    506     {
    507         return "tab_" + (WebInspector.TabbedEditorContainer._tabId++);
    508     },
    509 
    510     /**
    511      * @return {!WebInspector.UISourceCode} uiSourceCode
    512      */
    513     currentFile: function()
    514     {
    515         return this._currentFile;
    516     },
    517 
    518     __proto__: WebInspector.Object.prototype
    519 }
    520 
    521 /**
    522  * @constructor
    523  * @param {string} url
    524  * @param {!WebInspector.TextRange=} selectionRange
    525  * @param {number=} scrollLineNumber
    526  */
    527 WebInspector.TabbedEditorContainer.HistoryItem = function(url, selectionRange, scrollLineNumber)
    528 {
    529     /** @const */ this.url = url;
    530     /** @const */ this._isSerializable = url.length < WebInspector.TabbedEditorContainer.HistoryItem.serializableUrlLengthLimit;
    531     this.selectionRange = selectionRange;
    532     this.scrollLineNumber = scrollLineNumber;
    533 }
    534 
    535 WebInspector.TabbedEditorContainer.HistoryItem.serializableUrlLengthLimit = 4096;
    536 
    537 /**
    538  * @param {!Object} serializedHistoryItem
    539  * @return {!WebInspector.TabbedEditorContainer.HistoryItem}
    540  */
    541 WebInspector.TabbedEditorContainer.HistoryItem.fromObject = function (serializedHistoryItem)
    542 {
    543     var selectionRange = serializedHistoryItem.selectionRange ? WebInspector.TextRange.fromObject(serializedHistoryItem.selectionRange) : undefined;
    544     return new WebInspector.TabbedEditorContainer.HistoryItem(serializedHistoryItem.url, selectionRange, serializedHistoryItem.scrollLineNumber);
    545 }
    546 
    547 WebInspector.TabbedEditorContainer.HistoryItem.prototype = {
    548     /**
    549      * @return {?Object}
    550      */
    551     serializeToObject: function()
    552     {
    553         if (!this._isSerializable)
    554             return null;
    555         var serializedHistoryItem = {};
    556         serializedHistoryItem.url = this.url;
    557         serializedHistoryItem.selectionRange = this.selectionRange;
    558         serializedHistoryItem.scrollLineNumber = this.scrollLineNumber;
    559         return serializedHistoryItem;
    560     }
    561 }
    562 
    563 /**
    564  * @constructor
    565  * @param {!Array.<!WebInspector.TabbedEditorContainer.HistoryItem>} items
    566  */
    567 WebInspector.TabbedEditorContainer.History = function(items)
    568 {
    569     this._items = items;
    570     this._rebuildItemIndex();
    571 }
    572 
    573 /**
    574  * @param {!Array.<!Object>} serializedHistory
    575  * @return {!WebInspector.TabbedEditorContainer.History}
    576  */
    577 WebInspector.TabbedEditorContainer.History.fromObject = function(serializedHistory)
    578 {
    579     var items = [];
    580     for (var i = 0; i < serializedHistory.length; ++i)
    581         items.push(WebInspector.TabbedEditorContainer.HistoryItem.fromObject(serializedHistory[i]));
    582     return new WebInspector.TabbedEditorContainer.History(items);
    583 }
    584 
    585 WebInspector.TabbedEditorContainer.History.prototype = {
    586     /**
    587      * @param {string} url
    588      * @return {number}
    589      */
    590     index: function(url)
    591     {
    592         var index = this._itemsIndex[url];
    593         if (typeof index === "number")
    594             return index;
    595         return -1;
    596     },
    597 
    598     _rebuildItemIndex: function()
    599     {
    600         this._itemsIndex = {};
    601         for (var i = 0; i < this._items.length; ++i) {
    602             console.assert(!this._itemsIndex.hasOwnProperty(this._items[i].url));
    603             this._itemsIndex[this._items[i].url] = i;
    604         }
    605     },
    606 
    607     /**
    608      * @param {string} url
    609      * @return {!WebInspector.TextRange|undefined}
    610      */
    611     selectionRange: function(url)
    612     {
    613         var index = this.index(url);
    614         return index !== -1 ? this._items[index].selectionRange : undefined;
    615     },
    616 
    617     /**
    618      * @param {string} url
    619      * @param {!WebInspector.TextRange=} selectionRange
    620      */
    621     updateSelectionRange: function(url, selectionRange)
    622     {
    623         if (!selectionRange)
    624             return;
    625         var index = this.index(url);
    626         if (index === -1)
    627             return;
    628         this._items[index].selectionRange = selectionRange;
    629     },
    630 
    631     /**
    632      * @param {string} url
    633      * @return {number|undefined}
    634      */
    635     scrollLineNumber: function(url)
    636     {
    637         var index = this.index(url);
    638         return index !== -1 ? this._items[index].scrollLineNumber : undefined;
    639     },
    640 
    641     /**
    642      * @param {string} url
    643      * @param {number} scrollLineNumber
    644      */
    645     updateScrollLineNumber: function(url, scrollLineNumber)
    646     {
    647         var index = this.index(url);
    648         if (index === -1)
    649             return;
    650         this._items[index].scrollLineNumber = scrollLineNumber;
    651     },
    652 
    653     /**
    654      * @param {!Array.<string>} urls
    655      */
    656     update: function(urls)
    657     {
    658         for (var i = urls.length - 1; i >= 0; --i) {
    659             var index = this.index(urls[i]);
    660             var item;
    661             if (index !== -1) {
    662                 item = this._items[index];
    663                 this._items.splice(index, 1);
    664             } else
    665                 item = new WebInspector.TabbedEditorContainer.HistoryItem(urls[i]);
    666             this._items.unshift(item);
    667             this._rebuildItemIndex();
    668         }
    669     },
    670 
    671     /**
    672      * @param {string} url
    673      */
    674     remove: function(url)
    675     {
    676         var index = this.index(url);
    677         if (index !== -1) {
    678             this._items.splice(index, 1);
    679             this._rebuildItemIndex();
    680         }
    681     },
    682 
    683     /**
    684      * @param {!WebInspector.Setting} setting
    685      */
    686     save: function(setting)
    687     {
    688         setting.set(this._serializeToObject());
    689     },
    690 
    691     /**
    692      * @return {!Array.<!Object>}
    693      */
    694     _serializeToObject: function()
    695     {
    696         var serializedHistory = [];
    697         for (var i = 0; i < this._items.length; ++i) {
    698             var serializedItem = this._items[i].serializeToObject();
    699             if (serializedItem)
    700                 serializedHistory.push(serializedItem);
    701             if (serializedHistory.length === WebInspector.TabbedEditorContainer.maximalPreviouslyViewedFilesCount)
    702                 break;
    703         }
    704         return serializedHistory;
    705     },
    706 
    707 
    708     /**
    709      * @return {!Array.<string>}
    710      */
    711     _urls: function()
    712     {
    713         var result = [];
    714         for (var i = 0; i < this._items.length; ++i)
    715             result.push(this._items[i].url);
    716         return result;
    717     }
    718 }
    719 
    720 /**
    721  * @constructor
    722  * @implements {WebInspector.TabbedPaneTabDelegate}
    723  * @param {!WebInspector.TabbedEditorContainer} editorContainer
    724  */
    725 WebInspector.EditorContainerTabDelegate = function(editorContainer)
    726 {
    727     this._editorContainer = editorContainer;
    728 }
    729 
    730 WebInspector.EditorContainerTabDelegate.prototype = {
    731     /**
    732      * @param {!WebInspector.TabbedPane} tabbedPane
    733      * @param {!Array.<string>} ids
    734      */
    735     closeTabs: function(tabbedPane, ids)
    736     {
    737         this._editorContainer._closeTabs(ids);
    738     }
    739 }
    740