diff --git a/src/vs/workbench/parts/files/browser/fileActions.contribution.ts b/src/vs/workbench/parts/files/browser/fileActions.contribution.ts index e184a1d0189..9e7b942fca1 100644 --- a/src/vs/workbench/parts/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/parts/files/browser/fileActions.contribution.ts @@ -10,7 +10,7 @@ import {Action, IAction} from 'vs/base/common/actions'; import {ActionItem, BaseActionItem, Separator} from 'vs/base/browser/ui/actionbar/actionbar'; import {Scope, IActionBarRegistry, Extensions as ActionBarExtensions, ActionBarContributor} from 'vs/workbench/browser/actionBarRegistry'; import {IEditorInputActionContext, IEditorInputAction, EditorInputActionContributor} from 'vs/workbench/browser/parts/editor/baseEditor'; -import {AddToWorkingFiles, FocusWorkingFiles, OpenPreviousWorkingFile, OpenNextWorkingFile, CloseAllFilesAction, CloseFileAction, GlobalCompareResourcesAction, GlobalNewFolderAction, RevertFileAction, SaveFilesAction, SaveAllAction, SaveFileAction, keybindingForAction, MoveFileToTrashAction, TriggerRenameFileAction, PasteFileAction, CopyFileAction, SelectResourceForCompareAction, CompareResourcesAction, NewFolderAction, NewFileAction, OpenToSideAction} from 'vs/workbench/parts/files/browser/fileActions'; +import {AddToWorkingFiles, FocusWorkingFiles, OpenPreviousWorkingFile, OpenNextWorkingFile, CloseAllFilesAction, CloseFileAction, CloseOtherFilesAction, GlobalCompareResourcesAction, GlobalNewFolderAction, RevertFileAction, SaveFilesAction, SaveAllAction, SaveFileAction, keybindingForAction, MoveFileToTrashAction, TriggerRenameFileAction, PasteFileAction, CopyFileAction, SelectResourceForCompareAction, CompareResourcesAction, NewFolderAction, NewFileAction, OpenToSideAction} from 'vs/workbench/parts/files/browser/fileActions'; import {RevertLocalChangesAction, AcceptLocalChangesAction, ConflictResolutionDiffEditorInput} from 'vs/workbench/parts/files/browser/saveErrorHandler'; import {SyncActionDescriptor} from 'vs/platform/actions/common/actions'; import {IWorkbenchActionRegistry, Extensions as ActionExtensions} from 'vs/workbench/common/actionRegistry'; @@ -169,6 +169,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(RevertFileAction, Reve registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewFolderAction, GlobalNewFolderAction.ID, GlobalNewFolderAction.LABEL), category); registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalCompareResourcesAction, GlobalCompareResourcesAction.ID, GlobalCompareResourcesAction.LABEL), category); registry.registerWorkbenchAction(new SyncActionDescriptor(CloseFileAction, CloseFileAction.ID, CloseFileAction.LABEL, { primary: KeyMod.chord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W) }), category); +registry.registerWorkbenchAction(new SyncActionDescriptor(CloseOtherFilesAction, CloseOtherFilesAction.ID, CloseOtherFilesAction.LABEL, { primary: KeyMod.chord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W) }), category); registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllFilesAction, CloseAllFilesAction.ID, CloseAllFilesAction.LABEL, { primary: KeyMod.chord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_W) }), category); registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextWorkingFile, OpenNextWorkingFile.ID, OpenNextWorkingFile.LABEL, { primary: KeyMod.chord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.DownArrow) }), category); registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousWorkingFile, OpenPreviousWorkingFile.ID, OpenPreviousWorkingFile.LABEL, { primary: KeyMod.chord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.UpArrow) }), category); diff --git a/src/vs/workbench/parts/files/browser/fileActions.ts b/src/vs/workbench/parts/files/browser/fileActions.ts index 18662d5a0fc..3f5a2d6e5f8 100644 --- a/src/vs/workbench/parts/files/browser/fileActions.ts +++ b/src/vs/workbench/parts/files/browser/fileActions.ts @@ -1794,58 +1794,55 @@ export class OpenResourcesAction extends Action { } } -export class CloseWorkingFileAction extends Action { - - public static ID = 'workbench.files.action.closeWorkingFiles'; - - private model: WorkingFilesModel; - private element: WorkingFileEntry; - private listenerToDispose: IDisposable; +export abstract class BaseCloseWorkingFileAction extends Action { + protected model: WorkingFilesModel; + private elements: URI[]; constructor( + id: string, + label: string, + clazz: string, model: WorkingFilesModel, - element: WorkingFileEntry, + elements: WorkingFileEntry[], @IUntitledEditorService private untitledEditorService: IUntitledEditorService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @ITextFileService private textFileService: ITextFileService, @IMessageService private messageService: IMessageService, @IQuickOpenService private quickOpenService: IQuickOpenService ) { - super(CloseWorkingFileAction.ID, element ? nls.localize('closeLabel', "Close File") : nls.localize('closeAllLabel', "Close All Files"), element ? (element.dirty ? 'action-close-dirty-file' : 'action-close-file') : 'action-close-all-files'); + super(id, label, clazz); this.model = model; - this.element = element; - - if (this.model) { - this.enabled = (this.model.count() > 0); - this.listenerToDispose = this.model.onModelChange(this.onModelChange, this); - } - } - - private onModelChange(event: Files.IWorkingFileModelChangeEvent): void { - this.enabled = (this.model.count() > 0); + this.elements = elements ? elements.map(e => e.resource) : void 0 /* all */; } public run(): Promise { let workingFilesCount = this.model.getEntries().length; // Handle dirty + let isDirty: boolean; + if (this.elements) { + isDirty = this.elements.some(e => this.textFileService.isDirty(e)); + } else { + isDirty = this.textFileService.isDirty(); + } + let saveOrRevertPromise: TPromise = Promise.as(null); - if (this.textFileService.isDirty(this.element ? this.element.resource : void 0 /* all */)) { - let confirmResult = this.textFileService.confirmSave(this.element ? [this.element.resource] : void 0 /* all */); + if (isDirty) { + let confirmResult = this.textFileService.confirmSave(this.elements); switch (confirmResult) { case Files.ConfirmResult.SAVE: - if (this.element) { - saveOrRevertPromise = this.textFileService.saveAll([this.element.resource]); + if (this.elements) { + saveOrRevertPromise = this.textFileService.saveAll(this.elements); } else { saveOrRevertPromise = this.textFileService.saveAll(true /* include untitled */); } break; case Files.ConfirmResult.DONT_SAVE: - if (this.element) { - saveOrRevertPromise = this.textFileService.revertAll([this.element.resource]); + if (this.elements) { + saveOrRevertPromise = this.textFileService.revertAll(this.elements); } else { saveOrRevertPromise = this.textFileService.revertAll(); } @@ -1860,8 +1857,8 @@ export class CloseWorkingFileAction extends Action { // Collect resources to dispose let resourcesToDispose: URI[] = []; - if (this.element) { - resourcesToDispose.push(this.element.resource); + if (this.elements) { + resourcesToDispose = this.elements; } else { resourcesToDispose = this.model.getEntries().map((e) => e.resource); } @@ -1901,6 +1898,31 @@ export class CloseWorkingFileAction extends Action { input.dispose(); } } +}; + +export class CloseAllWorkingFilesAction extends BaseCloseWorkingFileAction { + + public static ID = 'workbench.files.action.closeAllWorkingFiles'; + + private listenerToDispose: IDisposable; + + constructor( + model: WorkingFilesModel, + @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService, + @ITextFileService textFileService: ITextFileService, + @IMessageService messageService: IMessageService, + @IQuickOpenService quickOpenService: IQuickOpenService + ) { + super(CloseAllWorkingFilesAction.ID, nls.localize('closeAllLabel', "Close All Files"), 'action-close-all-files', model, null, untitledEditorService, editorService, textFileService, messageService, quickOpenService); + + this.enabled = (model.count() > 0); + this.listenerToDispose = model.onModelChange(this.onModelChange, this); + } + + private onModelChange(event: Files.IWorkingFileModelChangeEvent): void { + this.enabled = (this.model.count() > 0); + } public dispose(): void { if (this.listenerToDispose) { @@ -1910,7 +1932,60 @@ export class CloseWorkingFileAction extends Action { super.dispose(); } -}; +} + +export class CloseOneWorkingFileAction extends BaseCloseWorkingFileAction { + + public static ID = 'workbench.files.action.closeOneWorkingFile'; + + constructor( + model: WorkingFilesModel, + element: WorkingFileEntry, + @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService, + @ITextFileService textFileService: ITextFileService, + @IMessageService messageService: IMessageService, + @IQuickOpenService quickOpenService: IQuickOpenService + ) { + super(CloseAllWorkingFilesAction.ID, nls.localize('closeLabel', "Close File"), element.dirty ? 'action-close-dirty-file' : 'action-close-file', model, [element], untitledEditorService, editorService, textFileService, messageService, quickOpenService); + } +} + +export class CloseOtherWorkingFilesAction extends BaseCloseWorkingFileAction { + + public static ID = 'workbench.files.action.closeOtherWorkingFiles'; + + constructor( + model: WorkingFilesModel, + element: WorkingFileEntry, + @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService, + @ITextFileService textFileService: ITextFileService, + @IMessageService messageService: IMessageService, + @IQuickOpenService quickOpenService: IQuickOpenService + ) { + super(CloseAllWorkingFilesAction.ID, nls.localize('closeOtherLabel', "Close Other Files"), 'action-close-file', model, model.getEntries().filter(e => e !== element), untitledEditorService, editorService, textFileService, messageService, quickOpenService); + } +} + +function disposeNonDirtyFileInputs(editorService: IWorkbenchEditorService, quickopenService: IQuickOpenService, textFileService: ITextFileService, exclude?: URI): void { + let activeFileInputs = editorService.getVisibleEditors().map(e => workbenchEditorCommon.asFileEditorInput(e.input, true)).filter(i => i instanceof FileEditorInput); + activeFileInputs.forEach((f: FileEditorInput) => { + if (exclude && exclude.toString() === f.getResource().toString()) { + return; // excluded + } + + if (textFileService.isDirty(f.getResource())) { + return; // do not touch dirty + } + + fileEditorInputsForResource(f.getResource(), editorService, quickopenService).forEach(i => { + if (!i.isDisposed()) { + i.dispose(true); + } + }); + }); +} function fileEditorInputsForResource(resource: URI, editorService: IWorkbenchEditorService, quickopenService: IQuickOpenService): FileEditorInput[] { @@ -1966,7 +2041,7 @@ export class CloseFileAction extends Action { // Use action to close a working file that will take care of everthing if (entry) { - let closeAction = this.instantiationService.createInstance(CloseWorkingFileAction, model, entry); + let closeAction = this.instantiationService.createInstance(CloseOneWorkingFileAction, model, entry); closeAction.run().done(() => closeAction.dispose(), errors.onUnexpectedError); } @@ -2001,6 +2076,48 @@ export class CloseFileAction extends Action { } } +export class CloseOtherFilesAction extends Action { + + public static ID = 'workbench.files.action.closeOtherFiles'; + public static LABEL = nls.localize('closeOtherFiles', "Close Other Files"); + + constructor( + id: string, + label: string, + @IInstantiationService private instantiationService: IInstantiationService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @ITextFileService private textFileService: ITextFileService, + @IMessageService private messageService: IMessageService, + @IQuickOpenService private quickOpenService: IQuickOpenService + ) { + super(id, label); + } + + public run(): Promise { + const workingFilesModel = this.textFileService.getWorkingFilesModel(); + + let activeResource = workbenchEditorCommon.getUntitledOrFileResource(this.editorService.getActiveEditorInput(), true); + let actionToRun: IAction; + + // Close all but active resource + if (activeResource && workingFilesModel.hasEntry(activeResource)) { + actionToRun = this.instantiationService.createInstance(CloseOtherWorkingFilesAction, workingFilesModel, workingFilesModel.findEntry(activeResource)); + } + + // Without active resource: Close all + else { + actionToRun = this.instantiationService.createInstance(CloseAllWorkingFilesAction, workingFilesModel); + } + + return actionToRun.run().then(() => { + actionToRun.dispose(); + + // Dispose remaining non dirty ones except for active one + disposeNonDirtyFileInputs(this.editorService, this.quickOpenService, this.textFileService, activeResource); + }); + } +} + export class CloseAllFilesAction extends Action { public static ID = 'workbench.files.action.closeAllFiles'; @@ -2019,23 +2136,14 @@ export class CloseAllFilesAction extends Action { } public run(): Promise { - let activeFileInputs = this.editorService.getVisibleEditors().map(e => workbenchEditorCommon.asFileEditorInput(e.input, true)).filter(i => i instanceof FileEditorInput); // Close all Working Files - let closeAction = this.instantiationService.createInstance(CloseWorkingFileAction, this.textFileService.getWorkingFilesModel(), null); + let closeAction = this.instantiationService.createInstance(CloseAllWorkingFilesAction, this.textFileService.getWorkingFilesModel()); return closeAction.run().then(() => { closeAction.dispose(); // Dispose remaining non dirty ones - activeFileInputs.forEach((f: FileEditorInput) => { - if (!this.textFileService.isDirty(f.getResource())) { - fileEditorInputsForResource(f.getResource(), this.editorService, this.quickOpenService).forEach(i => { - if (!i.isDisposed()) { - i.dispose(true); - } - }); - } - }); + disposeNonDirtyFileInputs(this.editorService, this.quickOpenService, this.textFileService); }); } } diff --git a/src/vs/workbench/parts/files/browser/media/explorerviewlet.css b/src/vs/workbench/parts/files/browser/media/explorerviewlet.css index 18409adc1df..d3265abf4f2 100644 --- a/src/vs/workbench/parts/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/parts/files/browser/media/explorerviewlet.css @@ -27,6 +27,7 @@ .explorer-viewlet .explorer-working-files .monaco-tree .monaco-tree-row > .content.actions.working-file-dirty > .primary-action-bar { display: block; + width: initial; } .explorer-viewlet .explorer-working-files .monaco-tree .monaco-tree-row > .content.actions > .sub-content { diff --git a/src/vs/workbench/parts/files/browser/views/workingFilesView.ts b/src/vs/workbench/parts/files/browser/views/workingFilesView.ts index e4c32990da1..bfe6f722dc4 100644 --- a/src/vs/workbench/parts/files/browser/views/workingFilesView.ts +++ b/src/vs/workbench/parts/files/browser/views/workingFilesView.ts @@ -18,7 +18,7 @@ import {IDisposable} from 'vs/base/common/lifecycle'; import errors = require('vs/base/common/errors'); import {EventType as WorkbenchEventType, UntitledEditorEvent, EditorEvent} from 'vs/workbench/common/events'; import {AdaptiveCollapsibleViewletView} from 'vs/workbench/browser/viewlet'; -import {CloseWorkingFileAction, SaveAllAction} from 'vs/workbench/parts/files/browser/fileActions'; +import {CloseAllWorkingFilesAction, SaveAllAction} from 'vs/workbench/parts/files/browser/fileActions'; import {WorkingFileEntry} from 'vs/workbench/parts/files/common/workingFilesModel'; import {WorkingFilesDragAndDrop, WorkingFilesSorter, WorkingFilesController, WorkingFilesDataSource, WorkingFilesRenderer, WorkingFilesAccessibilityProvider, WorkingFilesActionProvider} from 'vs/workbench/parts/files/browser/views/workingFilesViewer'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; @@ -76,7 +76,7 @@ export class WorkingFilesView extends AdaptiveCollapsibleViewletView { public getActions(): IAction[] { return [ this.instantiationService.createInstance(SaveAllAction, SaveAllAction.ID, SaveAllAction.LABEL), - this.instantiationService.createInstance(CloseWorkingFileAction, this.model, null) + this.instantiationService.createInstance(CloseAllWorkingFilesAction, this.model) ]; } diff --git a/src/vs/workbench/parts/files/browser/views/workingFilesViewer.ts b/src/vs/workbench/parts/files/browser/views/workingFilesViewer.ts index d8f22a430ea..5f61a0ae1de 100644 --- a/src/vs/workbench/parts/files/browser/views/workingFilesViewer.ts +++ b/src/vs/workbench/parts/files/browser/views/workingFilesViewer.ts @@ -22,7 +22,7 @@ import {Separator} from 'vs/base/browser/ui/actionbar/actionbar'; import actions = require('vs/base/common/actions'); import {ActionsRenderer} from 'vs/base/parts/tree/browser/actionsRenderer'; import {ContributableActionProvider} from 'vs/workbench/browser/actionBarRegistry'; -import {keybindingForAction, CloseWorkingFileAction, SelectResourceForCompareAction, CompareResourcesAction, SaveFileAsAction, SaveFileAction, RevertFileAction, OpenToSideAction} from 'vs/workbench/parts/files/browser/fileActions'; +import {keybindingForAction, CloseOneWorkingFileAction, CloseOtherWorkingFilesAction, CloseAllWorkingFilesAction, SelectResourceForCompareAction, CompareResourcesAction, SaveFileAsAction, SaveFileAction, RevertFileAction, OpenToSideAction} from 'vs/workbench/parts/files/browser/fileActions'; import {asFileResource, ITextFileService, AutoSaveMode} from 'vs/workbench/parts/files/common/files'; import {WorkingFileEntry, WorkingFilesModel} from 'vs/workbench/parts/files/common/workingFilesModel'; import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; @@ -146,7 +146,7 @@ export class WorkingFilesActionProvider extends ContributableActionProvider { let actions: actions.IAction[] = []; if (element instanceof WorkingFileEntry) { - actions.push(this.instantiationService.createInstance(CloseWorkingFileAction, this.model, element)); + actions.push(this.instantiationService.createInstance(CloseOneWorkingFileAction, this.model, element)); } return Promise.as(actions); @@ -209,7 +209,12 @@ export class WorkingFilesActionProvider extends ContributableActionProvider { // Close actions.push(new Separator()); - actions.push(this.instantiationService.createInstance(CloseWorkingFileAction, this.model, element)); + actions.push(this.instantiationService.createInstance(CloseOneWorkingFileAction, this.model, element)); + actions.push(this.instantiationService.createInstance(CloseAllWorkingFilesAction, this.model)); + + if (this.model.count() > 1) { + actions.push(this.instantiationService.createInstance(CloseOtherWorkingFilesAction, this.model, element)); + } } return actions; @@ -340,7 +345,7 @@ export class WorkingFilesController extends DefaultController { // Close working file on middle mouse click if (element instanceof WorkingFileEntry && event.browserEvent && event.browserEvent.button === 1 /* Middle Button */) { - const closeAction = this.instantiationService.createInstance(CloseWorkingFileAction, this.model, element); + const closeAction = this.instantiationService.createInstance(CloseOneWorkingFileAction, this.model, element); closeAction.run().done(() => { closeAction.dispose(); }, errors.onUnexpectedError);