| /* | |
| * Copyright (C) 2011 Google Inc. All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions are | |
| * met: | |
| * | |
| * 1. Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * | |
| * 2. Redistributions in binary form must reproduce the above | |
| * copyright notice, this list of conditions and the following disclaimer | |
| * in the documentation and/or other materials provided with the | |
| * distribution. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS | |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. | |
| * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| /** | |
| * @interface | |
| */ | |
| Sources.TabbedEditorContainerDelegate = function() {}; | |
| Sources.TabbedEditorContainerDelegate.prototype = { | |
| /** | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| * @return {!UI.Widget} | |
| */ | |
| viewForFile(uiSourceCode) {}, | |
| /** | |
| * @param {!Sources.UISourceCodeFrame} sourceFrame | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| */ | |
| recycleUISourceCodeFrame(sourceFrame, uiSourceCode) {}, | |
| }; | |
| /** | |
| * @unrestricted | |
| */ | |
| Sources.TabbedEditorContainer = class extends Common.Object { | |
| /** | |
| * @param {!Sources.TabbedEditorContainerDelegate} delegate | |
| * @param {!Common.Setting} setting | |
| * @param {!Element} placeholderElement | |
| * @param {!Element=} focusedPlaceholderElement | |
| */ | |
| constructor(delegate, setting, placeholderElement, focusedPlaceholderElement) { | |
| super(); | |
| this._delegate = delegate; | |
| this._tabbedPane = new UI.TabbedPane(); | |
| this._tabbedPane.setPlaceholderElement(placeholderElement, focusedPlaceholderElement); | |
| this._tabbedPane.setTabDelegate(new Sources.EditorContainerTabDelegate(this)); | |
| this._tabbedPane.setCloseableTabs(true); | |
| this._tabbedPane.setAllowTabReorder(true, true); | |
| this._tabbedPane.addEventListener(UI.TabbedPane.Events.TabClosed, this._tabClosed, this); | |
| this._tabbedPane.addEventListener(UI.TabbedPane.Events.TabSelected, this._tabSelected, this); | |
| Persistence.persistence.addEventListener( | |
| Persistence.Persistence.Events.BindingCreated, this._onBindingCreated, this); | |
| Persistence.persistence.addEventListener( | |
| Persistence.Persistence.Events.BindingRemoved, this._onBindingRemoved, this); | |
| this._tabIds = new Map(); | |
| this._files = {}; | |
| this._previouslyViewedFilesSetting = setting; | |
| this._history = Sources.TabbedEditorContainer.History.fromObject(this._previouslyViewedFilesSetting.get()); | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _onBindingCreated(event) { | |
| const binding = /** @type {!Persistence.PersistenceBinding} */ (event.data); | |
| this._updateFileTitle(binding.fileSystem); | |
| const networkTabId = this._tabIds.get(binding.network); | |
| let fileSystemTabId = this._tabIds.get(binding.fileSystem); | |
| const wasSelectedInNetwork = this._currentFile === binding.network; | |
| const currentSelectionRange = this._history.selectionRange(binding.network.url()); | |
| const currentScrollLineNumber = this._history.scrollLineNumber(binding.network.url()); | |
| this._history.remove(binding.network.url()); | |
| if (!networkTabId) { | |
| return; | |
| } | |
| if (!fileSystemTabId) { | |
| const networkView = this._tabbedPane.tabView(networkTabId); | |
| const tabIndex = this._tabbedPane.tabIndex(networkTabId); | |
| if (networkView instanceof Sources.UISourceCodeFrame) { | |
| this._delegate.recycleUISourceCodeFrame(networkView, binding.fileSystem); | |
| fileSystemTabId = this._appendFileTab(binding.fileSystem, false, tabIndex, networkView); | |
| } else { | |
| fileSystemTabId = this._appendFileTab(binding.fileSystem, false, tabIndex); | |
| const fileSystemTabView = /** @type {!UI.Widget} */ (this._tabbedPane.tabView(fileSystemTabId)); | |
| this._restoreEditorProperties(fileSystemTabView, currentSelectionRange, currentScrollLineNumber); | |
| } | |
| } | |
| this._closeTabs([networkTabId], true); | |
| if (wasSelectedInNetwork) { | |
| this._tabbedPane.selectTab(fileSystemTabId, false); | |
| } | |
| this._updateHistory(); | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _onBindingRemoved(event) { | |
| const binding = /** @type {!Persistence.PersistenceBinding} */ (event.data); | |
| this._updateFileTitle(binding.fileSystem); | |
| } | |
| /** | |
| * @return {!UI.Widget} | |
| */ | |
| get view() { | |
| return this._tabbedPane; | |
| } | |
| /** | |
| * @return {?UI.Widget} | |
| */ | |
| get visibleView() { | |
| return this._tabbedPane.visibleView; | |
| } | |
| /** | |
| * @return {!Array.<!UI.Widget>} | |
| */ | |
| fileViews() { | |
| return /** @type {!Array.<!UI.Widget>} */ (this._tabbedPane.tabViews()); | |
| } | |
| /** | |
| * @return {!UI.Toolbar} | |
| */ | |
| leftToolbar() { | |
| return this._tabbedPane.leftToolbar(); | |
| } | |
| /** | |
| * @return {!UI.Toolbar} | |
| */ | |
| rightToolbar() { | |
| return this._tabbedPane.rightToolbar(); | |
| } | |
| /** | |
| * @param {!Element} parentElement | |
| */ | |
| show(parentElement) { | |
| this._tabbedPane.show(parentElement); | |
| } | |
| /** | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| */ | |
| showFile(uiSourceCode) { | |
| this._innerShowFile(uiSourceCode, true); | |
| } | |
| /** | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| */ | |
| closeFile(uiSourceCode) { | |
| const tabId = this._tabIds.get(uiSourceCode); | |
| if (!tabId) { | |
| return; | |
| } | |
| this._closeTabs([tabId]); | |
| } | |
| closeAllFiles() { | |
| this._closeTabs(this._tabbedPane.tabIds()); | |
| } | |
| /** | |
| * @return {!Array.<!Workspace.UISourceCode>} | |
| */ | |
| historyUISourceCodes() { | |
| // FIXME: there should be a way to fetch UISourceCode for its uri. | |
| const uriToUISourceCode = {}; | |
| for (const id in this._files) { | |
| const uiSourceCode = this._files[id]; | |
| uriToUISourceCode[uiSourceCode.url()] = uiSourceCode; | |
| } | |
| const result = []; | |
| const uris = this._history._urls(); | |
| for (let i = 0; i < uris.length; ++i) { | |
| const uiSourceCode = uriToUISourceCode[uris[i]]; | |
| if (uiSourceCode) { | |
| result.push(uiSourceCode); | |
| } | |
| } | |
| return result; | |
| } | |
| _addViewListeners() { | |
| if (!this._currentView || !this._currentView.textEditor) { | |
| return; | |
| } | |
| this._currentView.textEditor.addEventListener( | |
| SourceFrame.SourcesTextEditor.Events.ScrollChanged, this._scrollChanged, this); | |
| this._currentView.textEditor.addEventListener( | |
| SourceFrame.SourcesTextEditor.Events.SelectionChanged, this._selectionChanged, this); | |
| } | |
| _removeViewListeners() { | |
| if (!this._currentView || !this._currentView.textEditor) { | |
| return; | |
| } | |
| this._currentView.textEditor.removeEventListener( | |
| SourceFrame.SourcesTextEditor.Events.ScrollChanged, this._scrollChanged, this); | |
| this._currentView.textEditor.removeEventListener( | |
| SourceFrame.SourcesTextEditor.Events.SelectionChanged, this._selectionChanged, this); | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _scrollChanged(event) { | |
| if (this._scrollTimer) { | |
| clearTimeout(this._scrollTimer); | |
| } | |
| const lineNumber = /** @type {number} */ (event.data); | |
| this._scrollTimer = setTimeout(saveHistory.bind(this), 100); | |
| this._history.updateScrollLineNumber(this._currentFile.url(), lineNumber); | |
| /** | |
| * @this {Sources.TabbedEditorContainer} | |
| */ | |
| function saveHistory() { | |
| this._history.save(this._previouslyViewedFilesSetting); | |
| } | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _selectionChanged(event) { | |
| const range = /** @type {!TextUtils.TextRange} */ (event.data); | |
| this._history.updateSelectionRange(this._currentFile.url(), range); | |
| this._history.save(this._previouslyViewedFilesSetting); | |
| Extensions.extensionServer.sourceSelectionChanged(this._currentFile.url(), range); | |
| } | |
| /** | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| * @param {boolean=} userGesture | |
| */ | |
| _innerShowFile(uiSourceCode, userGesture) { | |
| const binding = Persistence.persistence.binding(uiSourceCode); | |
| uiSourceCode = binding ? binding.fileSystem : uiSourceCode; | |
| if (this._currentFile === uiSourceCode) { | |
| return; | |
| } | |
| this._removeViewListeners(); | |
| this._currentFile = uiSourceCode; | |
| const tabId = this._tabIds.get(uiSourceCode) || this._appendFileTab(uiSourceCode, userGesture); | |
| this._tabbedPane.selectTab(tabId, userGesture); | |
| if (userGesture) { | |
| this._editorSelectedByUserAction(); | |
| } | |
| const previousView = this._currentView; | |
| this._currentView = this.visibleView; | |
| this._addViewListeners(); | |
| const eventData = { | |
| currentFile: this._currentFile, | |
| currentView: this._currentView, | |
| previousView: previousView, | |
| userGesture: userGesture | |
| }; | |
| this.dispatchEventToListeners(Sources.TabbedEditorContainer.Events.EditorSelected, eventData); | |
| } | |
| /** | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| * @return {string} | |
| */ | |
| _titleForFile(uiSourceCode) { | |
| const maxDisplayNameLength = 30; | |
| let title = uiSourceCode.displayName(true).trimMiddle(maxDisplayNameLength); | |
| if (uiSourceCode.isDirty()) { | |
| title += '*'; | |
| } | |
| return title; | |
| } | |
| /** | |
| * @param {string} id | |
| * @param {string} nextTabId | |
| */ | |
| _maybeCloseTab(id, nextTabId) { | |
| const uiSourceCode = this._files[id]; | |
| const shouldPrompt = uiSourceCode.isDirty() && uiSourceCode.project().canSetFileContent(); | |
| // FIXME: this should be replaced with common Save/Discard/Cancel dialog. | |
| if (!shouldPrompt || | |
| confirm(Common.UIString('Are you sure you want to close unsaved file: %s?', uiSourceCode.name()))) { | |
| uiSourceCode.resetWorkingCopy(); | |
| if (nextTabId) { | |
| this._tabbedPane.selectTab(nextTabId, true); | |
| } | |
| this._tabbedPane.closeTab(id, true); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * @param {!Array.<string>} ids | |
| * @param {boolean=} forceCloseDirtyTabs | |
| */ | |
| _closeTabs(ids, forceCloseDirtyTabs) { | |
| const dirtyTabs = []; | |
| const cleanTabs = []; | |
| for (let i = 0; i < ids.length; ++i) { | |
| const id = ids[i]; | |
| const uiSourceCode = this._files[id]; | |
| if (!forceCloseDirtyTabs && uiSourceCode.isDirty()) { | |
| dirtyTabs.push(id); | |
| } else { | |
| cleanTabs.push(id); | |
| } | |
| } | |
| if (dirtyTabs.length) { | |
| this._tabbedPane.selectTab(dirtyTabs[0], true); | |
| } | |
| this._tabbedPane.closeTabs(cleanTabs, true); | |
| for (let i = 0; i < dirtyTabs.length; ++i) { | |
| const nextTabId = i + 1 < dirtyTabs.length ? dirtyTabs[i + 1] : null; | |
| if (!this._maybeCloseTab(dirtyTabs[i], nextTabId)) { | |
| break; | |
| } | |
| } | |
| } | |
| /** | |
| * @param {string} tabId | |
| * @param {!UI.ContextMenu} contextMenu | |
| */ | |
| _onContextMenu(tabId, contextMenu) { | |
| const uiSourceCode = this._files[tabId]; | |
| if (uiSourceCode) { | |
| contextMenu.appendApplicableItems(uiSourceCode); | |
| } | |
| } | |
| /** | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| */ | |
| addUISourceCode(uiSourceCode) { | |
| const binding = Persistence.persistence.binding(uiSourceCode); | |
| uiSourceCode = binding ? binding.fileSystem : uiSourceCode; | |
| if (this._currentFile === uiSourceCode) { | |
| return; | |
| } | |
| const uri = uiSourceCode.url(); | |
| const index = this._history.index(uri); | |
| if (index === -1) { | |
| return; | |
| } | |
| if (!this._tabIds.has(uiSourceCode)) { | |
| this._appendFileTab(uiSourceCode, false); | |
| } | |
| // Select tab if this file was the last to be shown. | |
| if (!index) { | |
| this._innerShowFile(uiSourceCode, false); | |
| return; | |
| } | |
| if (!this._currentFile) { | |
| return; | |
| } | |
| const currentProjectIsSnippets = Snippets.isSnippetsUISourceCode(this._currentFile); | |
| const addedProjectIsSnippets = Snippets.isSnippetsUISourceCode(uiSourceCode); | |
| if (this._history.index(this._currentFile.url()) && currentProjectIsSnippets && !addedProjectIsSnippets) { | |
| this._innerShowFile(uiSourceCode, false); | |
| } | |
| } | |
| /** | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| */ | |
| removeUISourceCode(uiSourceCode) { | |
| this.removeUISourceCodes([uiSourceCode]); | |
| } | |
| /** | |
| * @param {!Array.<!Workspace.UISourceCode>} uiSourceCodes | |
| */ | |
| removeUISourceCodes(uiSourceCodes) { | |
| const tabIds = []; | |
| for (let i = 0; i < uiSourceCodes.length; ++i) { | |
| const uiSourceCode = uiSourceCodes[i]; | |
| const tabId = this._tabIds.get(uiSourceCode); | |
| if (tabId) { | |
| tabIds.push(tabId); | |
| } | |
| } | |
| this._tabbedPane.closeTabs(tabIds); | |
| } | |
| /** | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| */ | |
| _editorClosedByUserAction(uiSourceCode) { | |
| this._history.remove(uiSourceCode.url()); | |
| this._updateHistory(); | |
| } | |
| _editorSelectedByUserAction() { | |
| this._updateHistory(); | |
| } | |
| _updateHistory() { | |
| const tabIds = this._tabbedPane.lastOpenedTabIds(Sources.TabbedEditorContainer.maximalPreviouslyViewedFilesCount); | |
| /** | |
| * @param {string} tabId | |
| * @this {Sources.TabbedEditorContainer} | |
| */ | |
| function tabIdToURI(tabId) { | |
| return this._files[tabId].url(); | |
| } | |
| this._history.update(tabIds.map(tabIdToURI.bind(this))); | |
| this._history.save(this._previouslyViewedFilesSetting); | |
| } | |
| /** | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| * @return {string} | |
| */ | |
| _tooltipForFile(uiSourceCode) { | |
| uiSourceCode = Persistence.persistence.network(uiSourceCode) || uiSourceCode; | |
| return uiSourceCode.url(); | |
| } | |
| /** | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| * @param {boolean=} userGesture | |
| * @param {number=} index | |
| * @param {!UI.Widget=} replaceView | |
| * @return {string} | |
| */ | |
| _appendFileTab(uiSourceCode, userGesture, index, replaceView) { | |
| const view = replaceView || this._delegate.viewForFile(uiSourceCode); | |
| const title = this._titleForFile(uiSourceCode); | |
| const tooltip = this._tooltipForFile(uiSourceCode); | |
| const tabId = this._generateTabId(); | |
| this._tabIds.set(uiSourceCode, tabId); | |
| this._files[tabId] = uiSourceCode; | |
| if (!replaceView) { | |
| const savedSelectionRange = this._history.selectionRange(uiSourceCode.url()); | |
| const savedScrollLineNumber = this._history.scrollLineNumber(uiSourceCode.url()); | |
| this._restoreEditorProperties(view, savedSelectionRange, savedScrollLineNumber); | |
| } | |
| this._tabbedPane.appendTab(tabId, title, view, tooltip, userGesture, undefined, index); | |
| this._updateFileTitle(uiSourceCode); | |
| this._addUISourceCodeListeners(uiSourceCode); | |
| if (uiSourceCode.loadError()) { | |
| this._addLoadErrorIcon(tabId); | |
| } else if (!uiSourceCode.contentLoaded()) { | |
| uiSourceCode.requestContent().then(content => { | |
| if (uiSourceCode.loadError()) { | |
| this._addLoadErrorIcon(tabId); | |
| } | |
| }); | |
| } | |
| return tabId; | |
| } | |
| /** | |
| * @param {string} tabId | |
| */ | |
| _addLoadErrorIcon(tabId) { | |
| const icon = UI.Icon.create('smallicon-error'); | |
| icon.title = ls`Unable to load this content.`; | |
| if (this._tabbedPane.tabView(tabId)) { | |
| this._tabbedPane.setTabIcon(tabId, icon); | |
| } | |
| } | |
| /** | |
| * @param {!UI.Widget} editorView | |
| * @param {!TextUtils.TextRange=} selection | |
| * @param {number=} firstLineNumber | |
| */ | |
| _restoreEditorProperties(editorView, selection, firstLineNumber) { | |
| const sourceFrame = | |
| editorView instanceof SourceFrame.SourceFrame ? /** @type {!SourceFrame.SourceFrame} */ (editorView) : null; | |
| if (!sourceFrame) { | |
| return; | |
| } | |
| if (selection) { | |
| sourceFrame.setSelection(selection); | |
| } | |
| if (typeof firstLineNumber === 'number') { | |
| sourceFrame.scrollToLine(firstLineNumber); | |
| } | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _tabClosed(event) { | |
| const tabId = /** @type {string} */ (event.data.tabId); | |
| const userGesture = /** @type {boolean} */ (event.data.isUserGesture); | |
| const uiSourceCode = this._files[tabId]; | |
| if (this._currentFile === uiSourceCode) { | |
| this._removeViewListeners(); | |
| delete this._currentView; | |
| delete this._currentFile; | |
| } | |
| this._tabIds.remove(uiSourceCode); | |
| delete this._files[tabId]; | |
| this._removeUISourceCodeListeners(uiSourceCode); | |
| this.dispatchEventToListeners(Sources.TabbedEditorContainer.Events.EditorClosed, uiSourceCode); | |
| if (userGesture) { | |
| this._editorClosedByUserAction(uiSourceCode); | |
| } | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _tabSelected(event) { | |
| const tabId = /** @type {string} */ (event.data.tabId); | |
| const userGesture = /** @type {boolean} */ (event.data.isUserGesture); | |
| const uiSourceCode = this._files[tabId]; | |
| this._innerShowFile(uiSourceCode, userGesture); | |
| } | |
| /** | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| */ | |
| _addUISourceCodeListeners(uiSourceCode) { | |
| uiSourceCode.addEventListener(Workspace.UISourceCode.Events.TitleChanged, this._uiSourceCodeTitleChanged, this); | |
| uiSourceCode.addEventListener( | |
| Workspace.UISourceCode.Events.WorkingCopyChanged, this._uiSourceCodeWorkingCopyChanged, this); | |
| uiSourceCode.addEventListener( | |
| Workspace.UISourceCode.Events.WorkingCopyCommitted, this._uiSourceCodeWorkingCopyCommitted, this); | |
| } | |
| /** | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| */ | |
| _removeUISourceCodeListeners(uiSourceCode) { | |
| uiSourceCode.removeEventListener(Workspace.UISourceCode.Events.TitleChanged, this._uiSourceCodeTitleChanged, this); | |
| uiSourceCode.removeEventListener( | |
| Workspace.UISourceCode.Events.WorkingCopyChanged, this._uiSourceCodeWorkingCopyChanged, this); | |
| uiSourceCode.removeEventListener( | |
| Workspace.UISourceCode.Events.WorkingCopyCommitted, this._uiSourceCodeWorkingCopyCommitted, this); | |
| } | |
| /** | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| */ | |
| _updateFileTitle(uiSourceCode) { | |
| const tabId = this._tabIds.get(uiSourceCode); | |
| if (tabId) { | |
| const title = this._titleForFile(uiSourceCode); | |
| const tooltip = this._tooltipForFile(uiSourceCode); | |
| this._tabbedPane.changeTabTitle(tabId, title, tooltip); | |
| let icon = null; | |
| if (uiSourceCode.loadError()) { | |
| icon = UI.Icon.create('smallicon-error'); | |
| icon.title = ls`Unable to load this content.`; | |
| } else if (Persistence.persistence.hasUnsavedCommittedChanges(uiSourceCode)) { | |
| icon = UI.Icon.create('smallicon-warning'); | |
| icon.title = Common.UIString('Changes to this file were not saved to file system.'); | |
| } else { | |
| icon = Persistence.PersistenceUtils.iconForUISourceCode(uiSourceCode); | |
| } | |
| this._tabbedPane.setTabIcon(tabId, icon); | |
| } | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _uiSourceCodeTitleChanged(event) { | |
| const uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data); | |
| this._updateFileTitle(uiSourceCode); | |
| this._updateHistory(); | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _uiSourceCodeWorkingCopyChanged(event) { | |
| const uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data); | |
| this._updateFileTitle(uiSourceCode); | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _uiSourceCodeWorkingCopyCommitted(event) { | |
| const uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data.uiSourceCode); | |
| this._updateFileTitle(uiSourceCode); | |
| } | |
| /** | |
| * @return {string} | |
| */ | |
| _generateTabId() { | |
| return 'tab_' + (Sources.TabbedEditorContainer._tabId++); | |
| } | |
| /** | |
| * @return {?Workspace.UISourceCode} uiSourceCode | |
| */ | |
| currentFile() { | |
| return this._currentFile || null; | |
| } | |
| }; | |
| /** @enum {symbol} */ | |
| Sources.TabbedEditorContainer.Events = { | |
| EditorSelected: Symbol('EditorSelected'), | |
| EditorClosed: Symbol('EditorClosed') | |
| }; | |
| Sources.TabbedEditorContainer._tabId = 0; | |
| Sources.TabbedEditorContainer.maximalPreviouslyViewedFilesCount = 30; | |
| /** | |
| * @unrestricted | |
| */ | |
| Sources.TabbedEditorContainer.HistoryItem = class { | |
| /** | |
| * @param {string} url | |
| * @param {!TextUtils.TextRange=} selectionRange | |
| * @param {number=} scrollLineNumber | |
| */ | |
| constructor(url, selectionRange, scrollLineNumber) { | |
| /** @const */ this.url = url; | |
| /** @const */ this._isSerializable = | |
| url.length < Sources.TabbedEditorContainer.HistoryItem.serializableUrlLengthLimit; | |
| this.selectionRange = selectionRange; | |
| this.scrollLineNumber = scrollLineNumber; | |
| } | |
| /** | |
| * @param {!Object} serializedHistoryItem | |
| * @return {!Sources.TabbedEditorContainer.HistoryItem} | |
| */ | |
| static fromObject(serializedHistoryItem) { | |
| const selectionRange = serializedHistoryItem.selectionRange ? | |
| TextUtils.TextRange.fromObject(serializedHistoryItem.selectionRange) : | |
| undefined; | |
| return new Sources.TabbedEditorContainer.HistoryItem( | |
| serializedHistoryItem.url, selectionRange, serializedHistoryItem.scrollLineNumber); | |
| } | |
| /** | |
| * @return {?Object} | |
| */ | |
| serializeToObject() { | |
| if (!this._isSerializable) { | |
| return null; | |
| } | |
| const serializedHistoryItem = {}; | |
| serializedHistoryItem.url = this.url; | |
| serializedHistoryItem.selectionRange = this.selectionRange; | |
| serializedHistoryItem.scrollLineNumber = this.scrollLineNumber; | |
| return serializedHistoryItem; | |
| } | |
| }; | |
| Sources.TabbedEditorContainer.HistoryItem.serializableUrlLengthLimit = 4096; | |
| /** | |
| * @unrestricted | |
| */ | |
| Sources.TabbedEditorContainer.History = class { | |
| /** | |
| * @param {!Array.<!Sources.TabbedEditorContainer.HistoryItem>} items | |
| */ | |
| constructor(items) { | |
| this._items = items; | |
| this._rebuildItemIndex(); | |
| } | |
| /** | |
| * @param {!Array.<!Object>} serializedHistory | |
| * @return {!Sources.TabbedEditorContainer.History} | |
| */ | |
| static fromObject(serializedHistory) { | |
| const items = []; | |
| for (let i = 0; i < serializedHistory.length; ++i) { | |
| items.push(Sources.TabbedEditorContainer.HistoryItem.fromObject(serializedHistory[i])); | |
| } | |
| return new Sources.TabbedEditorContainer.History(items); | |
| } | |
| /** | |
| * @param {string} url | |
| * @return {number} | |
| */ | |
| index(url) { | |
| return this._itemsIndex.has(url) ? /** @type {number} */ (this._itemsIndex.get(url)) : -1; | |
| } | |
| _rebuildItemIndex() { | |
| /** @type {!Map<string, number>} */ | |
| this._itemsIndex = new Map(); | |
| for (let i = 0; i < this._items.length; ++i) { | |
| console.assert(!this._itemsIndex.has(this._items[i].url)); | |
| this._itemsIndex.set(this._items[i].url, i); | |
| } | |
| } | |
| /** | |
| * @param {string} url | |
| * @return {!TextUtils.TextRange|undefined} | |
| */ | |
| selectionRange(url) { | |
| const index = this.index(url); | |
| return index !== -1 ? this._items[index].selectionRange : undefined; | |
| } | |
| /** | |
| * @param {string} url | |
| * @param {!TextUtils.TextRange=} selectionRange | |
| */ | |
| updateSelectionRange(url, selectionRange) { | |
| if (!selectionRange) { | |
| return; | |
| } | |
| const index = this.index(url); | |
| if (index === -1) { | |
| return; | |
| } | |
| this._items[index].selectionRange = selectionRange; | |
| } | |
| /** | |
| * @param {string} url | |
| * @return {number|undefined} | |
| */ | |
| scrollLineNumber(url) { | |
| const index = this.index(url); | |
| return index !== -1 ? this._items[index].scrollLineNumber : undefined; | |
| } | |
| /** | |
| * @param {string} url | |
| * @param {number} scrollLineNumber | |
| */ | |
| updateScrollLineNumber(url, scrollLineNumber) { | |
| const index = this.index(url); | |
| if (index === -1) { | |
| return; | |
| } | |
| this._items[index].scrollLineNumber = scrollLineNumber; | |
| } | |
| /** | |
| * @param {!Array.<string>} urls | |
| */ | |
| update(urls) { | |
| for (let i = urls.length - 1; i >= 0; --i) { | |
| const index = this.index(urls[i]); | |
| let item; | |
| if (index !== -1) { | |
| item = this._items[index]; | |
| this._items.splice(index, 1); | |
| } else { | |
| item = new Sources.TabbedEditorContainer.HistoryItem(urls[i]); | |
| } | |
| this._items.unshift(item); | |
| this._rebuildItemIndex(); | |
| } | |
| } | |
| /** | |
| * @param {string} url | |
| */ | |
| remove(url) { | |
| const index = this.index(url); | |
| if (index !== -1) { | |
| this._items.splice(index, 1); | |
| this._rebuildItemIndex(); | |
| } | |
| } | |
| /** | |
| * @param {!Common.Setting} setting | |
| */ | |
| save(setting) { | |
| setting.set(this._serializeToObject()); | |
| } | |
| /** | |
| * @return {!Array.<!Object>} | |
| */ | |
| _serializeToObject() { | |
| const serializedHistory = []; | |
| for (let i = 0; i < this._items.length; ++i) { | |
| const serializedItem = this._items[i].serializeToObject(); | |
| if (serializedItem) { | |
| serializedHistory.push(serializedItem); | |
| } | |
| if (serializedHistory.length === Sources.TabbedEditorContainer.maximalPreviouslyViewedFilesCount) { | |
| break; | |
| } | |
| } | |
| return serializedHistory; | |
| } | |
| /** | |
| * @return {!Array.<string>} | |
| */ | |
| _urls() { | |
| const result = []; | |
| for (let i = 0; i < this._items.length; ++i) { | |
| result.push(this._items[i].url); | |
| } | |
| return result; | |
| } | |
| }; | |
| /** | |
| * @implements {UI.TabbedPaneTabDelegate} | |
| * @unrestricted | |
| */ | |
| Sources.EditorContainerTabDelegate = class { | |
| /** | |
| * @param {!Sources.TabbedEditorContainer} editorContainer | |
| */ | |
| constructor(editorContainer) { | |
| this._editorContainer = editorContainer; | |
| } | |
| /** | |
| * @override | |
| * @param {!UI.TabbedPane} tabbedPane | |
| * @param {!Array.<string>} ids | |
| */ | |
| closeTabs(tabbedPane, ids) { | |
| this._editorContainer._closeTabs(ids); | |
| } | |
| /** | |
| * @override | |
| * @param {string} tabId | |
| * @param {!UI.ContextMenu} contextMenu | |
| */ | |
| onContextMenu(tabId, contextMenu) { | |
| this._editorContainer._onContextMenu(tabId, contextMenu); | |
| } | |
| }; |