Feature Request: "Close all but this" context menu option for working files (fixes #2643)

This commit is contained in:
Benjamin Pasero 2016-02-04 09:47:39 +01:00
parent f3e6aed456
commit 238c707342
5 changed files with 162 additions and 47 deletions

View file

@ -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);

View file

@ -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<Files.ITextFileOperationResult> = 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);
});
}
}

View file

@ -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 {

View file

@ -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)
];
}

View file

@ -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);