diff --git a/src/vs/workbench/parts/debug/electron-browser/breakpointsView.ts b/src/vs/workbench/parts/debug/electron-browser/breakpointsView.ts index 4c0b7cf9950a1915c4664260a91843b29973842f..5d16986e20567c8cf428a366cf22f008f2545032 100644 --- a/src/vs/workbench/parts/debug/electron-browser/breakpointsView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/breakpointsView.ts @@ -108,7 +108,9 @@ export class BreakpointsView extends ViewsViewletPanel { } protected layoutBody(size: number): void { - this.list.layout(size); + if (this.list) { + this.list.layout(size); + } } private onListContextMenu(e: IListContextMenuEvent): void { diff --git a/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts b/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts index 619a0e36a4e31b98f42817d2efd6e82bacec3426..b7b9b3cd8d8e41d120e0a56bb85d449fb214731b 100644 --- a/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts +++ b/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts @@ -271,7 +271,7 @@ export class ExplorerViewlet extends PersistentViewsViewlet implements IExplorer return false; } - if (view instanceof ExplorerView || view instanceof OpenEditorsView) { + if (view instanceof ExplorerView) { const viewer = view.getViewer(); if (!viewer) { return false; @@ -280,6 +280,9 @@ export class ExplorerViewlet extends PersistentViewsViewlet implements IExplorer return !!viewer.getFocus() || (viewer.getSelection() && viewer.getSelection().length > 0); } + if (view instanceof OpenEditorsView && !!view.getList()) { + return view.getList().isDOMFocused(); + } return false; } diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.ts b/src/vs/workbench/parts/files/electron-browser/fileActions.ts index 5dbf38e45153ad710b6306f09293a700a11f280b..959f0644dc23fa2193a5a26523f44670076a425a 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileActions.ts @@ -1141,7 +1141,9 @@ export class OpenToSideAction extends Action { public run(): TPromise { // Remove highlight - this.tree.clearHighlight(); + if (this.tree) { + this.tree.clearHighlight(); + } // Set side input return this.editorService.openEditor({ @@ -1713,7 +1715,7 @@ export class FocusOpenEditorsView extends Action { const openEditorsView = viewlet.getOpenEditorsView(); if (openEditorsView) { openEditorsView.setExpanded(true); - openEditorsView.getViewer().DOMFocus(); + openEditorsView.getList().domFocus(); } }); } diff --git a/src/vs/workbench/parts/files/electron-browser/fileCommands.ts b/src/vs/workbench/parts/files/electron-browser/fileCommands.ts index b2eeab111dd6f9c885f7fc273090c6e6d90d947a..40455bd3cb2bbd10b1c2adce9b69acebe3a9aee4 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileCommands.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileCommands.ts @@ -174,21 +174,22 @@ export function withFocusedFilesExplorer(accessor: ServicesAccessor): TPromise<{ }); } -function withFocusedOpenEditorsViewItem(accessor: ServicesAccessor): TPromise<{ explorer: ExplorerViewlet, tree: ITree, item: OpenEditor }> { +function withFocusedOpenEditorsViewItem(accessor: ServicesAccessor): TPromise<{ explorer: ExplorerViewlet, item: OpenEditor }> { return withVisibleExplorer(accessor).then(explorer => { - if (!explorer || !explorer.getOpenEditorsView()) { + if (!explorer || !explorer.getOpenEditorsView() || !explorer.getOpenEditorsView().getList()) { return void 0; // empty folder or hidden explorer } - const tree = explorer.getOpenEditorsView().getViewer(); + const list = explorer.getOpenEditorsView().getList(); // Ignore if in highlight mode or not focused - const focus = tree.getFocus(); - if (tree.getHighlight() || !tree.isDOMFocused() || !(focus instanceof OpenEditor)) { + const focused = list.getFocusedElements(); + const focus = focused.length ? focused[0] : undefined; + if (!list.isDOMFocused() || !(focus instanceof OpenEditor)) { return void 0; } - return { explorer, tree, item: focus }; + return { explorer, item: focus }; }); } diff --git a/src/vs/workbench/parts/files/electron-browser/media/explorerviewlet.css b/src/vs/workbench/parts/files/electron-browser/media/explorerviewlet.css index 07ff1c0ca4b4c08a2d8256e649f188b83f19ee69..ef3c88021fd65e43b66e1e5106b6cc41509cc456 100644 --- a/src/vs/workbench/parts/files/electron-browser/media/explorerviewlet.css +++ b/src/vs/workbench/parts/files/electron-browser/media/explorerviewlet.css @@ -41,11 +41,12 @@ flex: 0; /* do not steal space when label is hidden because we are in edit mode */ } -.explorer-viewlet .explorer-open-editors .monaco-tree .monaco-tree-row > .content { +.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row { + padding-left: 22px; display: flex; } -.explorer-viewlet .explorer-open-editors .monaco-tree .monaco-tree-row > .content > .monaco-action-bar { +.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .monaco-action-bar { visibility: hidden; } @@ -106,32 +107,32 @@ display: none; } -.explorer-viewlet .explorer-open-editors .monaco-tree .monaco-tree-row:hover > .content .monaco-action-bar, -.explorer-viewlet .explorer-open-editors .monaco-tree.focused .monaco-tree-row.focused > .content .monaco-action-bar, -.explorer-viewlet .explorer-open-editors .monaco-tree .monaco-tree-row > .content.dirty > .monaco-action-bar { +.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row:hover > .monaco-action-bar, +.explorer-viewlet .explorer-open-editors .monaco-list.focused .monaco-list-row.focused > .monaco-action-bar, +.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.dirty > .monaco-action-bar { visibility: visible; } -.explorer-viewlet .explorer-open-editors .monaco-tree .monaco-tree-row > .content .monaco-action-bar .action-label { +.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .monaco-action-bar .action-label { display: block; } -.explorer-viewlet .explorer-open-editors .monaco-tree .monaco-tree-row > .content .monaco-action-bar .close-editor-action { +.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .monaco-action-bar .close-editor-action { width: 8px; height: 22px; } -.explorer-viewlet .explorer-open-editors .monaco-tree .monaco-tree-row > .content .monaco-action-bar .action-close-all-files, -.explorer-viewlet .explorer-open-editors .monaco-tree .monaco-tree-row > .content .monaco-action-bar .save-all { +.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .monaco-action-bar .action-close-all-files, +.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .monaco-action-bar .save-all { width: 23px; height: 22px; } -.explorer-viewlet .explorer-open-editors .monaco-tree .monaco-tree-row > .content > .open-editor { +.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .open-editor { flex: 1; } -.explorer-viewlet .explorer-open-editors .monaco-tree .monaco-tree-row > .content > .editor-group { +.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .editor-group { flex: 1; } @@ -169,7 +170,7 @@ height: 20px; } -.explorer-viewlet .explorer-open-editors .monaco-tree .monaco-tree-row .editor-group { +.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row .editor-group { font-size: 11px; font-weight: bold; text-transform: uppercase; @@ -177,10 +178,10 @@ } /* Bold font style does not go well with CJK fonts */ -.explorer-viewlet:lang(zh-Hans) .explorer-open-editors .monaco-tree .monaco-tree-row .editor-group, -.explorer-viewlet:lang(zh-Hant) .explorer-open-editors .monaco-tree .monaco-tree-row .editor-group, -.explorer-viewlet:lang(ja) .explorer-open-editors .monaco-tree .monaco-tree-row .editor-group, -.explorer-viewlet:lang(ko) .explorer-open-editors .monaco-tree .monaco-tree-row .editor-group { +.explorer-viewlet:lang(zh-Hans) .explorer-open-editors .monaco-list .monaco-list-row .editor-group, +.explorer-viewlet:lang(zh-Hant) .explorer-open-editors .monaco-list .monaco-list-row .editor-group, +.explorer-viewlet:lang(ja) .explorer-open-editors .monaco-list .monaco-list-row .editor-group, +.explorer-viewlet:lang(ko) .explorer-open-editors .monaco-list .monaco-list-row .editor-group { font-weight: normal; } diff --git a/src/vs/workbench/parts/files/electron-browser/media/fileactions.css b/src/vs/workbench/parts/files/electron-browser/media/fileactions.css index 0954b8841ce7d5ba3452978ddd6b1422ea1c9b0d..a073640bc8f58380cd6744e772140d234df5a0bc 100644 --- a/src/vs/workbench/parts/files/electron-browser/media/fileactions.css +++ b/src/vs/workbench/parts/files/electron-browser/media/fileactions.css @@ -104,20 +104,20 @@ background: url("action-close.svg") center center no-repeat; } -.explorer-viewlet .explorer-open-editors .focused .monaco-tree-row.selected:not(.highlighted) > .content .close-editor-action { +.explorer-viewlet .explorer-open-editors .focused .monaco-list-row.selected:not(.highlighted) .close-editor-action { background: url("action-close-focus.svg") center center no-repeat; } -.explorer-viewlet .explorer-open-editors .monaco-tree .monaco-tree-row:not(:hover) > .content.dirty > .monaco-action-bar .close-editor-action { +.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .close-editor-action { background: url("action-close-dirty.svg") center center no-repeat; } -.vs-dark .explorer-viewlet .explorer-open-editors .monaco-tree .monaco-tree-row:not(:hover) > .content.dirty > .monaco-action-bar .close-editor-action, -.hc-black .monaco-workbench .explorer-viewlet .explorer-open-editors .monaco-tree .monaco-tree-row:not(:hover) > .content.dirty > .monaco-action-bar .close-editor-action { +.vs-dark .explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .close-editor-action, +.hc-black .monaco-workbench .explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .close-editor-action { background: url("action-close-dirty-dark.svg") center center no-repeat; } -.explorer-viewlet .explorer-open-editors .monaco-tree.focused .monaco-tree-row.selected:not(:hover) > .content.dirty > .monaco-action-bar .close-editor-action { +.explorer-viewlet .explorer-open-editors .monaco-list.focused .monaco-list-row.selected.dirty:not(:hover) > .monaco-action-bar .close-editor-action { background: url("action-close-dirty-focus.svg") center center no-repeat; } diff --git a/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts b/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts index 2621f9fe4d9f05e58be03c2e8e5a6d956fe00aea..68edcff5330f4b00ce128afdab34297bf2877625 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts @@ -3,39 +3,44 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import nls = require('vs/nls'); -import errors = require('vs/base/common/errors'); +import * as nls from 'vs/nls'; +import * as errors from 'vs/base/common/errors'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { TPromise } from 'vs/base/common/winjs.base'; import { IAction } from 'vs/base/common/actions'; -import dom = require('vs/base/browser/dom'); -import { IItemCollapseEvent } from 'vs/base/parts/tree/browser/treeModel'; +import * as dom from 'vs/base/browser/dom'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { Position, IEditorInput } from 'vs/platform/editor/common/editor'; import { IEditorStacksModel, IStacksModelChangeEvent, IEditorGroup } from 'vs/workbench/common/editor'; -import { SaveAllAction } from 'vs/workbench/parts/files/electron-browser/fileActions'; -import { TreeViewsViewletPanel, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { VIEWLET_ID, OpenEditorsFocusedContext, ExplorerFocusedContext } from 'vs/workbench/parts/files/common/files'; +import { SaveAllAction, SaveAllInGroupAction, OpenToSideAction, SaveFileAction, RevertFileAction, SaveFileAsAction, CompareWithSavedAction, CompareResourcesAction, SelectResourceForCompareAction } from 'vs/workbench/parts/files/electron-browser/fileActions'; +import { IViewletViewOptions, IViewOptions, ViewsViewletPanel } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { OpenEditorsFocusedContext, ExplorerFocusedContext, IFilesConfiguration } from 'vs/workbench/parts/files/common/files'; import { ITextFileService, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { OpenEditor } from 'vs/workbench/parts/files/common/explorerModel'; -import { Renderer, DataSource, Controller, AccessibilityProvider, ActionProvider, DragAndDrop } from 'vs/workbench/parts/files/electron-browser/views/openEditorsViewer'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { CloseAllEditorsAction } from 'vs/workbench/browser/parts/editor/editorActions'; +import { CloseAllEditorsAction, CloseUnmodifiedEditorsInGroupAction, CloseEditorsInGroupAction, CloseOtherEditorsInGroupAction, CloseEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; import { ToggleEditorLayoutAction } from 'vs/workbench/browser/actions/toggleEditorLayout'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { EditorGroup } from 'vs/workbench/common/editor/editorStacksModel'; import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService'; +import { IListService, WorkbenchList } from 'vs/platform/list/browser/listService'; +import { IDelegate, IRenderer, IListContextMenuEvent, IListMouseEvent } from 'vs/base/browser/ui/list/list'; +import { EditorLabel } from 'vs/workbench/browser/labels'; +import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; const $ = dom.$; -export class OpenEditorsView extends TreeViewsViewletPanel { +export class OpenEditorsView extends ViewsViewletPanel { private static readonly DEFAULT_VISIBLE_OPEN_EDITORS = 9; private static readonly DEFAULT_DYNAMIC_HEIGHT = true; @@ -44,24 +49,25 @@ export class OpenEditorsView extends TreeViewsViewletPanel { private model: IEditorStacksModel; private dirtyCountElement: HTMLElement; - private structuralTreeRefreshScheduler: RunOnceScheduler; + private listRefreshScheduler: RunOnceScheduler; private structuralRefreshDelay: number; - private groupToRefresh: IEditorGroup; - private fullRefreshNeeded: boolean; + private list: WorkbenchList; + private needsRefresh: boolean; constructor( options: IViewletViewOptions, @IInstantiationService private instantiationService: IInstantiationService, @IContextMenuService contextMenuService: IContextMenuService, @ITextFileService private textFileService: ITextFileService, - @IEditorGroupService editorGroupService: IEditorGroupService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IEditorGroupService private editorGroupService: IEditorGroupService, @IConfigurationService private configurationService: IConfigurationService, @IKeybindingService keybindingService: IKeybindingService, @IListService private listService: IListService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService, @IContextKeyService private contextKeyService: IContextKeyService, - @IViewletService private viewletService: IViewletService, - @IThemeService private themeService: IThemeService + @IThemeService private themeService: IThemeService, + @ITelemetryService private telemetryService: ITelemetryService ) { super({ ...(options as IViewOptions), @@ -71,7 +77,25 @@ export class OpenEditorsView extends TreeViewsViewletPanel { this.model = editorGroupService.getStacksModel(); this.structuralRefreshDelay = 0; - this.structuralTreeRefreshScheduler = new RunOnceScheduler(() => this.structuralTreeUpdate(), this.structuralRefreshDelay); + this.listRefreshScheduler = new RunOnceScheduler(() => { + this.list.splice(0, this.list.length, this.elements); + this.focusActiveEditor(); + this.updateSize(); + this.needsRefresh = false; + }, this.structuralRefreshDelay); + + // update on model changes + this.disposables.push(this.model.onModelChanged(e => this.onEditorStacksModelChanged(e))); + + // Also handle configuration updates + this.disposables.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChange(e))); + + // Handle dirty counter + this.disposables.push(this.untitledEditorService.onDidChangeDirty(e => this.updateDirtyIndicator())); + this.disposables.push(this.textFileService.models.onModelsDirty(e => this.updateDirtyIndicator())); + this.disposables.push(this.textFileService.models.onModelsSaved(e => this.updateDirtyIndicator())); + this.disposables.push(this.textFileService.models.onModelsSaveError(e => this.updateDirtyIndicator())); + this.disposables.push(this.textFileService.models.onModelsReverted(e => this.updateDirtyIndicator())); } protected renderHeaderTitle(container: HTMLElement): void { @@ -97,6 +121,43 @@ export class OpenEditorsView extends TreeViewsViewletPanel { this.updateDirtyIndicator(); } + public renderBody(container: HTMLElement): void { + dom.addClass(container, 'explorer-open-editors'); + dom.addClass(container, 'show-file-icons'); + + const delegate = new OpenEditorsDelegate(); + this.updateSize(); + this.list = new WorkbenchList(container, delegate, [ + new EditorGroupRenderer(this.keybindingService, this.instantiationService), + new OpenEditorRenderer(this.instantiationService, this.keybindingService, this.configurationService) + ], { + identityProvider: element => element instanceof OpenEditor ? element.getId() : element.id.toString(), + multipleSelectionSupport: false + }, this.contextKeyService, this.listService, this.themeService); + + // Bind context keys + OpenEditorsFocusedContext.bindTo(this.list.contextKeyService); + ExplorerFocusedContext.bindTo(this.list.contextKeyService); + + this.disposables.push(this.list.onContextMenu(e => this.onListContextMenu(e))); + + // Open when selecting via keyboard + this.disposables.push(this.list.onMouseClick(e => this.onMouseClick(e, false))); + this.disposables.push(this.list.onMouseDblClick(e => this.onMouseClick(e, true))); + this.disposables.push(this.list.onKeyDown(e => { + const event = new StandardKeyboardEvent(e); + if (event.keyCode === KeyCode.Enter) { + const focused = this.list.getFocusedElements(); + const element = focused.length ? focused[0] : undefined; + if (element instanceof OpenEditor) { + this.openEditor(element, { pinned: false, sideBySide: !!event.ctrlKey, preserveFocus: false }); + } + } + })); + + this.listRefreshScheduler.schedule(0); + } + public getActions(): IAction[] { return [ this.instantiationService.createInstance(ToggleEditorLayoutAction, ToggleEditorLayoutAction.ID, ToggleEditorLayoutAction.LABEL), @@ -105,159 +166,223 @@ export class OpenEditorsView extends TreeViewsViewletPanel { ]; } - public renderBody(container: HTMLElement): void { - this.treeContainer = super.renderViewTree(container); - dom.addClass(this.treeContainer, 'explorer-open-editors'); - dom.addClass(this.treeContainer, 'show-file-icons'); - - const dataSource = this.instantiationService.createInstance(DataSource); - const actionProvider = this.instantiationService.createInstance(ActionProvider, this.model); - const renderer = this.instantiationService.createInstance(Renderer, actionProvider); - const controller = this.instantiationService.createInstance(Controller, actionProvider, this.model); - const accessibilityProvider = this.instantiationService.createInstance(AccessibilityProvider); - const dnd = this.instantiationService.createInstance(DragAndDrop); - - this.tree = new WorkbenchTree(this.treeContainer, { - dataSource, - renderer, - controller, - accessibilityProvider, - dnd - }, { - indentPixels: 0, - twistiePixels: 22, - ariaLabel: nls.localize({ key: 'treeAriaLabel', comment: ['Open is an adjective'] }, "Open Editors: List of Active Files"), - showTwistie: false, - keyboardSupport: false - }, this.contextKeyService, this.listService, this.themeService); - - // Bind context keys - OpenEditorsFocusedContext.bindTo(this.tree.contextKeyService); - ExplorerFocusedContext.bindTo(this.tree.contextKeyService); + public setExpanded(expanded: boolean): void { + super.setExpanded(expanded); + if (expanded && this.needsRefresh) { + this.listRefreshScheduler.schedule(0); + } + } - // Open when selecting via keyboard - this.disposables.push(this.tree.onDidChangeSelection(event => { - if (event && event.payload && event.payload.origin === 'keyboard') { - controller.openEditor(this.tree.getFocus(), { pinned: false, sideBySide: false, preserveFocus: false }); + public setVisible(visible: boolean): TPromise { + return super.setVisible(visible).then(() => { + if (visible && this.needsRefresh) { + this.listRefreshScheduler.schedule(0); } - })); + }); + } + + public getList(): WorkbenchList { + return this.list; + } - // Prevent collapsing of editor groups - this.disposables.push(this.tree.onDidCollapseItem((event: IItemCollapseEvent) => { - if (event.item && event.item.getElement() instanceof EditorGroup) { - setTimeout(() => this.tree.expand(event.item.getElement())); // unwind from callback + protected layoutBody(size: number): void { + if (this.list) { + this.list.layout(size); + } + } + + private get elements(): (IEditorGroup | OpenEditor)[] { + const result: (IEditorGroup | OpenEditor)[] = []; + this.model.groups.forEach(g => { + if (this.model.groups.length > 1) { + result.push(g); } - })); + result.push(...g.getEditors().map(ei => new OpenEditor(ei, g))); + }); - this.fullRefreshNeeded = true; - this.structuralTreeUpdate(); + return result; } - public create(): TPromise { + private getIndex(group: IEditorGroup, editor: IEditorInput): number { + let index = 0; + for (let g of this.model.groups) { + if (this.model.groups.length > 1) { + index++; + } + if (g.id !== group.id) { + index += g.getEditors().length; + } else { + if (!editor) { + return index - 1; + } + for (let e of g.getEditors()) { + if (e.getResource().toString() !== editor.getResource().toString()) { + index++; + } else { + return index; + } + } + } + } - // Load Config - this.updateSize(); + return -1; + } - // listeners - this.registerListeners(); + private onMouseClick(event: IListMouseEvent, isDoubleClick: boolean): void { + const element = event.element; + if (!(element instanceof OpenEditor)) { + return; + } - return super.create(); + if (event.browserEvent && event.browserEvent.button === 1 /* Middle Button */) { + const position = this.model.positionOfGroup(element.editorGroup); + this.editorService.closeEditor(position, element.editorInput).done(null, errors.onUnexpectedError); + } else { + this.openEditor(element, { preserveFocus: !isDoubleClick, pinned: isDoubleClick, sideBySide: event.browserEvent.ctrlKey || event.browserEvent.metaKey }); + } } - private registerListeners(): void { + private openEditor(element: OpenEditor, options: { preserveFocus: boolean; pinned: boolean; sideBySide: boolean; }): void { + if (element) { + /* __GDPR__ + "workbenchActionExecuted" : { + "id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('workbenchActionExecuted', { id: 'workbench.files.openFile', from: 'openEditors' }); + let position = this.model.positionOfGroup(element.editorGroup); + if (options.sideBySide && position !== Position.THREE) { + position++; + } + this.editorGroupService.activateGroup(this.model.groupAt(position)); + this.editorService.openEditor(element.editorInput, options, position) + .done(() => this.editorGroupService.activateGroup(this.model.groupAt(position)), errors.onUnexpectedError); + } + } - // update on model changes - this.disposables.push(this.model.onModelChanged(e => this.onEditorStacksModelChanged(e))); + private onListContextMenu(e: IListContextMenuEvent): void { + // TODO@isidor contributable actions + const autoSaveEnabled = this.textFileService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY; + const element = e.element; + const actions: IAction[] = []; + if (element instanceof EditorGroup) { + if (!autoSaveEnabled) { + actions.push(this.instantiationService.createInstance(SaveAllInGroupAction, SaveAllInGroupAction.ID, nls.localize('saveAll', "Save All"))); + actions.push(new Separator()); + } - // Also handle configuration updates - this.disposables.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChange(e))); + actions.push(this.instantiationService.createInstance(CloseUnmodifiedEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction.ID, nls.localize('closeAllUnmodified', "Close Unmodified"))); + actions.push(this.instantiationService.createInstance(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, nls.localize('closeAll', "Close All"))); + } else { + const openEditor = element; + const resource = openEditor.getResource(); + if (resource) { + // Open to side + actions.unshift(this.instantiationService.createInstance(OpenToSideAction, undefined, resource, false)); + + if (!openEditor.isUntitled()) { + + // Files: Save / Revert + if (!autoSaveEnabled) { + actions.push(new Separator()); + + const saveAction = this.instantiationService.createInstance(SaveFileAction, SaveFileAction.ID, SaveFileAction.LABEL); + saveAction.setResource(resource); + saveAction.enabled = openEditor.isDirty(); + actions.push(saveAction); + + const revertAction = this.instantiationService.createInstance(RevertFileAction, RevertFileAction.ID, RevertFileAction.LABEL); + revertAction.setResource(resource); + revertAction.enabled = openEditor.isDirty(); + actions.push(revertAction); + } + } - // Handle dirty counter - this.disposables.push(this.untitledEditorService.onDidChangeDirty(e => this.updateDirtyIndicator())); - this.disposables.push(this.textFileService.models.onModelsDirty(e => this.updateDirtyIndicator())); - this.disposables.push(this.textFileService.models.onModelsSaved(e => this.updateDirtyIndicator())); - this.disposables.push(this.textFileService.models.onModelsSaveError(e => this.updateDirtyIndicator())); - this.disposables.push(this.textFileService.models.onModelsReverted(e => this.updateDirtyIndicator())); + // Untitled: Save / Save As + if (openEditor.isUntitled()) { + actions.push(new Separator()); + + if (this.untitledEditorService.hasAssociatedFilePath(resource)) { + let saveUntitledAction = this.instantiationService.createInstance(SaveFileAction, SaveFileAction.ID, SaveFileAction.LABEL); + saveUntitledAction.setResource(resource); + actions.push(saveUntitledAction); + } + + let saveAsAction = this.instantiationService.createInstance(SaveFileAsAction, SaveFileAsAction.ID, SaveFileAsAction.LABEL); + saveAsAction.setResource(resource); + actions.push(saveAsAction); + } + + // Compare Actions + actions.push(new Separator()); - // We are not updating the tree while the viewlet is not visible. Thus refresh when viewlet becomes visible #6702 - this.disposables.push(this.viewletService.onDidViewletOpen(viewlet => { - if (viewlet.getId() === VIEWLET_ID) { - this.fullRefreshNeeded = true; - this.structuralTreeUpdate(); - this.updateDirtyIndicator(); + if (!openEditor.isUntitled()) { + const compareWithSavedAction = this.instantiationService.createInstance(CompareWithSavedAction, CompareWithSavedAction.ID, nls.localize('compareWithSaved', "Compare with Saved")); + compareWithSavedAction.setResource(resource); + compareWithSavedAction.enabled = openEditor.isDirty(); + actions.push(compareWithSavedAction); + } + + const runCompareAction = this.instantiationService.createInstance(CompareResourcesAction, resource, undefined); + if (runCompareAction._isEnabled()) { + actions.push(runCompareAction); + } + actions.push(this.instantiationService.createInstance(SelectResourceForCompareAction, resource, undefined)); + + actions.push(new Separator()); } - })); + + actions.push(this.instantiationService.createInstance(CloseEditorAction, CloseEditorAction.ID, nls.localize('close', "Close"))); + const closeOtherEditorsInGroupAction = this.instantiationService.createInstance(CloseOtherEditorsInGroupAction, CloseOtherEditorsInGroupAction.ID, nls.localize('closeOthers', "Close Others")); + closeOtherEditorsInGroupAction.enabled = openEditor.editorGroup.count > 1; + actions.push(closeOtherEditorsInGroupAction); + actions.push(this.instantiationService.createInstance(CloseUnmodifiedEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction.ID, nls.localize('closeAllUnmodified', "Close Unmodified"))); + actions.push(this.instantiationService.createInstance(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, nls.localize('closeAll', "Close All"))); + } + + this.contextMenuService.showContextMenu({ + getAnchor: () => e.anchor, + getActions: () => TPromise.as(actions), + getActionsContext: () => element + }); } private onEditorStacksModelChanged(e: IStacksModelChangeEvent): void { - if (this.isDisposed || !this.isVisible() || !this.tree) { + if (!this.isVisible() || !this.list || !this.isExpanded()) { + this.needsRefresh = true; return; } // Do a minimal tree update based on if the change is structural or not #6670 if (e.structural) { - // If an editor changed structurally it is enough to refresh the group, otherwise a group changed structurally and we need the full refresh. - // If there are multiple groups to refresh - refresh the whole tree. - if (e.editor && !this.groupToRefresh) { - this.groupToRefresh = e.group; - } else { - this.fullRefreshNeeded = true; - } - this.structuralTreeRefreshScheduler.schedule(this.structuralRefreshDelay); + this.listRefreshScheduler.schedule(this.structuralRefreshDelay); } else { - const toRefresh = e.editor ? new OpenEditor(e.editor, e.group) : e.group; - this.tree.refresh(toRefresh, false).done(() => this.highlightActiveEditor(), errors.onUnexpectedError); - } - } - - private structuralTreeUpdate(): void { - // View size - this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize(this.model); - // Show groups only if there is more than 1 group - const treeInput = this.model.groups.length === 1 ? this.model.groups[0] : this.model; - // TODO@Isidor temporary workaround due to a partial tree refresh issue - this.fullRefreshNeeded = true; - const toRefresh = this.fullRefreshNeeded ? null : this.groupToRefresh; - (treeInput !== this.tree.getInput() ? this.tree.setInput(treeInput) : this.tree.refresh(toRefresh)).done(() => { - this.fullRefreshNeeded = false; - this.groupToRefresh = null; - - // Always expand all the groups as they are unclickable - return this.tree.expandAll(this.model.groups).then(() => this.highlightActiveEditor()); - }, errors.onUnexpectedError); + const newElement = e.editor ? new OpenEditor(e.editor, e.group) : e.group; + const index = this.getIndex(e.group, e.editor); + this.list.splice(index, 1, [newElement]); + this.focusActiveEditor(); + } } - private highlightActiveEditor(): void { + private focusActiveEditor(): void { if (this.model.activeGroup && this.model.activeGroup.activeEditor /* could be empty */) { - const openEditor = new OpenEditor(this.model.activeGroup.activeEditor, this.model.activeGroup); - this.tree.clearFocus(); - this.tree.clearSelection(); - - if (openEditor) { - this.tree.setFocus(openEditor); - this.tree.setSelection([openEditor]); - const relativeTop = this.tree.getRelativeTop(openEditor); - if (relativeTop <= 0 || relativeTop >= 1) { - // Only reveal the element if it is not visible #8279 - this.tree.reveal(openEditor).done(null, errors.onUnexpectedError); - } - } + const index = this.getIndex(this.model.activeGroup, this.model.activeGroup.activeEditor); + this.list.setFocus([index]); + this.list.setSelection([index]); + this.list.reveal(index); } } private onConfigurationChange(event: IConfigurationChangeEvent): void { - if (this.isDisposed) { - return; // guard against possible race condition when config change causes recreate of views - } - if (event.affectsConfiguration('explorer.openEditors')) { this.updateSize(); } // Trigger a 'repaint' when decoration settings change if (event.affectsConfiguration('explorer.decorations')) { - this.tree.refresh(); + this.listRefreshScheduler.schedule(); } } @@ -304,7 +429,7 @@ export class OpenEditorsView extends TreeViewsViewletPanel { itemsToShow = Math.max(visibleOpenEditors, 1); } - return itemsToShow * Renderer.ITEM_HEIGHT; + return itemsToShow * OpenEditorsDelegate.ITEM_HEIGHT; } public setStructuralRefreshDelay(delay: number): void { @@ -312,9 +437,209 @@ export class OpenEditorsView extends TreeViewsViewletPanel { } public getOptimalWidth(): number { - let parentNode = this.tree.getHTMLElement(); + let parentNode = this.list.getHTMLElement(); let childNodes = [].slice.call(parentNode.querySelectorAll('.open-editor > a')); return dom.getLargestChildWidth(parentNode, childNodes); } } + +interface IOpenEditorTemplateData { + container: HTMLElement; + root: EditorLabel; + actionBar: ActionBar; +} + +interface IEditorGroupTemplateData { + root: HTMLElement; + name: HTMLSpanElement; + actionBar: ActionBar; +} + +class OpenEditorsDelegate implements IDelegate { + + public static readonly ITEM_HEIGHT = 22; + + getHeight(element: OpenEditor | IEditorGroup): number { + return OpenEditorsDelegate.ITEM_HEIGHT; + } + + getTemplateId(element: OpenEditor | IEditorGroup): string { + if (element instanceof EditorGroup) { + return EditorGroupRenderer.ID; + } + + return OpenEditorRenderer.ID; + } +} + +class EditorGroupRenderer implements IRenderer { + static ID = 'editorgroup'; + + constructor( + private keybindingService: IKeybindingService, + private instantiationService: IInstantiationService + ) { + // noop + } + + get templateId() { + return EditorGroupRenderer.ID; + } + + renderTemplate(container: HTMLElement): IEditorGroupTemplateData { + const editorGroupTemplate: IEditorGroupTemplateData = Object.create(null); + editorGroupTemplate.root = dom.append(container, $('.editor-group')); + editorGroupTemplate.name = dom.append(editorGroupTemplate.root, $('span.name')); + editorGroupTemplate.actionBar = new ActionBar(container); + + const editorGroupActions = [ + this.instantiationService.createInstance(SaveAllInGroupAction, SaveAllInGroupAction.ID, SaveAllInGroupAction.LABEL), + this.instantiationService.createInstance(CloseUnmodifiedEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction.ID, CloseUnmodifiedEditorsInGroupAction.LABEL), + this.instantiationService.createInstance(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, CloseEditorsInGroupAction.LABEL) + ]; + editorGroupActions.forEach(a => { + const key = this.keybindingService.lookupKeybinding(a.id); + editorGroupTemplate.actionBar.push(a, { icon: true, label: false, keybinding: key ? key.getLabel() : void 0 }); + }); + + return editorGroupTemplate; + } + + renderElement(editorGroup: IEditorGroup, index: number, templateData: IEditorGroupTemplateData): void { + templateData.name.textContent = editorGroup.label; + templateData.actionBar.context = { group: editorGroup }; + } + + disposeTemplate(templateData: IEditorGroupTemplateData): void { + templateData.actionBar.dispose(); + } +} + +class OpenEditorRenderer implements IRenderer { + static ID = 'openeditor'; + + constructor( + private instantiationService: IInstantiationService, + private keybindingService: IKeybindingService, + private configurationService: IConfigurationService + ) { + // noop + } + + get templateId() { + return OpenEditorRenderer.ID; + } + + renderTemplate(container: HTMLElement): IOpenEditorTemplateData { + const editorTemplate: IOpenEditorTemplateData = Object.create(null); + editorTemplate.container = container; + editorTemplate.actionBar = new ActionBar(container); + + const closeEditorAction = this.instantiationService.createInstance(CloseEditorAction, CloseEditorAction.ID, CloseEditorAction.LABEL); + const key = this.keybindingService.lookupKeybinding(closeEditorAction.id); + editorTemplate.actionBar.push(closeEditorAction, { icon: true, label: false, keybinding: key ? key.getLabel() : void 0 }); + + editorTemplate.root = this.instantiationService.createInstance(EditorLabel, container, void 0); + + return editorTemplate; + } + + renderElement(editor: OpenEditor, index: number, templateData: IOpenEditorTemplateData): void { + editor.isDirty() ? dom.addClass(templateData.container, 'dirty') : dom.removeClass(templateData.container, 'dirty'); + templateData.root.setEditor(editor.editorInput, { + italic: editor.isPreview(), + extraClasses: ['open-editor'], + fileDecorations: this.configurationService.getValue().explorer.decorations + }); + templateData.actionBar.context = { group: editor.editorGroup, editor: editor.editorInput }; + } + + disposeTemplate(templateData: IOpenEditorTemplateData): void { + templateData.actionBar.dispose(); + templateData.root.dispose(); + } +} + +// export class DragAndDrop extends DefaultDragAndDrop { + +// constructor( +// @IWorkbenchEditorService private editorService: IWorkbenchEditorService, +// @IEditorGroupService private editorGroupService: IEditorGroupService +// ) { +// super(); +// } + +// public getDragURI(tree: ITree, element: OpenEditor): string { +// if (!(element instanceof OpenEditor)) { +// return null; +// } + +// const resource = element.getResource(); +// // Some open editors do not have a resource so use the name as drag identifier instead #7021 +// return resource ? resource.toString() : element.editorInput.getName(); +// } + +// public getDragLabel(tree: ITree, elements: OpenEditor[]): string { +// if (elements.length > 1) { +// return String(elements.length); +// } + +// return elements[0].editorInput.getName(); +// } + +// public onDragOver(tree: ITree, data: IDragAndDropData, target: OpenEditor | EditorGroup, originalEvent: DragMouseEvent): IDragOverReaction { +// if (!(target instanceof OpenEditor) && !(target instanceof EditorGroup)) { +// return DRAG_OVER_REJECT; +// } + +// if (data instanceof ExternalElementsDragAndDropData) { +// let resource = explorerItemToFileResource(data.getData()[0]); + +// if (!resource) { +// return DRAG_OVER_REJECT; +// } + +// return resource.isDirectory ? DRAG_OVER_REJECT : DRAG_OVER_ACCEPT; +// } + +// if (data instanceof DesktopDragAndDropData) { +// return DRAG_OVER_REJECT; +// } + +// if (!(data instanceof ElementsDragAndDropData)) { +// return DRAG_OVER_REJECT; +// } + +// return DRAG_OVER_ACCEPT; +// } + +// public drop(tree: ITree, data: IDragAndDropData, target: OpenEditor | EditorGroup, originalEvent: DragMouseEvent): void { +// let draggedElement: OpenEditor | EditorGroup; +// const model = this.editorGroupService.getStacksModel(); +// const positionOfTargetGroup = model.positionOfGroup(target instanceof EditorGroup ? target : target.editorGroup); +// const index = target instanceof OpenEditor ? target.editorGroup.indexOf(target.editorInput) : undefined; +// // Support drop from explorer viewer +// if (data instanceof ExternalElementsDragAndDropData) { +// let resource = explorerItemToFileResource(data.getData()[0]); +// (resource as IResourceInput).options = { index, pinned: true }; +// this.editorService.openEditor(resource, positionOfTargetGroup).done(null, errors.onUnexpectedError); +// } + +// // Drop within viewer +// else { +// let source: OpenEditor | EditorGroup[] = data.getData(); +// if (Array.isArray(source)) { +// draggedElement = source[0]; +// } +// } + +// if (draggedElement) { +// if (draggedElement instanceof OpenEditor) { +// this.editorGroupService.moveEditor(draggedElement.editorInput, model.positionOfGroup(draggedElement.editorGroup), positionOfTargetGroup, { index }); +// } else { +// this.editorGroupService.moveGroup(model.positionOfGroup(draggedElement), positionOfTargetGroup); +// } +// } +// } +// } \ No newline at end of file diff --git a/src/vs/workbench/parts/files/electron-browser/views/openEditorsViewer.ts b/src/vs/workbench/parts/files/electron-browser/views/openEditorsViewer.ts deleted file mode 100644 index 6c0ac532f9a2e1c7029772c5652103e657d261e2..0000000000000000000000000000000000000000 --- a/src/vs/workbench/parts/files/electron-browser/views/openEditorsViewer.ts +++ /dev/null @@ -1,521 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import nls = require('vs/nls'); -import errors = require('vs/base/common/errors'); -import { TPromise } from 'vs/base/common/winjs.base'; -import { IAction } from 'vs/base/common/actions'; -import { EditorLabel } from 'vs/workbench/browser/labels'; -import { DefaultController, ClickBehavior, DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults'; -import { IDataSource, ITree, IAccessibilityProvider, IDragAndDropData, IDragOverReaction, DRAG_OVER_ACCEPT, DRAG_OVER_REJECT, ContextMenuEvent, IRenderer } from 'vs/base/parts/tree/browser/tree'; -import { ExternalElementsDragAndDropData, ElementsDragAndDropData, DesktopDragAndDropData } from 'vs/base/parts/tree/browser/treeDnd'; -import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import dom = require('vs/base/browser/dom'); -import { IMouseEvent, DragMouseEvent } from 'vs/base/browser/mouseEvent'; -import { IResourceInput, Position } from 'vs/platform/editor/common/editor'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IEditorGroup, IEditorStacksModel } from 'vs/workbench/common/editor'; -import { OpenEditor } from 'vs/workbench/parts/files/common/explorerModel'; -import { ContributableActionProvider } from 'vs/workbench/browser/actions'; -import { explorerItemToFileResource, IFilesConfiguration } from 'vs/workbench/parts/files/common/files'; -import { ITextFileService, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; -import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { EditorStacksModel, EditorGroup } from 'vs/workbench/common/editor/editorStacksModel'; -import { SaveFileAction, RevertFileAction, SaveFileAsAction, OpenToSideAction, SelectResourceForCompareAction, CompareResourcesAction, SaveAllInGroupAction, CompareWithSavedAction } from 'vs/workbench/parts/files/electron-browser/fileActions'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { CloseOtherEditorsInGroupAction, CloseEditorAction, CloseEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction } from 'vs/workbench/browser/parts/editor/editorActions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; - -const $ = dom.$; - -export class DataSource implements IDataSource { - - public getId(tree: ITree, element: any): string { - if (element instanceof EditorStacksModel) { - return 'root'; - } - if (element instanceof EditorGroup) { - return (element).id.toString(); - } - - return (element).getId(); - } - - public hasChildren(tree: ITree, element: any): boolean { - return element instanceof EditorStacksModel || element instanceof EditorGroup; - } - - public getChildren(tree: ITree, element: any): TPromise { - if (element instanceof EditorStacksModel) { - return TPromise.as((element).groups); - } - - const editorGroup = element; - return TPromise.as(editorGroup.getEditors().map(ei => new OpenEditor(ei, editorGroup))); - } - - public getParent(tree: ITree, element: any): TPromise { - return TPromise.as(null); - } -} - -interface IOpenEditorTemplateData { - container: HTMLElement; - root: EditorLabel; - actionBar: ActionBar; -} - -interface IEditorGroupTemplateData { - root: HTMLElement; - name: HTMLSpanElement; - actionBar: ActionBar; -} - -export class Renderer implements IRenderer { - - public static readonly ITEM_HEIGHT = 22; - private static readonly EDITOR_GROUP_TEMPLATE_ID = 'editorgroup'; - private static readonly OPEN_EDITOR_TEMPLATE_ID = 'openeditor'; - - constructor( - private actionProvider: ActionProvider, - @IInstantiationService private instantiationService: IInstantiationService, - @IKeybindingService private keybindingService: IKeybindingService, - @IConfigurationService private configurationService: IConfigurationService - ) { - // noop - } - - public getHeight(tree: ITree, element: any): number { - return Renderer.ITEM_HEIGHT; - } - - public getTemplateId(tree: ITree, element: any): string { - if (element instanceof EditorGroup) { - return Renderer.EDITOR_GROUP_TEMPLATE_ID; - } - - return Renderer.OPEN_EDITOR_TEMPLATE_ID; - } - - public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any { - if (templateId === Renderer.EDITOR_GROUP_TEMPLATE_ID) { - const editorGroupTemplate: IEditorGroupTemplateData = Object.create(null); - editorGroupTemplate.root = dom.append(container, $('.editor-group')); - editorGroupTemplate.name = dom.append(editorGroupTemplate.root, $('span.name')); - editorGroupTemplate.actionBar = new ActionBar(container); - - const editorGroupActions = this.actionProvider.getEditorGroupActions(); - editorGroupActions.forEach(a => { - const key = this.keybindingService.lookupKeybinding(a.id); - editorGroupTemplate.actionBar.push(a, { icon: true, label: false, keybinding: key ? key.getLabel() : void 0 }); - }); - - return editorGroupTemplate; - } - - const editorTemplate: IOpenEditorTemplateData = Object.create(null); - editorTemplate.container = container; - editorTemplate.actionBar = new ActionBar(container); - - const openEditorActions = this.actionProvider.getOpenEditorActions(); - openEditorActions.forEach(a => { - const key = this.keybindingService.lookupKeybinding(a.id); - editorTemplate.actionBar.push(a, { icon: true, label: false, keybinding: key ? key.getLabel() : void 0 }); - }); - - editorTemplate.root = this.instantiationService.createInstance(EditorLabel, container, void 0); - - return editorTemplate; - } - - public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void { - if (templateId === Renderer.EDITOR_GROUP_TEMPLATE_ID) { - this.renderEditorGroup(tree, element, templateData); - } else { - this.renderOpenEditor(tree, element, templateData); - } - } - - private renderEditorGroup(tree: ITree, editorGroup: IEditorGroup, templateData: IEditorGroupTemplateData): void { - templateData.name.textContent = editorGroup.label; - templateData.actionBar.context = { group: editorGroup }; - } - - private renderOpenEditor(tree: ITree, editor: OpenEditor, templateData: IOpenEditorTemplateData): void { - editor.isDirty() ? dom.addClass(templateData.container, 'dirty') : dom.removeClass(templateData.container, 'dirty'); - templateData.root.setEditor(editor.editorInput, { - italic: editor.isPreview(), - extraClasses: ['open-editor'], - fileDecorations: this.configurationService.getValue().explorer.decorations - }); - templateData.actionBar.context = { group: editor.editorGroup, editor: editor.editorInput }; - } - - public disposeTemplate(tree: ITree, templateId: string, templateData: any): void { - if (templateId === Renderer.OPEN_EDITOR_TEMPLATE_ID) { - (templateData).actionBar.dispose(); - (templateData).root.dispose(); - } - if (templateId === Renderer.EDITOR_GROUP_TEMPLATE_ID) { - (templateData).actionBar.dispose(); - } - } -} - -export class Controller extends DefaultController { - - constructor(private actionProvider: ActionProvider, private model: IEditorStacksModel, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IEditorGroupService private editorGroupService: IEditorGroupService, - @IContextMenuService private contextMenuService: IContextMenuService, - @ITelemetryService private telemetryService: ITelemetryService - ) { - super({ clickBehavior: ClickBehavior.ON_MOUSE_DOWN, keyboardSupport: false }); - } - - public onClick(tree: ITree, element: any, event: IMouseEvent): boolean { - - // Close opened editor on middle mouse click - if (element instanceof OpenEditor && event.browserEvent && event.browserEvent.button === 1 /* Middle Button */) { - const position = this.model.positionOfGroup(element.editorGroup); - - this.editorService.closeEditor(position, element.editorInput).done(null, errors.onUnexpectedError); - - return true; - } - - return super.onClick(tree, element, event); - } - - protected onLeftClick(tree: ITree, element: any, event: IMouseEvent, origin: string = 'mouse'): boolean { - const payload = { origin: origin }; - const isDoubleClick = (origin === 'mouse' && event.detail === 2); - - // Cancel Event - const isMouseDown = event && event.browserEvent && event.browserEvent.type === 'mousedown'; - if (!isMouseDown) { - event.preventDefault(); // we cannot preventDefault onMouseDown because this would break DND otherwise - } - event.stopPropagation(); - - // Status group should never get selected nor expanded/collapsed - if (!(element instanceof OpenEditor)) { - return true; - } - - // Set DOM focus - tree.DOMFocus(); - - // Allow to unselect - if (event.shiftKey) { - const selection = tree.getSelection(); - if (selection && selection.length > 0 && selection[0] === element) { - tree.clearSelection(payload); - } - } - - // Select, Focus and open files - else { - tree.setFocus(element, payload); - - if (isDoubleClick) { - event.preventDefault(); // focus moves to editor, we need to prevent default - } - - tree.setSelection([element], payload); - this.openEditor(element, { preserveFocus: !isDoubleClick, pinned: isDoubleClick, sideBySide: event.ctrlKey || event.metaKey }); - } - - return true; - } - - // Do not allow left / right to expand and collapse groups #7848 - protected onLeft(tree: ITree, event: IKeyboardEvent): boolean { - return true; - } - - protected onRight(tree: ITree, event: IKeyboardEvent): boolean { - return true; - } - - public onContextMenu(tree: ITree, element: any, event: ContextMenuEvent): boolean { - if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') { - return false; - } - // Check if clicked on some element - if (element === tree.getInput()) { - return false; - } - - event.preventDefault(); - event.stopPropagation(); - - tree.setFocus(element); - const group = element instanceof EditorGroup ? element : (element).editorGroup; - const editor = element instanceof OpenEditor ? (element).editorInput : undefined; - - let anchor = { x: event.posx, y: event.posy }; - this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, - getActions: () => this.actionProvider.getSecondaryActions(tree, element), - onHide: (wasCancelled?: boolean) => { - if (wasCancelled) { - tree.DOMFocus(); - } - }, - getActionsContext: () => ({ group, editor }) - }); - - return true; - } - - public openEditor(element: OpenEditor, options: { preserveFocus: boolean; pinned: boolean; sideBySide: boolean; }): void { - if (element) { - /* __GDPR__ - "workbenchActionExecuted" : { - "id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('workbenchActionExecuted', { id: 'workbench.files.openFile', from: 'openEditors' }); - let position = this.model.positionOfGroup(element.editorGroup); - if (options.sideBySide && position !== Position.THREE) { - position++; - } - this.editorGroupService.activateGroup(this.model.groupAt(position)); - this.editorService.openEditor(element.editorInput, options, position) - .done(() => this.editorGroupService.activateGroup(this.model.groupAt(position)), errors.onUnexpectedError); - } - } -} - -export class AccessibilityProvider implements IAccessibilityProvider { - - getAriaLabel(tree: ITree, element: any): string { - if (element instanceof EditorGroup) { - return nls.localize('editorGroupAriaLabel', "{0}, Editor Group", (element).label); - } - - return nls.localize('openEditorAriaLabel', "{0}, Open Editor", (element).editorInput.getName()); - } -} - -export class ActionProvider extends ContributableActionProvider { - - constructor( - private model: IEditorStacksModel, - @IInstantiationService private instantiationService: IInstantiationService, - @ITextFileService private textFileService: ITextFileService, - @IUntitledEditorService private untitledEditorService: IUntitledEditorService - ) { - super(); - } - - public hasActions(tree: ITree, element: any): boolean { - const multipleGroups = this.model.groups.length > 1; - return element instanceof OpenEditor || (element instanceof EditorGroup && multipleGroups); - } - - public getActions(tree: ITree, element: any): TPromise { - if (element instanceof OpenEditor) { - return TPromise.as(this.getOpenEditorActions()); - } - if (element instanceof EditorGroup) { - return TPromise.as(this.getEditorGroupActions()); - } - - return TPromise.as([]); - } - - public getOpenEditorActions(): IAction[] { - return [this.instantiationService.createInstance(CloseEditorAction, CloseEditorAction.ID, CloseEditorAction.LABEL)]; - } - - public getEditorGroupActions(): IAction[] { - const saveAllAction = this.instantiationService.createInstance(SaveAllInGroupAction, SaveAllInGroupAction.ID, SaveAllInGroupAction.LABEL); - - return [ - saveAllAction, - this.instantiationService.createInstance(CloseUnmodifiedEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction.ID, CloseUnmodifiedEditorsInGroupAction.LABEL), - this.instantiationService.createInstance(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, CloseEditorsInGroupAction.LABEL) - ]; - } - - public hasSecondaryActions(tree: ITree, element: any): boolean { - return element instanceof OpenEditor || element instanceof EditorGroup; - } - - public getSecondaryActions(tree: ITree, element: any): TPromise { - return super.getSecondaryActions(tree, element).then(result => { - const autoSaveEnabled = this.textFileService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY; - - if (element instanceof EditorGroup) { - if (!autoSaveEnabled) { - result.push(this.instantiationService.createInstance(SaveAllInGroupAction, SaveAllInGroupAction.ID, nls.localize('saveAll', "Save All"))); - result.push(new Separator()); - } - - result.push(this.instantiationService.createInstance(CloseUnmodifiedEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction.ID, nls.localize('closeAllUnmodified', "Close Unmodified"))); - result.push(this.instantiationService.createInstance(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, nls.localize('closeAll', "Close All"))); - } else { - const openEditor = element; - const resource = openEditor.getResource(); - if (resource) { - // Open to side - result.unshift(this.instantiationService.createInstance(OpenToSideAction, tree, resource, false)); - - if (!openEditor.isUntitled()) { - - // Files: Save / Revert - if (!autoSaveEnabled) { - result.push(new Separator()); - - const saveAction = this.instantiationService.createInstance(SaveFileAction, SaveFileAction.ID, SaveFileAction.LABEL); - saveAction.setResource(resource); - saveAction.enabled = openEditor.isDirty(); - result.push(saveAction); - - const revertAction = this.instantiationService.createInstance(RevertFileAction, RevertFileAction.ID, RevertFileAction.LABEL); - revertAction.setResource(resource); - revertAction.enabled = openEditor.isDirty(); - result.push(revertAction); - } - } - - // Untitled: Save / Save As - if (openEditor.isUntitled()) { - result.push(new Separator()); - - if (this.untitledEditorService.hasAssociatedFilePath(resource)) { - let saveUntitledAction = this.instantiationService.createInstance(SaveFileAction, SaveFileAction.ID, SaveFileAction.LABEL); - saveUntitledAction.setResource(resource); - result.push(saveUntitledAction); - } - - let saveAsAction = this.instantiationService.createInstance(SaveFileAsAction, SaveFileAsAction.ID, SaveFileAsAction.LABEL); - saveAsAction.setResource(resource); - result.push(saveAsAction); - } - - // Compare Actions - result.push(new Separator()); - - if (!openEditor.isUntitled()) { - const compareWithSavedAction = this.instantiationService.createInstance(CompareWithSavedAction, CompareWithSavedAction.ID, nls.localize('compareWithSaved', "Compare with Saved")); - compareWithSavedAction.setResource(resource); - compareWithSavedAction.enabled = openEditor.isDirty(); - result.push(compareWithSavedAction); - } - - const runCompareAction = this.instantiationService.createInstance(CompareResourcesAction, resource, tree); - if (runCompareAction._isEnabled()) { - result.push(runCompareAction); - } - result.push(this.instantiationService.createInstance(SelectResourceForCompareAction, resource, tree)); - - result.push(new Separator()); - } - - result.push(this.instantiationService.createInstance(CloseEditorAction, CloseEditorAction.ID, nls.localize('close', "Close"))); - const closeOtherEditorsInGroupAction = this.instantiationService.createInstance(CloseOtherEditorsInGroupAction, CloseOtherEditorsInGroupAction.ID, nls.localize('closeOthers', "Close Others")); - closeOtherEditorsInGroupAction.enabled = openEditor.editorGroup.count > 1; - result.push(closeOtherEditorsInGroupAction); - result.push(this.instantiationService.createInstance(CloseUnmodifiedEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction.ID, nls.localize('closeAllUnmodified', "Close Unmodified"))); - result.push(this.instantiationService.createInstance(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, nls.localize('closeAll', "Close All"))); - } - - return result; - }); - } -} - -export class DragAndDrop extends DefaultDragAndDrop { - - constructor( - @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IEditorGroupService private editorGroupService: IEditorGroupService - ) { - super(); - } - - public getDragURI(tree: ITree, element: OpenEditor): string { - if (!(element instanceof OpenEditor)) { - return null; - } - - const resource = element.getResource(); - // Some open editors do not have a resource so use the name as drag identifier instead #7021 - return resource ? resource.toString() : element.editorInput.getName(); - } - - public getDragLabel(tree: ITree, elements: OpenEditor[]): string { - if (elements.length > 1) { - return String(elements.length); - } - - return elements[0].editorInput.getName(); - } - - public onDragOver(tree: ITree, data: IDragAndDropData, target: OpenEditor | EditorGroup, originalEvent: DragMouseEvent): IDragOverReaction { - if (!(target instanceof OpenEditor) && !(target instanceof EditorGroup)) { - return DRAG_OVER_REJECT; - } - - if (data instanceof ExternalElementsDragAndDropData) { - let resource = explorerItemToFileResource(data.getData()[0]); - - if (!resource) { - return DRAG_OVER_REJECT; - } - - return resource.isDirectory ? DRAG_OVER_REJECT : DRAG_OVER_ACCEPT; - } - - if (data instanceof DesktopDragAndDropData) { - return DRAG_OVER_REJECT; - } - - if (!(data instanceof ElementsDragAndDropData)) { - return DRAG_OVER_REJECT; - } - - return DRAG_OVER_ACCEPT; - } - - public drop(tree: ITree, data: IDragAndDropData, target: OpenEditor | EditorGroup, originalEvent: DragMouseEvent): void { - let draggedElement: OpenEditor | EditorGroup; - const model = this.editorGroupService.getStacksModel(); - const positionOfTargetGroup = model.positionOfGroup(target instanceof EditorGroup ? target : target.editorGroup); - const index = target instanceof OpenEditor ? target.editorGroup.indexOf(target.editorInput) : undefined; - // Support drop from explorer viewer - if (data instanceof ExternalElementsDragAndDropData) { - let resource = explorerItemToFileResource(data.getData()[0]); - (resource as IResourceInput).options = { index, pinned: true }; - this.editorService.openEditor(resource, positionOfTargetGroup).done(null, errors.onUnexpectedError); - } - - // Drop within viewer - else { - let source: OpenEditor | EditorGroup[] = data.getData(); - if (Array.isArray(source)) { - draggedElement = source[0]; - } - } - - if (draggedElement) { - if (draggedElement instanceof OpenEditor) { - this.editorGroupService.moveEditor(draggedElement.editorInput, model.positionOfGroup(draggedElement.editorGroup), positionOfTargetGroup, { index }); - } else { - this.editorGroupService.moveGroup(model.positionOfGroup(draggedElement), positionOfTargetGroup); - } - } - } -}