// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @constructor
 * @implements {WebInspector.TabbedEditorContainerDelegate}
 * @implements {WebInspector.Searchable}
 * @implements {WebInspector.Replaceable}
 * @extends {WebInspector.VBox}
 * @param {!WebInspector.Workspace} workspace
 * @param {!WebInspector.SourcesPanel} sourcesPanel
 * @suppressGlobalPropertiesCheck
 */
WebInspector.SourcesView = function(workspace, sourcesPanel)
{
    WebInspector.VBox.call(this);
    this.registerRequiredCSS("sources/sourcesView.css");
    this.element.id = "sources-panel-sources-view";
    this.setMinimumAndPreferredSizes(50, 52, 150, 100);

    this._workspace = workspace;
    this._sourcesPanel = sourcesPanel;

    this._searchableView = new WebInspector.SearchableView(this, "sourcesViewSearchConfig");
    this._searchableView.setMinimalSearchQuerySize(0);
    this._searchableView.show(this.element);

    /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.UISourceCodeFrame>} */
    this._sourceFramesByUISourceCode = new Map();

    var tabbedEditorPlaceholderText = WebInspector.isMac() ? WebInspector.UIString("Hit Cmd+P to open a file") : WebInspector.UIString("Hit Ctrl+P to open a file");
    this._editorContainer = new WebInspector.TabbedEditorContainer(this, WebInspector.settings.createLocalSetting("previouslyViewedFiles", []), tabbedEditorPlaceholderText);
    this._editorContainer.show(this._searchableView.element);
    this._editorContainer.addEventListener(WebInspector.TabbedEditorContainer.Events.EditorSelected, this._editorSelected, this);
    this._editorContainer.addEventListener(WebInspector.TabbedEditorContainer.Events.EditorClosed, this._editorClosed, this);

    this._historyManager = new WebInspector.EditingLocationHistoryManager(this, this.currentSourceFrame.bind(this));

    this._toolbarContainerElement = this.element.createChild("div", "sources-toolbar");
    this._toolbarEditorActions = new WebInspector.Toolbar(this._toolbarContainerElement);

    self.runtime.instancesPromise(WebInspector.SourcesView.EditorAction).then(appendButtonsForExtensions.bind(this));
    /**
     * @param {!Array.<!WebInspector.SourcesView.EditorAction>} actions
     * @this {WebInspector.SourcesView}
     */
    function appendButtonsForExtensions(actions)
    {
        for (var i = 0; i < actions.length; ++i)
            this._toolbarEditorActions.appendToolbarItem(actions[i].button(this));
    }
    this._scriptViewToolbarText = new WebInspector.Toolbar(this._toolbarContainerElement);

    WebInspector.startBatchUpdate();
    this._workspace.uiSourceCodes().forEach(this._addUISourceCode.bind(this));
    WebInspector.endBatchUpdate();

    this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this);
    this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
    this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectRemoved, this._projectRemoved.bind(this), this);

    function handleBeforeUnload(event)
    {
        if (event.returnValue)
            return;
        var unsavedSourceCodes = WebInspector.workspace.unsavedSourceCodes();
        if (!unsavedSourceCodes.length)
            return;

        event.returnValue = WebInspector.UIString("DevTools have unsaved changes that will be permanently lost.");
        WebInspector.inspectorView.setCurrentPanel(WebInspector.SourcesPanel.instance());
        for (var i = 0; i < unsavedSourceCodes.length; ++i)
            WebInspector.Revealer.reveal(unsavedSourceCodes[i]);
    }
    if (!window.opener)
        window.addEventListener("beforeunload", handleBeforeUnload, true);

    this._shortcuts = {};
    this.element.addEventListener("keydown", this._handleKeyDown.bind(this), false);
}

WebInspector.SourcesView.Events = {
    EditorClosed: "EditorClosed",
    EditorSelected: "EditorSelected",
}

/**
 * @param {!WebInspector.UISourceCode} uiSourceCode
 * @return {string}
 */
WebInspector.SourcesView.uiSourceCodeHighlighterType = function(uiSourceCode)
{
    var networkContentType = WebInspector.NetworkProject.uiSourceCodeContentType(uiSourceCode);
    if (networkContentType)
        return networkContentType.canonicalMimeType();

    var mimeType = WebInspector.ResourceType.mimeTypesForExtensions[uiSourceCode.extension().toLowerCase()];
    return mimeType || uiSourceCode.contentType().canonicalMimeType();
}

WebInspector.SourcesView.prototype = {
    /**
     * @param {function(!Array.<!WebInspector.KeyboardShortcut.Descriptor>, function(!Event=):boolean)} registerShortcutDelegate
     */
    registerShortcuts: function(registerShortcutDelegate)
    {
        /**
         * @this {WebInspector.SourcesView}
         * @param {!Array.<!WebInspector.KeyboardShortcut.Descriptor>} shortcuts
         * @param {function(!Event=):boolean} handler
         */
        function registerShortcut(shortcuts, handler)
        {
            registerShortcutDelegate(shortcuts, handler);
            this._registerShortcuts(shortcuts, handler);
        }

        registerShortcut.call(this, WebInspector.ShortcutsScreen.SourcesPanelShortcuts.JumpToPreviousLocation, this._onJumpToPreviousLocation.bind(this));
        registerShortcut.call(this, WebInspector.ShortcutsScreen.SourcesPanelShortcuts.JumpToNextLocation, this._onJumpToNextLocation.bind(this));
        registerShortcut.call(this, WebInspector.ShortcutsScreen.SourcesPanelShortcuts.CloseEditorTab, this._onCloseEditorTab.bind(this));
        registerShortcut.call(this, WebInspector.ShortcutsScreen.SourcesPanelShortcuts.GoToLine, this._showGoToLineDialog.bind(this));
        registerShortcut.call(this, WebInspector.ShortcutsScreen.SourcesPanelShortcuts.GoToMember, this._showOutlineDialog.bind(this));
        registerShortcut.call(this, [WebInspector.KeyboardShortcut.makeDescriptor("o", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta | WebInspector.KeyboardShortcut.Modifiers.Shift)], this._showOutlineDialog.bind(this));
        registerShortcut.call(this, WebInspector.ShortcutsScreen.SourcesPanelShortcuts.ToggleBreakpoint, this._toggleBreakpoint.bind(this));
        registerShortcut.call(this, WebInspector.ShortcutsScreen.SourcesPanelShortcuts.Save, this._save.bind(this));
        registerShortcut.call(this, WebInspector.ShortcutsScreen.SourcesPanelShortcuts.SaveAll, this._saveAll.bind(this));
    },

    /**
     * @param {!Array.<!WebInspector.KeyboardShortcut.Descriptor>} keys
     * @param {function(!Event=):boolean} handler
     */
    _registerShortcuts: function(keys, handler)
    {
        for (var i = 0; i < keys.length; ++i)
            this._shortcuts[keys[i].key] = handler;
    },

    _handleKeyDown: function(event)
    {
        var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
        var handler = this._shortcuts[shortcutKey];
        if (handler && handler())
            event.consume(true);
    },

    wasShown: function()
    {
        WebInspector.VBox.prototype.wasShown.call(this);
        WebInspector.context.setFlavor(WebInspector.SourcesView, this);
    },

    willHide: function()
    {
        WebInspector.context.setFlavor(WebInspector.SourcesView, null);
        WebInspector.VBox.prototype.willHide.call(this);
    },

    /**
     * @return {!Element}
     */
    toolbarContainerElement: function()
    {
        return this._toolbarContainerElement;
    },

    /**
     * @override
     * @return {!Element}
     */
    defaultFocusedElement: function()
    {
        return this._editorContainer.view.defaultFocusedElement();
    },

    /**
     * @return {!WebInspector.SearchableView}
     */
    searchableView: function()
    {
        return this._searchableView;
    },

    /**
     * @return {!WebInspector.Widget}
     */
    visibleView: function()
    {
        return this._editorContainer.visibleView;
    },

    /**
     * @return {?WebInspector.SourceFrame}
     */
    currentSourceFrame: function()
    {
        var view = this.visibleView();
        if (!(view instanceof WebInspector.SourceFrame))
            return null;
        return /** @type {!WebInspector.SourceFrame} */ (view);
    },

    /**
     * @return {?WebInspector.UISourceCode}
     */
    currentUISourceCode: function()
    {
        return this._currentUISourceCode;
    },

    /**
     * @param {!Event=} event
     */
    _onCloseEditorTab: function(event)
    {
        var uiSourceCode = this.currentUISourceCode();
        if (!uiSourceCode)
            return false;
        this._editorContainer.closeFile(uiSourceCode);
        return true;
    },

    /**
     * @param {!Event=} event
     */
    _onJumpToPreviousLocation: function(event)
    {
        this._historyManager.rollback();
        return true;
    },

    /**
     * @param {!Event=} event
     */
    _onJumpToNextLocation: function(event)
    {
        this._historyManager.rollover();
        return true;
    },

    /**
     * @param {!WebInspector.Event} event
     */
    _uiSourceCodeAdded: function(event)
    {
        var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
        this._addUISourceCode(uiSourceCode);
    },

    /**
     * @param {!WebInspector.UISourceCode} uiSourceCode
     */
    _addUISourceCode: function(uiSourceCode)
    {
        if (uiSourceCode.project().isServiceProject())
            return;
        this._editorContainer.addUISourceCode(uiSourceCode);
        // Replace debugger script-based uiSourceCode with a network-based one.
        var currentUISourceCode = this._currentUISourceCode;
        if (!currentUISourceCode)
            return;
        var networkURL = WebInspector.networkMapping.networkURL(uiSourceCode);
        var currentNetworkURL = WebInspector.networkMapping.networkURL(currentUISourceCode);
        if (currentUISourceCode.project().isServiceProject() && currentUISourceCode !== uiSourceCode && currentNetworkURL === networkURL && networkURL) {
            this._showFile(uiSourceCode);
            this._editorContainer.removeUISourceCode(currentUISourceCode);
        }
    },

    _uiSourceCodeRemoved: function(event)
    {
        var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
        this._removeUISourceCodes([uiSourceCode]);
    },

    /**
     * @param {!Array.<!WebInspector.UISourceCode>} uiSourceCodes
     */
    _removeUISourceCodes: function(uiSourceCodes)
    {
        this._editorContainer.removeUISourceCodes(uiSourceCodes);
        for (var i = 0; i < uiSourceCodes.length; ++i) {
            this._removeSourceFrame(uiSourceCodes[i]);
            this._historyManager.removeHistoryForSourceCode(uiSourceCodes[i]);
        }
    },

    _projectRemoved: function(event)
    {
        var project = event.data;
        var uiSourceCodes = project.uiSourceCodes();
        this._removeUISourceCodes(uiSourceCodes);
        if (project.type() === WebInspector.projectTypes.Network)
            this._editorContainer.reset();
    },

    _updateScriptViewToolbarItems: function()
    {
        this._scriptViewToolbarText.removeToolbarItems();
        var sourceFrame = this.currentSourceFrame();
        if (!sourceFrame)
            return;

        var toolbarText = sourceFrame.toolbarText();
        this._scriptViewToolbarText.appendToolbarItem(toolbarText);
    },

    /**
     * @param {!WebInspector.UISourceCode} uiSourceCode
     * @param {number=} lineNumber 0-based
     * @param {number=} columnNumber
     * @param {boolean=} omitFocus
     * @param {boolean=} omitHighlight
     */
    showSourceLocation: function(uiSourceCode, lineNumber, columnNumber, omitFocus, omitHighlight)
    {
        this._historyManager.updateCurrentState();
        var sourceFrame = this._showFile(uiSourceCode);
        if (typeof lineNumber === "number")
            sourceFrame.revealPosition(lineNumber, columnNumber, !omitHighlight);
        this._historyManager.pushNewState();
        if (!omitFocus)
            sourceFrame.focus();
    },

    /**
     * @param {!WebInspector.UISourceCode} uiSourceCode
     * @return {!WebInspector.SourceFrame}
     */
    _showFile: function(uiSourceCode)
    {
        var sourceFrame = this._getOrCreateSourceFrame(uiSourceCode);
        if (this._currentUISourceCode === uiSourceCode)
            return sourceFrame;

        this._currentUISourceCode = uiSourceCode;
        this._editorContainer.showFile(uiSourceCode);
        this._updateScriptViewToolbarItems();
        return sourceFrame;
    },

    /**
     * @param {!WebInspector.UISourceCode} uiSourceCode
     * @return {!WebInspector.UISourceCodeFrame}
     */
    _createSourceFrame: function(uiSourceCode)
    {
        var sourceFrame;
        switch (uiSourceCode.contentType()) {
        case WebInspector.resourceTypes.Script:
            sourceFrame = new WebInspector.JavaScriptSourceFrame(this._sourcesPanel, uiSourceCode);
            break;
        case WebInspector.resourceTypes.Document:
            sourceFrame = new WebInspector.JavaScriptSourceFrame(this._sourcesPanel, uiSourceCode);
            break;
        case WebInspector.resourceTypes.Stylesheet:
            sourceFrame = new WebInspector.CSSSourceFrame(uiSourceCode);
            break;
        default:
            sourceFrame = new WebInspector.UISourceCodeFrame(uiSourceCode);
        break;
        }
        sourceFrame.setHighlighterType(WebInspector.SourcesView.uiSourceCodeHighlighterType(uiSourceCode));
        this._sourceFramesByUISourceCode.set(uiSourceCode, sourceFrame);
        this._historyManager.trackSourceFrameCursorJumps(sourceFrame);
        return sourceFrame;
    },

    /**
     * @param {!WebInspector.UISourceCode} uiSourceCode
     * @return {!WebInspector.UISourceCodeFrame}
     */
    _getOrCreateSourceFrame: function(uiSourceCode)
    {
        return this._sourceFramesByUISourceCode.get(uiSourceCode) || this._createSourceFrame(uiSourceCode);
    },

    /**
     * @param {!WebInspector.SourceFrame} sourceFrame
     * @param {!WebInspector.UISourceCode} uiSourceCode
     * @return {boolean}
     */
    _sourceFrameMatchesUISourceCode: function(sourceFrame, uiSourceCode)
    {
        switch (uiSourceCode.contentType()) {
        case WebInspector.resourceTypes.Script:
        case WebInspector.resourceTypes.Document:
            return sourceFrame instanceof WebInspector.JavaScriptSourceFrame;
        case WebInspector.resourceTypes.Stylesheet:
            return sourceFrame instanceof WebInspector.CSSSourceFrame;
        default:
            return !(sourceFrame instanceof WebInspector.JavaScriptSourceFrame);
        }
    },

    /**
     * @param {!WebInspector.UISourceCode} uiSourceCode
     */
    _recreateSourceFrameIfNeeded: function(uiSourceCode)
    {
        var oldSourceFrame = this._sourceFramesByUISourceCode.get(uiSourceCode);
        if (!oldSourceFrame)
            return;
        if (this._sourceFrameMatchesUISourceCode(oldSourceFrame, uiSourceCode)) {
            oldSourceFrame.setHighlighterType(WebInspector.SourcesView.uiSourceCodeHighlighterType(uiSourceCode));
        } else {
            this._editorContainer.removeUISourceCode(uiSourceCode);
            this._removeSourceFrame(uiSourceCode);
        }
    },

    /**
     * @override
     * @param {!WebInspector.UISourceCode} uiSourceCode
     * @return {!WebInspector.UISourceCodeFrame}
     */
    viewForFile: function(uiSourceCode)
    {
        return this._getOrCreateSourceFrame(uiSourceCode);
    },

    /**
     * @param {!WebInspector.UISourceCode} uiSourceCode
     */
    _removeSourceFrame: function(uiSourceCode)
    {
        var sourceFrame = this._sourceFramesByUISourceCode.get(uiSourceCode);
        if (!sourceFrame)
            return;
        this._sourceFramesByUISourceCode.remove(uiSourceCode);
        sourceFrame.dispose();
    },

    clearCurrentExecutionLine: function()
    {
        if (this._executionSourceFrame)
            this._executionSourceFrame.clearExecutionLine();
        delete this._executionSourceFrame;
    },

    /**
     * @param {!WebInspector.UILocation} uiLocation
     */
    setExecutionLocation: function(uiLocation)
    {
        var sourceFrame = this._getOrCreateSourceFrame(uiLocation.uiSourceCode);
        sourceFrame.setExecutionLocation(uiLocation);
        this._executionSourceFrame = sourceFrame;
    },

    _editorClosed: function(event)
    {
        var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
        this._historyManager.removeHistoryForSourceCode(uiSourceCode);

        var wasSelected = false;
        if (this._currentUISourceCode === uiSourceCode) {
            delete this._currentUISourceCode;
            wasSelected = true;
        }

        // SourcesNavigator does not need to update on EditorClosed.
        this._updateScriptViewToolbarItems();
        this._searchableView.resetSearch();

        var data = {};
        data.uiSourceCode = uiSourceCode;
        data.wasSelected = wasSelected;
        this.dispatchEventToListeners(WebInspector.SourcesView.Events.EditorClosed, data);
    },

    _editorSelected: function(event)
    {
        var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data.currentFile);
        var shouldUseHistoryManager = uiSourceCode !== this._currentUISourceCode && event.data.userGesture;
        if (shouldUseHistoryManager)
            this._historyManager.updateCurrentState();
        var sourceFrame = this._showFile(uiSourceCode);
        if (shouldUseHistoryManager)
            this._historyManager.pushNewState();

        this._searchableView.setReplaceable(!!sourceFrame && sourceFrame.canEditSource());
        this._searchableView.refreshSearch();

        this.dispatchEventToListeners(WebInspector.SourcesView.Events.EditorSelected, uiSourceCode);
    },

    /**
     * @param {!WebInspector.UISourceCode} uiSourceCode
     */
    sourceRenamed: function(uiSourceCode)
    {
        this._recreateSourceFrameIfNeeded(uiSourceCode);
    },

    /**
     * @override
     */
    searchCanceled: function()
    {
        if (this._searchView)
            this._searchView.searchCanceled();

        delete this._searchView;
        delete this._searchConfig;
    },

    /**
     * @override
     * @param {!WebInspector.SearchableView.SearchConfig} searchConfig
     * @param {boolean} shouldJump
     * @param {boolean=} jumpBackwards
     */
    performSearch: function(searchConfig, shouldJump, jumpBackwards)
    {
        this._searchableView.updateSearchMatchesCount(0);

        var sourceFrame = this.currentSourceFrame();
        if (!sourceFrame)
            return;

        this._searchView = sourceFrame;
        this._searchConfig = searchConfig;

        /**
         * @param {!WebInspector.Widget} view
         * @param {number} searchMatches
         * @this {WebInspector.SourcesView}
         */
        function finishedCallback(view, searchMatches)
        {
            if (!searchMatches)
                return;

            this._searchableView.updateSearchMatchesCount(searchMatches);
        }

        /**
         * @param {number} currentMatchIndex
         * @this {WebInspector.SourcesView}
         */
        function currentMatchChanged(currentMatchIndex)
        {
            this._searchableView.updateCurrentMatchIndex(currentMatchIndex);
        }

        /**
         * @this {WebInspector.SourcesView}
         */
        function searchResultsChanged()
        {
            this.performSearch(this._searchConfig, false, false);
        }

        this._searchView.performSearch(this._searchConfig, shouldJump, !!jumpBackwards, finishedCallback.bind(this), currentMatchChanged.bind(this), searchResultsChanged.bind(this));
    },

    /**
     * @override
     */
    jumpToNextSearchResult: function()
    {
        if (!this._searchView)
            return;

        if (this._searchView !== this.currentSourceFrame()) {
            this.performSearch(this._searchConfig, true);
            return;
        }

        this._searchView.jumpToNextSearchResult();
    },

    /**
     * @override
     */
    jumpToPreviousSearchResult: function()
    {
        if (!this._searchView)
            return;

        if (this._searchView !== this.currentSourceFrame()) {
            this.performSearch(this._searchConfig, true);
            if (this._searchView)
                this._searchView.jumpToLastSearchResult();
            return;
        }

        this._searchView.jumpToPreviousSearchResult();
    },

    /**
     * @override
     * @return {boolean}
     */
    supportsCaseSensitiveSearch: function()
    {
        return true;
    },

    /**
     * @override
     * @return {boolean}
     */
    supportsRegexSearch: function()
    {
        return true;
    },

    /**
     * @override
     * @param {!WebInspector.SearchableView.SearchConfig} searchConfig
     * @param {string} replacement
     */
    replaceSelectionWith: function(searchConfig, replacement)
    {
        var sourceFrame = this.currentSourceFrame();
        if (!sourceFrame) {
            console.assert(sourceFrame);
            return;
        }
        sourceFrame.replaceSelectionWith(searchConfig, replacement);
    },

    /**
     * @override
     * @param {!WebInspector.SearchableView.SearchConfig} searchConfig
     * @param {string} replacement
     */
    replaceAllWith: function(searchConfig, replacement)
    {
        var sourceFrame = this.currentSourceFrame();
        if (!sourceFrame) {
            console.assert(sourceFrame);
            return;
        }
        sourceFrame.replaceAllWith(searchConfig, replacement);
    },

    /**
     * @param {!Event=} event
     * @return {boolean}
     */
    _showOutlineDialog: function(event)
    {
        var uiSourceCode = this._editorContainer.currentFile();
        if (!uiSourceCode)
            return false;

        switch (uiSourceCode.contentType()) {
        case WebInspector.resourceTypes.Document:
        case WebInspector.resourceTypes.Script:
            WebInspector.JavaScriptOutlineDialog.show(this, uiSourceCode, this.showSourceLocation.bind(this, uiSourceCode));
            return true;
        case WebInspector.resourceTypes.Stylesheet:
            WebInspector.StyleSheetOutlineDialog.show(this, uiSourceCode, this.showSourceLocation.bind(this, uiSourceCode));
            return true;
        default:
            // We don't want default browser shortcut to be executed, so pretend to handle this event.
            return true;
        }
    },

    /**
     * @param {string=} query
     */
    showOpenResourceDialog: function(query)
    {
        var uiSourceCodes = this._editorContainer.historyUISourceCodes();
        /** @type {!Map.<!WebInspector.UISourceCode, number>} */
        var defaultScores = new Map();
        for (var i = 1; i < uiSourceCodes.length; ++i) // Skip current element
            defaultScores.set(uiSourceCodes[i], uiSourceCodes.length - i);
        WebInspector.OpenResourceDialog.show(this, this.element, query, defaultScores);
    },

    /**
     * @param {!Event=} event
     * @return {boolean}
     */
    _showGoToLineDialog: function(event)
    {
        if (this._currentUISourceCode)
            this.showOpenResourceDialog(":");
        return true;
    },

    /**
     * @return {boolean}
     */
    _save: function()
    {
        this._saveSourceFrame(this.currentSourceFrame());
        return true;
    },

    /**
     * @return {boolean}
     */
    _saveAll: function()
    {
        var sourceFrames = this._editorContainer.fileViews();
        sourceFrames.forEach(this._saveSourceFrame.bind(this));
        return true;
    },

    /**
     * @param {?WebInspector.SourceFrame} sourceFrame
     */
    _saveSourceFrame: function(sourceFrame)
    {
        if (!sourceFrame)
            return;
        if (!(sourceFrame instanceof WebInspector.UISourceCodeFrame))
            return;
        var uiSourceCodeFrame = /** @type {!WebInspector.UISourceCodeFrame} */ (sourceFrame);
        uiSourceCodeFrame.commitEditing();
    },
    /**
     * @return {boolean}
     */
    _toggleBreakpoint: function()
    {
        var sourceFrame = this.currentSourceFrame();
        if (!sourceFrame)
            return false;

        if (sourceFrame instanceof WebInspector.JavaScriptSourceFrame) {
            var javaScriptSourceFrame = /** @type {!WebInspector.JavaScriptSourceFrame} */ (sourceFrame);
            javaScriptSourceFrame.toggleBreakpointOnCurrentLine();
            return true;
        }
        return false;
    },

    /**
     * @param {boolean} active
     */
    toggleBreakpointsActiveState: function(active)
    {
        this._editorContainer.view.element.classList.toggle("breakpoints-deactivated", !active);
    },

    __proto__: WebInspector.VBox.prototype
}

/**
 * @interface
 */
WebInspector.SourcesView.EditorAction = function()
{
}

WebInspector.SourcesView.EditorAction.prototype = {
    /**
     * @param {!WebInspector.SourcesView} sourcesView
     * @return {!WebInspector.ToolbarButton}
     */
    button: function(sourcesView) { }
}

/**
 * @constructor
 * @implements {WebInspector.ActionDelegate}
 */
WebInspector.SourcesView.SwitchFileActionDelegate = function()
{
}

/**
 * @param {!WebInspector.UISourceCode} currentUISourceCode
 * @return {?WebInspector.UISourceCode}
 */
WebInspector.SourcesView.SwitchFileActionDelegate._nextFile = function(currentUISourceCode)
{
    /**
     * @param {string} name
     * @return {string}
     */
    function fileNamePrefix(name)
    {
        var lastDotIndex = name.lastIndexOf(".");
        var namePrefix = name.substr(0, lastDotIndex !== -1 ? lastDotIndex : name.length);
        return namePrefix.toLowerCase();
    }

    var uiSourceCodes = currentUISourceCode.project().uiSourceCodes();
    var candidates = [];
    var path = currentUISourceCode.parentPath();
    var name = currentUISourceCode.name();
    var namePrefix = fileNamePrefix(name);
    for (var i = 0; i < uiSourceCodes.length; ++i) {
        var uiSourceCode = uiSourceCodes[i];
        if (path !== uiSourceCode.parentPath())
            continue;
        if (fileNamePrefix(uiSourceCode.name()) === namePrefix)
            candidates.push(uiSourceCode.name());
    }
    candidates.sort(String.naturalOrderComparator);
    var index = mod(candidates.indexOf(name) + 1, candidates.length);
    var fullPath = (path ? path + "/" : "") + candidates[index];
    var nextUISourceCode = currentUISourceCode.project().uiSourceCode(fullPath);
    return nextUISourceCode !== currentUISourceCode ? nextUISourceCode : null;
}


WebInspector.SourcesView.SwitchFileActionDelegate.prototype = {
    /**
     * @override
     * @param {!WebInspector.Context} context
     * @param {string} actionId
     */
    handleAction: function(context, actionId)
    {
        var sourcesView = WebInspector.context.flavor(WebInspector.SourcesView);
        var currentUISourceCode = sourcesView.currentUISourceCode();
        if (!currentUISourceCode)
            return;
        var nextUISourceCode = WebInspector.SourcesView.SwitchFileActionDelegate._nextFile(currentUISourceCode);
        if (!nextUISourceCode)
            return;
        sourcesView.showSourceLocation(nextUISourceCode);
    }
}
