mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-31 09:10:30 -04:00
Merge from vscode 2b0b9136329c181a9e381463a1f7dc3a2d105a34 (#4880)
This commit is contained in:
@@ -12,7 +12,7 @@ import { ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/te
|
||||
import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files';
|
||||
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { distinct, coalesce } from 'vs/base/common/arrays';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
@@ -34,10 +34,9 @@ import { QueryInput } from 'sql/parts/query/common/queryInput';
|
||||
|
||||
export class FileEditorTracker extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
protected closeOnFileDelete: boolean;
|
||||
|
||||
private modelLoadQueue: ResourceQueue;
|
||||
private activeOutOfWorkspaceWatchers: ResourceMap<URI>;
|
||||
private closeOnFileDelete: boolean;
|
||||
private modelLoadQueue = new ResourceQueue();
|
||||
private activeOutOfWorkspaceWatchers = new ResourceMap<IDisposable>();
|
||||
|
||||
constructor(
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@@ -52,9 +51,6 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
|
||||
) {
|
||||
super();
|
||||
|
||||
this.modelLoadQueue = new ResourceQueue();
|
||||
this.activeOutOfWorkspaceWatchers = new ResourceMap<URI>();
|
||||
|
||||
this.onConfigurationUpdated(configurationService.getValue<IWorkbenchEditorConfiguration>());
|
||||
|
||||
this.registerListeners();
|
||||
@@ -358,9 +354,9 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
|
||||
});
|
||||
|
||||
// Handle no longer visible out of workspace resources
|
||||
this.activeOutOfWorkspaceWatchers.forEach(resource => {
|
||||
this.activeOutOfWorkspaceWatchers.keys().forEach(resource => {
|
||||
if (!visibleOutOfWorkspacePaths.get(resource)) {
|
||||
this.fileService.unwatch(resource);
|
||||
dispose(this.activeOutOfWorkspaceWatchers.get(resource));
|
||||
this.activeOutOfWorkspaceWatchers.delete(resource);
|
||||
}
|
||||
});
|
||||
@@ -368,8 +364,8 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
|
||||
// Handle newly visible out of workspace resources
|
||||
visibleOutOfWorkspacePaths.forEach(resource => {
|
||||
if (!this.activeOutOfWorkspaceWatchers.get(resource)) {
|
||||
this.fileService.watch(resource);
|
||||
this.activeOutOfWorkspaceWatchers.set(resource, resource);
|
||||
const disposable = this.fileService.watch(resource);
|
||||
this.activeOutOfWorkspaceWatchers.set(resource, disposable);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -377,8 +373,8 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
// Dispose watchers if any
|
||||
this.activeOutOfWorkspaceWatchers.forEach(resource => this.fileService.unwatch(resource));
|
||||
// Dispose remaining watchers if any
|
||||
this.activeOutOfWorkspaceWatchers.forEach(disposable => dispose(disposable));
|
||||
this.activeOutOfWorkspaceWatchers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ export class TextFileEditor extends BaseTextEditor {
|
||||
// React to editors closing to preserve or clear view state. This needs to happen
|
||||
// in the onWillCloseEditor because at that time the editor has not yet
|
||||
// been disposed and we can safely persist the view state still as needed.
|
||||
this.groupListener = dispose(this.groupListener);
|
||||
dispose(this.groupListener);
|
||||
this.groupListener = ((group as IEditorGroupView).onWillCloseEditor(e => this.onWillCloseEditorInGroup(e)));
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ export class TextFileEditor extends BaseTextEditor {
|
||||
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_IS_DIRECTORY) {
|
||||
this.openAsFolder(input);
|
||||
|
||||
return Promise.reject<any>(new Error(nls.localize('openFolderError', "File is a directory")));
|
||||
return Promise.reject(new Error(nls.localize('openFolderError', "File is a directory")));
|
||||
}
|
||||
|
||||
// Offer to create a file from the error if we have a file not found and the name is valid
|
||||
@@ -296,7 +296,7 @@ export class TextFileEditor extends BaseTextEditor {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.groupListener = dispose(this.groupListener);
|
||||
dispose(this.groupListener);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import 'vs/css!./media/explorerviewlet';
|
||||
import { localize } from 'vs/nls';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { VIEWLET_ID, ExplorerViewletVisibleContext, IFilesConfiguration, OpenEditorsVisibleContext, OpenEditorsVisibleCondition, VIEW_CONTAINER } from 'vs/workbench/contrib/files/common/files';
|
||||
import { VIEWLET_ID, ExplorerViewletVisibleContext, IFilesConfiguration, OpenEditorsVisibleContext, VIEW_CONTAINER } from 'vs/workbench/contrib/files/common/files';
|
||||
import { ViewContainerViewlet, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { ExplorerView } from 'vs/workbench/contrib/files/browser/views/explorerView';
|
||||
@@ -103,7 +103,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor
|
||||
name: OpenEditorsView.NAME,
|
||||
ctorDescriptor: { ctor: OpenEditorsView },
|
||||
order: 0,
|
||||
when: OpenEditorsVisibleCondition,
|
||||
when: OpenEditorsVisibleContext,
|
||||
canToggleVisibility: true,
|
||||
focusCommand: {
|
||||
id: 'workbench.files.action.focusOpenEditorsView',
|
||||
|
||||
@@ -15,7 +15,7 @@ import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/c
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { isWindows, isMacintosh } from 'vs/base/common/platform';
|
||||
import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, IExplorerService } from 'vs/workbench/contrib/files/common/files';
|
||||
import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, IExplorerService, ExplorerResourceMoveableToTrash } from 'vs/workbench/contrib/files/common/files';
|
||||
import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL } from 'vs/workbench/browser/actions/workspaceCommands';
|
||||
import { CLOSE_SAVED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { AutoSaveContext } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
@@ -63,7 +63,7 @@ const MOVE_FILE_TO_TRASH_ID = 'moveFileToTrash';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: MOVE_FILE_TO_TRASH_ID,
|
||||
weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus,
|
||||
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext, ContextKeyExpr.has('config.files.enableTrash')),
|
||||
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext, ExplorerResourceMoveableToTrash),
|
||||
primary: KeyCode.Delete,
|
||||
mac: {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Backspace
|
||||
@@ -75,7 +75,7 @@ const DELETE_FILE_ID = 'deleteFile';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: DELETE_FILE_ID,
|
||||
weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus,
|
||||
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext, ContextKeyExpr.has('config.files.enableTrash')),
|
||||
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext),
|
||||
primary: KeyMod.Shift | KeyCode.Delete,
|
||||
mac: {
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Backspace
|
||||
@@ -86,7 +86,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: DELETE_FILE_ID,
|
||||
weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus,
|
||||
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext, ContextKeyExpr.not('config.files.enableTrash')),
|
||||
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext, ExplorerResourceMoveableToTrash.toNegated()),
|
||||
primary: KeyCode.Delete,
|
||||
mac: {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Backspace
|
||||
@@ -517,7 +517,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
title: nls.localize('deleteFile', "Delete Permanently"),
|
||||
precondition: ExplorerResourceNotReadonlyContext
|
||||
},
|
||||
when: ContextKeyExpr.and(ExplorerRootContext.toNegated(), ContextKeyExpr.has('config.files.enableTrash'))
|
||||
when: ContextKeyExpr.and(ExplorerRootContext.toNegated(), ExplorerResourceMoveableToTrash)
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
@@ -528,7 +528,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
title: nls.localize('deleteFile', "Delete Permanently"),
|
||||
precondition: ExplorerResourceNotReadonlyContext
|
||||
},
|
||||
when: ContextKeyExpr.and(ExplorerRootContext.toNegated(), ContextKeyExpr.not('config.files.enableTrash'))
|
||||
when: ContextKeyExpr.and(ExplorerRootContext.toNegated(), ExplorerResourceMoveableToTrash.toNegated())
|
||||
});
|
||||
|
||||
// Empty Editor Group Context Menu
|
||||
|
||||
@@ -17,12 +17,12 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { VIEWLET_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IFileService, AutoSaveConfiguration } from 'vs/platform/files/common/files';
|
||||
import { toResource, IUntitledResourceInput, ITextEditor } from 'vs/workbench/common/editor';
|
||||
import { toResource, ITextEditor } from 'vs/workbench/common/editor';
|
||||
import { ExplorerViewlet } from 'vs/workbench/contrib/files/browser/explorerViewlet';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IInstantiationService, ServicesAccessor, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { REVEAL_IN_EXPLORER_COMMAND_ID, SAVE_ALL_COMMAND_ID, SAVE_ALL_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileCommands';
|
||||
@@ -68,36 +68,14 @@ export const PASTE_FILE_LABEL = nls.localize('pasteFile', "Paste");
|
||||
|
||||
export const FileCopiedContext = new RawContextKey<boolean>('fileCopied', false);
|
||||
|
||||
export class BaseErrorReportingAction extends Action {
|
||||
const CONFIRM_DELETE_SETTING_KEY = 'explorer.confirmDelete';
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private _notificationService: INotificationService
|
||||
) {
|
||||
super(id, label);
|
||||
function onError(notificationService: INotificationService, error: any): void {
|
||||
if (error.message === 'string') {
|
||||
error = error.message;
|
||||
}
|
||||
|
||||
public get notificationService() {
|
||||
return this._notificationService;
|
||||
}
|
||||
|
||||
protected onError(error: any): void {
|
||||
if (error.message === 'string') {
|
||||
error = error.message;
|
||||
}
|
||||
|
||||
this._notificationService.error(toErrorMessage(error, false));
|
||||
}
|
||||
|
||||
protected onErrorWithRetry(error: any, retry: () => Promise<any>): void {
|
||||
this._notificationService.prompt(Severity.Error, toErrorMessage(error, false),
|
||||
[{
|
||||
label: nls.localize('retry', "Retry"),
|
||||
run: () => retry()
|
||||
}]
|
||||
);
|
||||
}
|
||||
notificationService.error(toErrorMessage(error, false));
|
||||
}
|
||||
|
||||
function refreshIfSeparator(value: string, explorerService: IExplorerService): void {
|
||||
@@ -108,66 +86,26 @@ function refreshIfSeparator(value: string, explorerService: IExplorerService): v
|
||||
}
|
||||
|
||||
/* New File */
|
||||
export class NewFileAction extends BaseErrorReportingAction {
|
||||
export class NewFileAction extends Action {
|
||||
static readonly ID = 'workbench.files.action.createFileFromExplorer';
|
||||
static readonly LABEL = nls.localize('createNewFile', "New File");
|
||||
|
||||
private toDispose: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private getElement: () => ExplorerItem,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IExplorerService private explorerService: IExplorerService,
|
||||
@IFileService private fileService: IFileService,
|
||||
@IEditorService private editorService: IEditorService
|
||||
@IExplorerService explorerService: IExplorerService,
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super('explorer.newFile', NEW_FILE_LABEL, notificationService);
|
||||
super('explorer.newFile', NEW_FILE_LABEL);
|
||||
this.class = 'explorer-action new-file';
|
||||
this.toDispose.push(this.explorerService.onDidChangeEditable(e => {
|
||||
const elementIsBeingEdited = this.explorerService.isEditable(e);
|
||||
this.toDispose.push(explorerService.onDidChangeEditable(e => {
|
||||
const elementIsBeingEdited = explorerService.isEditable(e);
|
||||
this.enabled = !elementIsBeingEdited;
|
||||
}));
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
let folder: ExplorerItem;
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
folder = element.isDirectory ? element : element.parent!;
|
||||
} else {
|
||||
folder = this.explorerService.roots[0];
|
||||
}
|
||||
|
||||
if (folder.isReadonly) {
|
||||
return Promise.reject(new Error('Parent folder is readonly.'));
|
||||
}
|
||||
|
||||
const stat = new NewExplorerItem(folder, false);
|
||||
return folder.fetchChildren(this.fileService, this.explorerService).then(() => {
|
||||
folder.addChild(stat);
|
||||
|
||||
const onSuccess = (value: string) => {
|
||||
return this.fileService.createFile(resources.joinPath(folder.resource, value)).then(stat => {
|
||||
refreshIfSeparator(value, this.explorerService);
|
||||
return this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } });
|
||||
}, (error) => {
|
||||
this.onErrorWithRetry(error, () => onSuccess(value));
|
||||
});
|
||||
};
|
||||
|
||||
this.explorerService.setEditable(stat, {
|
||||
validationMessage: value => validateFileName(stat, value),
|
||||
onFinish: (value, success) => {
|
||||
folder.removeChild(stat);
|
||||
this.explorerService.setEditable(stat, null);
|
||||
if (success) {
|
||||
onSuccess(value);
|
||||
} else {
|
||||
this.explorerService.select(folder.resource).then(undefined, onUnexpectedError);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return this.commandService.executeCommand(NEW_FILE_COMMAND_ID);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
@@ -177,65 +115,26 @@ export class NewFileAction extends BaseErrorReportingAction {
|
||||
}
|
||||
|
||||
/* New Folder */
|
||||
export class NewFolderAction extends BaseErrorReportingAction {
|
||||
export class NewFolderAction extends Action {
|
||||
static readonly ID = 'workbench.files.action.createFolderFromExplorer';
|
||||
static readonly LABEL = nls.localize('createNewFolder', "New Folder");
|
||||
|
||||
private toDispose: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private getElement: () => ExplorerItem,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IFileService private fileService: IFileService,
|
||||
@IExplorerService private explorerService: IExplorerService
|
||||
@IExplorerService explorerService: IExplorerService,
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super('explorer.newFolder', NEW_FOLDER_LABEL, notificationService);
|
||||
super('explorer.newFolder', NEW_FOLDER_LABEL);
|
||||
this.class = 'explorer-action new-folder';
|
||||
this.toDispose.push(this.explorerService.onDidChangeEditable(e => {
|
||||
const elementIsBeingEdited = this.explorerService.isEditable(e);
|
||||
this.toDispose.push(explorerService.onDidChangeEditable(e => {
|
||||
const elementIsBeingEdited = explorerService.isEditable(e);
|
||||
this.enabled = !elementIsBeingEdited;
|
||||
}));
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
let folder: ExplorerItem;
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
folder = element.isDirectory ? element : element.parent!;
|
||||
} else {
|
||||
folder = this.explorerService.roots[0];
|
||||
}
|
||||
|
||||
if (folder.isReadonly) {
|
||||
return Promise.reject(new Error('Parent folder is readonly.'));
|
||||
}
|
||||
|
||||
const stat = new NewExplorerItem(folder, true);
|
||||
return folder.fetchChildren(this.fileService, this.explorerService).then(() => {
|
||||
folder.addChild(stat);
|
||||
|
||||
const onSuccess = (value: string) => {
|
||||
return this.fileService.createFolder(resources.joinPath(folder.resource, value)).then(stat => {
|
||||
refreshIfSeparator(value, this.explorerService);
|
||||
return this.explorerService.select(stat.resource, true);
|
||||
}, (error) => {
|
||||
this.onErrorWithRetry(error, () => onSuccess(value));
|
||||
});
|
||||
};
|
||||
|
||||
this.explorerService.setEditable(stat, {
|
||||
validationMessage: value => validateFileName(stat, value),
|
||||
onFinish: (value, success) => {
|
||||
folder.removeChild(stat);
|
||||
this.explorerService.setEditable(stat, null);
|
||||
if (success) {
|
||||
onSuccess(value);
|
||||
} else {
|
||||
this.explorerService.select(folder.resource).then(undefined, onUnexpectedError);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return this.commandService.executeCommand(NEW_FOLDER_COMMAND_ID);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
@@ -267,229 +166,210 @@ export class GlobalNewUntitledFileAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
class BaseDeleteFileAction extends BaseErrorReportingAction {
|
||||
|
||||
private static readonly CONFIRM_DELETE_SETTING_KEY = 'explorer.confirmDelete';
|
||||
|
||||
private skipConfirm: boolean;
|
||||
|
||||
constructor(
|
||||
private elements: ExplorerItem[],
|
||||
private useTrash: boolean,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IDialogService private readonly dialogService: IDialogService,
|
||||
@ITextFileService private readonly textFileService: ITextFileService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
super('moveFileToTrash', MOVE_FILE_TO_TRASH_LABEL, notificationService);
|
||||
|
||||
this.useTrash = useTrash && elements.every(e => !extpath.isUNC(e.resource.fsPath)); // on UNC shares there is no trash
|
||||
this.enabled = this.elements && this.elements.every(e => !e.isReadonly);
|
||||
function deleteFiles(serviceAccesor: ServicesAccessor, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise<void> {
|
||||
let primaryButton: string;
|
||||
if (useTrash) {
|
||||
primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash");
|
||||
} else {
|
||||
primaryButton = nls.localize({ key: 'deleteButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete");
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
const distinctElements = resources.distinctParents(elements, e => e.resource);
|
||||
const textFileService = serviceAccesor.get(ITextFileService);
|
||||
const dialogService = serviceAccesor.get(IDialogService);
|
||||
const configurationService = serviceAccesor.get(IConfigurationService);
|
||||
const fileService = serviceAccesor.get(IFileService);
|
||||
|
||||
let primaryButton: string;
|
||||
if (this.useTrash) {
|
||||
primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash");
|
||||
} else {
|
||||
primaryButton = nls.localize({ key: 'deleteButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete");
|
||||
}
|
||||
|
||||
const distinctElements = resources.distinctParents(this.elements, e => e.resource);
|
||||
|
||||
// Handle dirty
|
||||
let confirmDirtyPromise: Promise<boolean> = Promise.resolve(true);
|
||||
const dirty = this.textFileService.getDirty().filter(d => distinctElements.some(e => resources.isEqualOrParent(d, e.resource, !isLinux /* ignorecase */)));
|
||||
if (dirty.length) {
|
||||
let message: string;
|
||||
if (distinctElements.length > 1) {
|
||||
message = nls.localize('dirtyMessageFilesDelete', "You are deleting files with unsaved changes. Do you want to continue?");
|
||||
} else if (distinctElements[0].isDirectory) {
|
||||
if (dirty.length === 1) {
|
||||
message = nls.localize('dirtyMessageFolderOneDelete', "You are deleting a folder with unsaved changes in 1 file. Do you want to continue?");
|
||||
} else {
|
||||
message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder with unsaved changes in {0} files. Do you want to continue?", dirty.length);
|
||||
}
|
||||
// Handle dirty
|
||||
let confirmDirtyPromise: Promise<boolean> = Promise.resolve(true);
|
||||
const dirty = textFileService.getDirty().filter(d => distinctElements.some(e => resources.isEqualOrParent(d, e.resource, !isLinux /* ignorecase */)));
|
||||
if (dirty.length) {
|
||||
let message: string;
|
||||
if (distinctElements.length > 1) {
|
||||
message = nls.localize('dirtyMessageFilesDelete', "You are deleting files with unsaved changes. Do you want to continue?");
|
||||
} else if (distinctElements[0].isDirectory) {
|
||||
if (dirty.length === 1) {
|
||||
message = nls.localize('dirtyMessageFolderOneDelete', "You are deleting a folder with unsaved changes in 1 file. Do you want to continue?");
|
||||
} else {
|
||||
message = nls.localize('dirtyMessageFileDelete', "You are deleting a file with unsaved changes. Do you want to continue?");
|
||||
message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder with unsaved changes in {0} files. Do you want to continue?", dirty.length);
|
||||
}
|
||||
|
||||
confirmDirtyPromise = this.dialogService.confirm({
|
||||
message,
|
||||
type: 'warning',
|
||||
detail: nls.localize('dirtyWarning', "Your changes will be lost if you don't save them."),
|
||||
primaryButton
|
||||
}).then(res => {
|
||||
if (!res.confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.skipConfirm = true; // since we already asked for confirmation
|
||||
return this.textFileService.revertAll(dirty).then(() => true);
|
||||
});
|
||||
} else {
|
||||
message = nls.localize('dirtyMessageFileDelete', "You are deleting a file with unsaved changes. Do you want to continue?");
|
||||
}
|
||||
|
||||
// Check if file is dirty in editor and save it to avoid data loss
|
||||
return confirmDirtyPromise.then(confirmed => {
|
||||
if (!confirmed) {
|
||||
return null;
|
||||
confirmDirtyPromise = dialogService.confirm({
|
||||
message,
|
||||
type: 'warning',
|
||||
detail: nls.localize('dirtyWarning', "Your changes will be lost if you don't save them."),
|
||||
primaryButton
|
||||
}).then(res => {
|
||||
if (!res.confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let confirmDeletePromise: Promise<IConfirmationResult>;
|
||||
|
||||
// Check if we need to ask for confirmation at all
|
||||
if (this.skipConfirm || (this.useTrash && this.configurationService.getValue<boolean>(BaseDeleteFileAction.CONFIRM_DELETE_SETTING_KEY) === false)) {
|
||||
confirmDeletePromise = Promise.resolve({ confirmed: true } as IConfirmationResult);
|
||||
}
|
||||
|
||||
// Confirm for moving to trash
|
||||
else if (this.useTrash) {
|
||||
const message = this.getMoveToTrashMessage(distinctElements);
|
||||
|
||||
confirmDeletePromise = this.dialogService.confirm({
|
||||
message,
|
||||
detail: isWindows ? nls.localize('undoBin', "You can restore from the Recycle Bin.") : nls.localize('undoTrash', "You can restore from the Trash."),
|
||||
primaryButton,
|
||||
checkbox: {
|
||||
label: nls.localize('doNotAskAgain', "Do not ask me again")
|
||||
},
|
||||
type: 'question'
|
||||
});
|
||||
}
|
||||
|
||||
// Confirm for deleting permanently
|
||||
else {
|
||||
const message = this.getDeleteMessage(distinctElements);
|
||||
confirmDeletePromise = this.dialogService.confirm({
|
||||
message,
|
||||
detail: nls.localize('irreversible', "This action is irreversible!"),
|
||||
primaryButton,
|
||||
type: 'warning'
|
||||
});
|
||||
}
|
||||
|
||||
return confirmDeletePromise.then(confirmation => {
|
||||
|
||||
// Check for confirmation checkbox
|
||||
let updateConfirmSettingsPromise: Promise<void> = Promise.resolve(undefined);
|
||||
if (confirmation.confirmed && confirmation.checkboxChecked === true) {
|
||||
updateConfirmSettingsPromise = this.configurationService.updateValue(BaseDeleteFileAction.CONFIRM_DELETE_SETTING_KEY, false, ConfigurationTarget.USER);
|
||||
}
|
||||
|
||||
return updateConfirmSettingsPromise.then(() => {
|
||||
|
||||
// Check for confirmation
|
||||
if (!confirmation.confirmed) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
// Call function
|
||||
const servicePromise = Promise.all(distinctElements.map(e => this.fileService.del(e.resource, { useTrash: this.useTrash, recursive: true })))
|
||||
.then(undefined, (error: any) => {
|
||||
// Handle error to delete file(s) from a modal confirmation dialog
|
||||
let errorMessage: string;
|
||||
let detailMessage: string | undefined;
|
||||
let primaryButton: string;
|
||||
if (this.useTrash) {
|
||||
errorMessage = isWindows ? nls.localize('binFailed', "Failed to delete using the Recycle Bin. Do you want to permanently delete instead?") : nls.localize('trashFailed', "Failed to delete using the Trash. Do you want to permanently delete instead?");
|
||||
detailMessage = nls.localize('irreversible', "This action is irreversible!");
|
||||
primaryButton = nls.localize({ key: 'deletePermanentlyButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete Permanently");
|
||||
} else {
|
||||
errorMessage = toErrorMessage(error, false);
|
||||
primaryButton = nls.localize({ key: 'retryButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Retry");
|
||||
}
|
||||
|
||||
return this.dialogService.confirm({
|
||||
message: errorMessage,
|
||||
detail: detailMessage,
|
||||
type: 'warning',
|
||||
primaryButton
|
||||
}).then(res => {
|
||||
|
||||
if (res.confirmed) {
|
||||
if (this.useTrash) {
|
||||
this.useTrash = false; // Delete Permanently
|
||||
}
|
||||
|
||||
this.skipConfirm = true;
|
||||
|
||||
return this.run();
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
return servicePromise;
|
||||
});
|
||||
});
|
||||
skipConfirm = true; // since we already asked for confirmation
|
||||
return textFileService.revertAll(dirty).then(() => true);
|
||||
});
|
||||
}
|
||||
|
||||
private getMoveToTrashMessage(distinctElements: ExplorerItem[]): string {
|
||||
if (this.containsBothDirectoryAndFile(distinctElements)) {
|
||||
return getConfirmMessage(nls.localize('confirmMoveTrashMessageFilesAndDirectories', "Are you sure you want to delete the following {0} files/directories and their contents?", distinctElements.length), distinctElements.map(e => e.resource));
|
||||
// Check if file is dirty in editor and save it to avoid data loss
|
||||
return confirmDirtyPromise.then(confirmed => {
|
||||
if (!confirmed) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (distinctElements.length > 1) {
|
||||
if (distinctElements[0].isDirectory) {
|
||||
return getConfirmMessage(nls.localize('confirmMoveTrashMessageMultipleDirectories', "Are you sure you want to delete the following {0} directories and their contents?", distinctElements.length), distinctElements.map(e => e.resource));
|
||||
let confirmDeletePromise: Promise<IConfirmationResult>;
|
||||
|
||||
// Check if we need to ask for confirmation at all
|
||||
if (skipConfirm || (useTrash && configurationService.getValue<boolean>(CONFIRM_DELETE_SETTING_KEY) === false)) {
|
||||
confirmDeletePromise = Promise.resolve({ confirmed: true });
|
||||
}
|
||||
|
||||
// Confirm for moving to trash
|
||||
else if (useTrash) {
|
||||
const message = getMoveToTrashMessage(distinctElements);
|
||||
|
||||
confirmDeletePromise = dialogService.confirm({
|
||||
message,
|
||||
detail: isWindows ? nls.localize('undoBin', "You can restore from the Recycle Bin.") : nls.localize('undoTrash', "You can restore from the Trash."),
|
||||
primaryButton,
|
||||
checkbox: {
|
||||
label: nls.localize('doNotAskAgain', "Do not ask me again")
|
||||
},
|
||||
type: 'question'
|
||||
});
|
||||
}
|
||||
|
||||
// Confirm for deleting permanently
|
||||
else {
|
||||
const message = getDeleteMessage(distinctElements);
|
||||
confirmDeletePromise = dialogService.confirm({
|
||||
message,
|
||||
detail: nls.localize('irreversible', "This action is irreversible!"),
|
||||
primaryButton,
|
||||
type: 'warning'
|
||||
});
|
||||
}
|
||||
|
||||
return confirmDeletePromise.then(confirmation => {
|
||||
|
||||
// Check for confirmation checkbox
|
||||
let updateConfirmSettingsPromise: Promise<void> = Promise.resolve(undefined);
|
||||
if (confirmation.confirmed && confirmation.checkboxChecked === true) {
|
||||
updateConfirmSettingsPromise = configurationService.updateValue(CONFIRM_DELETE_SETTING_KEY, false, ConfigurationTarget.USER);
|
||||
}
|
||||
|
||||
return getConfirmMessage(nls.localize('confirmMoveTrashMessageMultiple', "Are you sure you want to delete the following {0} files?", distinctElements.length), distinctElements.map(e => e.resource));
|
||||
}
|
||||
return updateConfirmSettingsPromise.then(() => {
|
||||
|
||||
// Check for confirmation
|
||||
if (!confirmation.confirmed) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
// Call function
|
||||
const servicePromise = Promise.all(distinctElements.map(e => fileService.del(e.resource, { useTrash: useTrash, recursive: true })))
|
||||
.then(undefined, (error: any) => {
|
||||
// Handle error to delete file(s) from a modal confirmation dialog
|
||||
let errorMessage: string;
|
||||
let detailMessage: string | undefined;
|
||||
let primaryButton: string;
|
||||
if (useTrash) {
|
||||
errorMessage = isWindows ? nls.localize('binFailed', "Failed to delete using the Recycle Bin. Do you want to permanently delete instead?") : nls.localize('trashFailed', "Failed to delete using the Trash. Do you want to permanently delete instead?");
|
||||
detailMessage = nls.localize('irreversible', "This action is irreversible!");
|
||||
primaryButton = nls.localize({ key: 'deletePermanentlyButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete Permanently");
|
||||
} else {
|
||||
errorMessage = toErrorMessage(error, false);
|
||||
primaryButton = nls.localize({ key: 'retryButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Retry");
|
||||
}
|
||||
|
||||
return dialogService.confirm({
|
||||
message: errorMessage,
|
||||
detail: detailMessage,
|
||||
type: 'warning',
|
||||
primaryButton
|
||||
}).then(res => {
|
||||
|
||||
if (res.confirmed) {
|
||||
if (useTrash) {
|
||||
useTrash = false; // Delete Permanently
|
||||
}
|
||||
|
||||
skipConfirm = true;
|
||||
|
||||
return deleteFiles(serviceAccesor, elements, useTrash, skipConfirm);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
return servicePromise;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getMoveToTrashMessage(distinctElements: ExplorerItem[]): string {
|
||||
if (containsBothDirectoryAndFile(distinctElements)) {
|
||||
return getConfirmMessage(nls.localize('confirmMoveTrashMessageFilesAndDirectories', "Are you sure you want to delete the following {0} files/directories and their contents?", distinctElements.length), distinctElements.map(e => e.resource));
|
||||
}
|
||||
|
||||
if (distinctElements.length > 1) {
|
||||
if (distinctElements[0].isDirectory) {
|
||||
return nls.localize('confirmMoveTrashMessageFolder', "Are you sure you want to delete '{0}' and its contents?", distinctElements[0].name);
|
||||
return getConfirmMessage(nls.localize('confirmMoveTrashMessageMultipleDirectories', "Are you sure you want to delete the following {0} directories and their contents?", distinctElements.length), distinctElements.map(e => e.resource));
|
||||
}
|
||||
|
||||
return nls.localize('confirmMoveTrashMessageFile', "Are you sure you want to delete '{0}'?", distinctElements[0].name);
|
||||
return getConfirmMessage(nls.localize('confirmMoveTrashMessageMultiple', "Are you sure you want to delete the following {0} files?", distinctElements.length), distinctElements.map(e => e.resource));
|
||||
}
|
||||
|
||||
private getDeleteMessage(distinctElements: ExplorerItem[]): string {
|
||||
if (this.containsBothDirectoryAndFile(distinctElements)) {
|
||||
return getConfirmMessage(nls.localize('confirmDeleteMessageFilesAndDirectories', "Are you sure you want to permanently delete the following {0} files/directories and their contents?", distinctElements.length), distinctElements.map(e => e.resource));
|
||||
}
|
||||
if (distinctElements[0].isDirectory) {
|
||||
return nls.localize('confirmMoveTrashMessageFolder', "Are you sure you want to delete '{0}' and its contents?", distinctElements[0].name);
|
||||
}
|
||||
|
||||
if (distinctElements.length > 1) {
|
||||
if (distinctElements[0].isDirectory) {
|
||||
return getConfirmMessage(nls.localize('confirmDeleteMessageMultipleDirectories', "Are you sure you want to permanently delete the following {0} directories and their contents?", distinctElements.length), distinctElements.map(e => e.resource));
|
||||
}
|
||||
return nls.localize('confirmMoveTrashMessageFile', "Are you sure you want to delete '{0}'?", distinctElements[0].name);
|
||||
}
|
||||
|
||||
return getConfirmMessage(nls.localize('confirmDeleteMessageMultiple', "Are you sure you want to permanently delete the following {0} files?", distinctElements.length), distinctElements.map(e => e.resource));
|
||||
}
|
||||
function getDeleteMessage(distinctElements: ExplorerItem[]): string {
|
||||
if (containsBothDirectoryAndFile(distinctElements)) {
|
||||
return getConfirmMessage(nls.localize('confirmDeleteMessageFilesAndDirectories', "Are you sure you want to permanently delete the following {0} files/directories and their contents?", distinctElements.length), distinctElements.map(e => e.resource));
|
||||
}
|
||||
|
||||
if (distinctElements.length > 1) {
|
||||
if (distinctElements[0].isDirectory) {
|
||||
return nls.localize('confirmDeleteMessageFolder', "Are you sure you want to permanently delete '{0}' and its contents?", distinctElements[0].name);
|
||||
return getConfirmMessage(nls.localize('confirmDeleteMessageMultipleDirectories', "Are you sure you want to permanently delete the following {0} directories and their contents?", distinctElements.length), distinctElements.map(e => e.resource));
|
||||
}
|
||||
|
||||
return nls.localize('confirmDeleteMessageFile', "Are you sure you want to permanently delete '{0}'?", distinctElements[0].name);
|
||||
return getConfirmMessage(nls.localize('confirmDeleteMessageMultiple', "Are you sure you want to permanently delete the following {0} files?", distinctElements.length), distinctElements.map(e => e.resource));
|
||||
}
|
||||
|
||||
private containsBothDirectoryAndFile(distinctElements: ExplorerItem[]): boolean {
|
||||
const directories = distinctElements.filter(element => element.isDirectory);
|
||||
const files = distinctElements.filter(element => !element.isDirectory);
|
||||
|
||||
return directories.length > 0 && files.length > 0;
|
||||
if (distinctElements[0].isDirectory) {
|
||||
return nls.localize('confirmDeleteMessageFolder', "Are you sure you want to permanently delete '{0}' and its contents?", distinctElements[0].name);
|
||||
}
|
||||
|
||||
return nls.localize('confirmDeleteMessageFile', "Are you sure you want to permanently delete '{0}'?", distinctElements[0].name);
|
||||
}
|
||||
|
||||
function containsBothDirectoryAndFile(distinctElements: ExplorerItem[]): boolean {
|
||||
const directories = distinctElements.filter(element => element.isDirectory);
|
||||
const files = distinctElements.filter(element => !element.isDirectory);
|
||||
|
||||
return directories.length > 0 && files.length > 0;
|
||||
}
|
||||
|
||||
let pasteShouldMove = false;
|
||||
// Paste File/Folder
|
||||
class PasteFileAction extends BaseErrorReportingAction {
|
||||
class PasteFileAction extends Action {
|
||||
|
||||
public static readonly ID = 'filesExplorer.paste';
|
||||
|
||||
constructor(
|
||||
private element: ExplorerItem,
|
||||
@IFileService private fileService: IFileService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IExplorerService private readonly explorerService: IExplorerService
|
||||
) {
|
||||
super(PasteFileAction.ID, PASTE_FILE_LABEL, notificationService);
|
||||
super(PasteFileAction.ID, PASTE_FILE_LABEL);
|
||||
|
||||
if (!this.element) {
|
||||
this.element = this.explorerService.roots[0];
|
||||
@@ -528,9 +408,9 @@ class PasteFileAction extends BaseErrorReportingAction {
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, e => this.onError(e));
|
||||
}, e => onError(this.notificationService, e));
|
||||
}, error => {
|
||||
this.onError(new Error(nls.localize('fileDeleted', "File to paste was deleted or moved meanwhile")));
|
||||
onError(this.notificationService, new Error(nls.localize('fileDeleted', "File to paste was deleted or moved meanwhile")));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -701,7 +581,7 @@ export class ToggleAutoSaveAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BaseSaveAllAction extends BaseErrorReportingAction {
|
||||
export abstract class BaseSaveAllAction extends Action {
|
||||
private toDispose: IDisposable[];
|
||||
private lastIsDirty: boolean;
|
||||
|
||||
@@ -711,9 +591,9 @@ export abstract class BaseSaveAllAction extends BaseErrorReportingAction {
|
||||
@ITextFileService private readonly textFileService: ITextFileService,
|
||||
@IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService,
|
||||
@ICommandService protected commandService: ICommandService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
) {
|
||||
super(id, label, notificationService);
|
||||
super(id, label);
|
||||
|
||||
this.toDispose = [];
|
||||
this.lastIsDirty = this.textFileService.isDirty();
|
||||
@@ -747,7 +627,7 @@ export abstract class BaseSaveAllAction extends BaseErrorReportingAction {
|
||||
|
||||
public run(context?: any): Promise<boolean> {
|
||||
return this.doRun(context).then(() => true, error => {
|
||||
this.onError(error);
|
||||
onError(this.notificationService, error);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
@@ -918,7 +798,7 @@ export class ShowOpenedFileInNewWindow extends Action {
|
||||
const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: true });
|
||||
if (fileResource) {
|
||||
if (this.fileService.canHandleResource(fileResource)) {
|
||||
this.windowService.openWindow([{ uri: fileResource, typeHint: 'file' }], { forceNewWindow: true, forceOpenWorkspaceAsFile: true });
|
||||
this.windowService.openWindow([{ fileUri: fileResource }], { forceNewWindow: true });
|
||||
} else {
|
||||
this.notificationService.info(nls.localize('openFileToShowInNewWindow.unsupportedschema', "The active editor must contain an openable resource."));
|
||||
}
|
||||
@@ -1002,7 +882,7 @@ export class CompareWithClipboardAction extends Action {
|
||||
|
||||
private static readonly SCHEME = 'clipboardCompare';
|
||||
|
||||
private registrationDisposal: IDisposable;
|
||||
private registrationDisposal: IDisposable | undefined;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@@ -1029,7 +909,8 @@ export class CompareWithClipboardAction extends Action {
|
||||
const editorLabel = nls.localize('clipboardComparisonLabel', "Clipboard ↔ {0}", name);
|
||||
|
||||
return this.editorService.openEditor({ leftResource: resource.with({ scheme: CompareWithClipboardAction.SCHEME }), rightResource: resource, label: editorLabel }).finally(() => {
|
||||
this.registrationDisposal = dispose(this.registrationDisposal);
|
||||
dispose(this.registrationDisposal);
|
||||
this.registrationDisposal = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1039,7 +920,8 @@ export class CompareWithClipboardAction extends Action {
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.registrationDisposal = dispose(this.registrationDisposal);
|
||||
dispose(this.registrationDisposal);
|
||||
this.registrationDisposal = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1073,43 +955,82 @@ function getContext(listWidget: ListWidget): IExplorerContext {
|
||||
return { stat, selection: selection && typeof stat !== 'undefined' && selection.indexOf(stat) >= 0 ? selection : [] };
|
||||
}
|
||||
|
||||
// TODO@isidor these commands are calling into actions due to the complex inheritance action structure.
|
||||
// It should be the other way around, that actions call into commands.
|
||||
function openExplorerAndRunAction(accessor: ServicesAccessor, constructor: IConstructorSignature1<() => ExplorerItem, Action>): Promise<any> {
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
function onErrorWithRetry(notificationService: INotificationService, error: any, retry: () => Promise<any>): void {
|
||||
notificationService.prompt(Severity.Error, toErrorMessage(error, false),
|
||||
[{
|
||||
label: nls.localize('retry', "Retry"),
|
||||
run: () => retry()
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boolean): Promise<void> {
|
||||
const listService = accessor.get(IListService);
|
||||
const explorerService = accessor.get(IExplorerService);
|
||||
const fileService = accessor.get(IFileService);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const viewletService = accessor.get(IViewletService);
|
||||
const activeViewlet = viewletService.getActiveViewlet();
|
||||
let explorerPromise = Promise.resolve(activeViewlet);
|
||||
if (!activeViewlet || activeViewlet.getId() !== VIEWLET_ID) {
|
||||
explorerPromise = viewletService.openViewlet(VIEWLET_ID, true);
|
||||
if (!activeViewlet || activeViewlet.getId() !== VIEWLET_ID || !listService.lastFocusedList) {
|
||||
await viewletService.openViewlet(VIEWLET_ID, true);
|
||||
}
|
||||
|
||||
return explorerPromise.then((explorer: ExplorerViewlet) => {
|
||||
const explorerView = explorer.getExplorerView();
|
||||
if (explorerView && explorerView.isBodyVisible() && listService.lastFocusedList) {
|
||||
explorerView.focus();
|
||||
const { stat } = getContext(listService.lastFocusedList);
|
||||
const action = instantiationService.createInstance(constructor, () => stat);
|
||||
|
||||
return action.run();
|
||||
const list = listService.lastFocusedList;
|
||||
if (list) {
|
||||
const { stat } = getContext(list);
|
||||
let folder: ExplorerItem;
|
||||
if (stat) {
|
||||
folder = stat.isDirectory ? stat : stat.parent!;
|
||||
} else {
|
||||
folder = explorerService.roots[0];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
if (folder.isReadonly) {
|
||||
throw new Error('Parent folder is readonly.');
|
||||
}
|
||||
|
||||
const newStat = new NewExplorerItem(folder, isFolder);
|
||||
await folder.fetchChildren(fileService, explorerService);
|
||||
|
||||
folder.addChild(newStat);
|
||||
|
||||
const onSuccess = async (value: string) => {
|
||||
const createPromise = isFolder ? fileService.createFolder(resources.joinPath(folder.resource, value)) : fileService.createFile(resources.joinPath(folder.resource, value));
|
||||
return createPromise.then(created => {
|
||||
refreshIfSeparator(value, explorerService);
|
||||
return isFolder ? explorerService.select(created.resource, true)
|
||||
: editorService.openEditor({ resource: created.resource, options: { pinned: true } }).then(() => undefined);
|
||||
}, (error) => {
|
||||
onErrorWithRetry(accessor.get(INotificationService), error, () => onSuccess(value));
|
||||
});
|
||||
};
|
||||
|
||||
explorerService.setEditable(newStat, {
|
||||
validationMessage: value => validateFileName(newStat, value),
|
||||
onFinish: (value, success) => {
|
||||
folder.removeChild(newStat);
|
||||
explorerService.setEditable(newStat, null);
|
||||
if (success) {
|
||||
onSuccess(value);
|
||||
} else {
|
||||
explorerService.select(folder.resource).then(undefined, onUnexpectedError);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: NEW_FILE_COMMAND_ID,
|
||||
handler: (accessor) => {
|
||||
return openExplorerAndRunAction(accessor, NewFileAction);
|
||||
openExplorerAndCreate(accessor, false).then(undefined, onUnexpectedError);
|
||||
}
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: NEW_FOLDER_COMMAND_ID,
|
||||
handler: (accessor) => {
|
||||
return openExplorerAndRunAction(accessor, NewFolderAction);
|
||||
openExplorerAndCreate(accessor, true).then(undefined, onUnexpectedError);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1140,29 +1061,25 @@ export const renameHandler = (accessor: ServicesAccessor) => {
|
||||
};
|
||||
|
||||
export const moveFileToTrashHandler = (accessor: ServicesAccessor) => {
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
const listService = accessor.get(IListService);
|
||||
if (!listService.lastFocusedList) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const explorerContext = getContext(listService.lastFocusedList);
|
||||
const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat];
|
||||
const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat!];
|
||||
|
||||
const moveFileToTrashAction = instantiationService.createInstance(BaseDeleteFileAction, stats, true);
|
||||
return moveFileToTrashAction.run();
|
||||
return deleteFiles(accessor, stats, true);
|
||||
};
|
||||
|
||||
export const deleteFileHandler = (accessor: ServicesAccessor) => {
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
const listService = accessor.get(IListService);
|
||||
if (!listService.lastFocusedList) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const explorerContext = getContext(listService.lastFocusedList);
|
||||
const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat];
|
||||
const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat!];
|
||||
|
||||
const deleteFileAction = instantiationService.createInstance(BaseDeleteFileAction, stats, false);
|
||||
return deleteFileAction.run();
|
||||
return deleteFiles(accessor, stats, false);
|
||||
};
|
||||
|
||||
export const copyFileHandler = (accessor: ServicesAccessor) => {
|
||||
|
||||
@@ -315,11 +315,6 @@ configurationRegistry.registerConfiguration({
|
||||
],
|
||||
'description': nls.localize('hotExit', "Controls whether unsaved files are remembered between sessions, allowing the save prompt when exiting the editor to be skipped.", HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE)
|
||||
},
|
||||
'files.useExperimentalFileWatcher': {
|
||||
'type': 'boolean',
|
||||
'default': false,
|
||||
'description': nls.localize('useExperimentalFileWatcher', "Use the new experimental file watcher.")
|
||||
},
|
||||
'files.defaultLanguage': {
|
||||
'type': 'string',
|
||||
'description': nls.localize('defaultLanguage', "The default language mode that is assigned to new files.")
|
||||
|
||||
@@ -14,14 +14,14 @@ import { coalesce } from 'vs/base/common/arrays';
|
||||
|
||||
// Commands can get exeucted from a command pallete, from a context menu or from some list using a keybinding
|
||||
// To cover all these cases we need to properly compute the resource on which the command is being executed
|
||||
export function getResourceForCommand(resource: URI | object, listService: IListService, editorService: IEditorService): URI | null {
|
||||
export function getResourceForCommand(resource: URI | object | undefined, listService: IListService, editorService: IEditorService): URI | null {
|
||||
if (URI.isUri(resource)) {
|
||||
return resource;
|
||||
}
|
||||
|
||||
let list = listService.lastFocusedList;
|
||||
if (list && list.getHTMLElement() === document.activeElement) {
|
||||
let focus: any;
|
||||
let focus: unknown;
|
||||
if (list instanceof List) {
|
||||
const focused = list.getFocusedElements();
|
||||
if (focused.length) {
|
||||
@@ -44,7 +44,7 @@ export function getResourceForCommand(resource: URI | object, listService: IList
|
||||
return editorService.activeEditor ? toResource(editorService.activeEditor, { supportSideBySide: true }) : null;
|
||||
}
|
||||
|
||||
export function getMultiSelectedResources(resource: URI | object, listService: IListService, editorService: IEditorService): Array<URI> {
|
||||
export function getMultiSelectedResources(resource: URI | object | undefined, listService: IListService, editorService: IEditorService): Array<URI> {
|
||||
const list = listService.lastFocusedList;
|
||||
if (list && list.getHTMLElement() === document.activeElement) {
|
||||
// Explorer
|
||||
|
||||
@@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import * as perf from 'vs/base/common/performance';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, IExplorerService, ExplorerResourceCut } from 'vs/workbench/contrib/files/common/files';
|
||||
import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, IExplorerService, ExplorerResourceCut, ExplorerResourceMoveableToTrash } from 'vs/workbench/contrib/files/common/files';
|
||||
import { NewFolderAction, NewFileAction, FileCopiedContext, RefreshExplorerView } from 'vs/workbench/contrib/files/browser/fileActions';
|
||||
import { toResource } from 'vs/workbench/common/editor';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
@@ -39,7 +39,7 @@ import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemAc
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ResourceLabels, IResourceLabelsContainer } from 'vs/workbench/browser/labels';
|
||||
import { ResourceLabels } from 'vs/workbench/browser/labels';
|
||||
import { createFileIconThemableTreeContainerScope } from 'vs/workbench/browser/parts/views/views';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree';
|
||||
@@ -49,6 +49,7 @@ import { isEqualOrParent } from 'vs/base/common/resources';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { first } from 'vs/base/common/arrays';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
||||
|
||||
export class ExplorerView extends ViewletPanel {
|
||||
static readonly ID: string = 'workbench.explorer.fileView';
|
||||
@@ -61,6 +62,7 @@ export class ExplorerView extends ViewletPanel {
|
||||
private folderContext: IContextKey<boolean>;
|
||||
private readonlyContext: IContextKey<boolean>;
|
||||
private rootContext: IContextKey<boolean>;
|
||||
private resourceMoveableToTrash: IContextKey<boolean>;
|
||||
|
||||
// Refresh is needed on the initial explorer open
|
||||
private shouldRefresh = true;
|
||||
@@ -85,7 +87,8 @@ export class ExplorerView extends ViewletPanel {
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IExplorerService private readonly explorerService: IExplorerService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IClipboardService private clipboardService: IClipboardService
|
||||
@IClipboardService private clipboardService: IClipboardService,
|
||||
@IFileService private readonly fileService: IFileService
|
||||
) {
|
||||
super({ ...(options as IViewletPanelOptions), id: ExplorerView.ID, ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService);
|
||||
|
||||
@@ -94,6 +97,7 @@ export class ExplorerView extends ViewletPanel {
|
||||
this.folderContext = ExplorerFolderContext.bindTo(contextKeyService);
|
||||
this.readonlyContext = ExplorerResourceReadonlyContext.bindTo(contextKeyService);
|
||||
this.rootContext = ExplorerRootContext.bindTo(contextKeyService);
|
||||
this.resourceMoveableToTrash = ExplorerResourceMoveableToTrash.bindTo(contextKeyService);
|
||||
|
||||
const decorationProvider = new ExplorerDecorationsProvider(this.explorerService, contextService);
|
||||
decorationService.registerDecorationsProvider(decorationProvider);
|
||||
@@ -217,12 +221,8 @@ export class ExplorerView extends ViewletPanel {
|
||||
getActions(): IAction[] {
|
||||
const actions: Action[] = [];
|
||||
|
||||
const getFocus = () => {
|
||||
const focus = this.tree.getFocus();
|
||||
return focus.length > 0 ? focus[0] : undefined;
|
||||
};
|
||||
actions.push(this.instantiationService.createInstance(NewFileAction, getFocus));
|
||||
actions.push(this.instantiationService.createInstance(NewFolderAction, getFocus));
|
||||
actions.push(this.instantiationService.createInstance(NewFileAction));
|
||||
actions.push(this.instantiationService.createInstance(NewFolderAction));
|
||||
actions.push(this.instantiationService.createInstance(RefreshExplorerView, RefreshExplorerView.ID, RefreshExplorerView.LABEL));
|
||||
actions.push(this.instantiationService.createInstance(CollapseAction, this.tree, true, 'explorer-action collapse-explorer'));
|
||||
|
||||
@@ -267,7 +267,7 @@ export class ExplorerView extends ViewletPanel {
|
||||
private createTree(container: HTMLElement): void {
|
||||
this.filter = this.instantiationService.createInstance(FilesFilter);
|
||||
this.disposables.push(this.filter);
|
||||
const explorerLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility } as IResourceLabelsContainer);
|
||||
const explorerLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility });
|
||||
this.disposables.push(explorerLabels);
|
||||
|
||||
const updateWidth = (stat: ExplorerItem) => this.tree.updateWidth(stat);
|
||||
@@ -405,6 +405,14 @@ export class ExplorerView extends ViewletPanel {
|
||||
this.folderContext.set((isSingleFolder && !stat) || !!stat && stat.isDirectory);
|
||||
this.readonlyContext.set(!!stat && stat.isReadonly);
|
||||
this.rootContext.set(!stat || (stat && stat.isRoot));
|
||||
|
||||
if (stat) {
|
||||
const enableTrash = this.configurationService.getValue<IFilesConfiguration>().files.enableTrash;
|
||||
const hasCapability = this.fileService.hasCapability(stat.resource, FileSystemProviderCapabilities.Trash);
|
||||
this.resourceMoveableToTrash.set(enableTrash && hasCapability);
|
||||
} else {
|
||||
this.resourceMoveableToTrash.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// General methods
|
||||
@@ -462,7 +470,7 @@ export class ExplorerView extends ViewletPanel {
|
||||
} else {
|
||||
const rawViewState = this.storageService.get(ExplorerView.TREE_VIEW_STATE_STORAGE_KEY, StorageScope.WORKSPACE);
|
||||
if (rawViewState) {
|
||||
viewState = JSON.parse(rawViewState) as IAsyncDataTreeViewState;
|
||||
viewState = JSON.parse(rawViewState);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -309,7 +309,7 @@ export class FilesFilter implements ITreeFilter<ExplorerItem, FuzzyScore> {
|
||||
|
||||
const excludesConfigCopy = deepClone(excludesConfig); // do not keep the config, as it gets mutated under our hoods
|
||||
|
||||
this.hiddenExpressionPerRoot.set(folder.uri.toString(), { original: excludesConfigCopy, parsed: glob.parse(excludesConfigCopy) } as CachedParsedExpression);
|
||||
this.hiddenExpressionPerRoot.set(folder.uri.toString(), { original: excludesConfigCopy, parsed: glob.parse(excludesConfigCopy) });
|
||||
});
|
||||
|
||||
return needsRefresh;
|
||||
@@ -334,7 +334,7 @@ export class FilesFilter implements ITreeFilter<ExplorerItem, FuzzyScore> {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.workspaceFolderChangeListener = dispose(this.workspaceFolderChangeListener);
|
||||
dispose(this.workspaceFolderChangeListener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -735,7 +735,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||
primaryButton: localize({ key: 'moveButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Move")
|
||||
});
|
||||
} else {
|
||||
confirmPromise = Promise.resolve({ confirmed: true } as IConfirmationResult);
|
||||
confirmPromise = Promise.resolve({ confirmed: true });
|
||||
}
|
||||
|
||||
return confirmPromise.then(res => {
|
||||
|
||||
@@ -25,7 +25,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { WorkbenchList } from 'vs/platform/list/browser/listService';
|
||||
import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
|
||||
import { ResourceLabels, IResourceLabel, IResourceLabelsContainer } from 'vs/workbench/browser/labels';
|
||||
import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
@@ -212,7 +212,7 @@ export class OpenEditorsView extends ViewletPanel {
|
||||
if (this.listLabels) {
|
||||
this.listLabels.clear();
|
||||
}
|
||||
this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility } as IResourceLabelsContainer);
|
||||
this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility });
|
||||
this.list = this.instantiationService.createInstance(WorkbenchList, container, delegate, [
|
||||
new EditorGroupRenderer(this.keybindingService, this.instantiationService),
|
||||
new OpenEditorRenderer(this.listLabels, this.instantiationService, this.keybindingService, this.configurationService)
|
||||
|
||||
Reference in New Issue
Block a user