提交 de264a8b 编写于 作者: B Benjamin Pasero

files - working copy service for explorer delete

上级 33c79d5a
......@@ -133,8 +133,8 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution
}
private saveAllDirty(options?: ISaveOptions): void {
for (const workingCopy of this.workingCopyService.workingCopies) {
if (workingCopy.isDirty() && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled)) {
for (const workingCopy of this.workingCopyService.dirtyWorkingCopies) {
if (!(workingCopy.capabilities & WorkingCopyCapabilities.Untitled)) {
workingCopy.save(options);
}
}
......
......@@ -34,7 +34,7 @@ export class BackupOnShutdown extends Disposable implements IWorkbenchContributi
// copies that have not been backed up yet and then prevent the
// shutdown if that is the case.
const dirtyWorkingCopies = this.workingCopyService.workingCopies.filter(workingCopy => workingCopy.isDirty());
const dirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies;
if (!dirtyWorkingCopies.length) {
return false; // no dirty: no veto
}
......
......@@ -46,7 +46,7 @@ export class BackupOnShutdown extends Disposable implements IWorkbenchContributi
private onBeforeShutdown(reason: ShutdownReason): boolean | Promise<boolean> {
// Dirty working copies need treatment on shutdown
const dirtyWorkingCopies = this.workingCopyService.workingCopies.filter(workingCopy => workingCopy.isDirty());
const dirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies;
if (dirtyWorkingCopies.length) {
// If auto save is enabled, save all working copies and then check again for dirty copies
......@@ -55,7 +55,7 @@ export class BackupOnShutdown extends Disposable implements IWorkbenchContributi
return this.doSaveAll(dirtyWorkingCopies, false /* not untitled */, { skipSaveParticipants: true }).then(() => {
// If we still have dirty working copies, we either have untitled ones or working copies that cannot be saved
const remainingDirtyWorkingCopies = this.workingCopyService.workingCopies.filter(workingCopy => workingCopy.isDirty());
const remainingDirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies;
if (remainingDirtyWorkingCopies.length) {
return this.handleDirtyBeforeShutdown(remainingDirtyWorkingCopies, reason);
}
......@@ -143,7 +143,7 @@ export class BackupOnShutdown extends Disposable implements IWorkbenchContributi
private async confirmBeforeShutdown(): Promise<boolean> {
// Show confirm dialog for all dirty working copies
const dirtyWorkingCopies = this.workingCopyService.workingCopies.filter(workingCopy => workingCopy.isDirty());
const dirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies;
const confirm = await this.fileDialogService.showSaveConfirm(dirtyWorkingCopies.map(w => w.resource));
// Save
......
......@@ -143,7 +143,7 @@ export class GlobalNewUntitledFileAction extends Action {
}
}
async function deleteFiles(textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise<void> {
async function deleteFiles(workingCopyService: IWorkingCopyService, textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, 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");
......@@ -155,16 +155,16 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi
// Handle dirty
let confirmed = true;
const dirty = textFileService.getDirty().filter(d => distinctElements.some(e => resources.isEqualOrParent(d, e.resource)));
if (dirty.length) {
const dirtyWorkingCopies = workingCopyService.dirtyWorkingCopies.filter(workingCopy => distinctElements.some(e => resources.isEqualOrParent(workingCopy.resource, e.resource)));
if (dirtyWorkingCopies.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) {
if (dirtyWorkingCopies.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);
message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder with unsaved changes in {0} files. Do you want to continue?", dirtyWorkingCopies.length);
}
} else {
message = nls.localize('dirtyMessageFileDelete', "You are deleting a file with unsaved changes. Do you want to continue?");
......@@ -181,7 +181,7 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi
confirmed = false;
} else {
skipConfirm = true;
await textFileService.revertAll(dirty);
await Promise.all(dirtyWorkingCopies.map(dirty => dirty.revert()));
}
}
......@@ -190,11 +190,11 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi
return;
}
let confirmDeletePromise: Promise<IConfirmationResult>;
let confirmation: 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 });
confirmation = { confirmed: true };
}
// Confirm for moving to trash
......@@ -207,7 +207,7 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi
detail += distinctElements.length > 1 ? nls.localize('undoTrashFiles', "You can restore these files from the Trash.") : nls.localize('undoTrash', "You can restore this file from the Trash.");
}
confirmDeletePromise = dialogService.confirm({
confirmation = await dialogService.confirm({
message,
detail,
primaryButton,
......@@ -223,7 +223,7 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi
let { message, detail } = getDeleteMessage(distinctElements);
detail += detail ? '\n' : '';
detail += nls.localize('irreversible', "This action is irreversible!");
confirmDeletePromise = dialogService.confirm({
confirmation = await dialogService.confirm({
message,
detail,
primaryButton,
......@@ -231,61 +231,54 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi
});
}
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 updateConfirmSettingsPromise.then(() => {
// Check for confirmation checkbox
if (confirmation.confirmed && confirmation.checkboxChecked === true) {
await configurationService.updateValue(CONFIRM_DELETE_SETTING_KEY, false, ConfigurationTarget.USER);
}
// Check for confirmation
if (!confirmation.confirmed) {
return Promise.resolve(undefined);
}
// Call function
const servicePromise = Promise.all(distinctElements.map(e => textFileService.delete(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");
}
// Check for confirmation
if (!confirmation.confirmed) {
return;
}
return dialogService.confirm({
message: errorMessage,
detail: detailMessage,
type: 'warning',
primaryButton
}).then(res => {
// Call function
try {
await Promise.all(distinctElements.map(e => textFileService.delete(e.resource, { useTrash: useTrash, recursive: true })));
} catch (error) {
if (res.confirmed) {
if (useTrash) {
useTrash = false; // Delete Permanently
}
// 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");
}
skipConfirm = true;
const res = await dialogService.confirm({
message: errorMessage,
detail: detailMessage,
type: 'warning',
primaryButton
});
return deleteFiles(textFileService, dialogService, configurationService, elements, useTrash, skipConfirm);
}
if (res.confirmed) {
if (useTrash) {
useTrash = false; // Delete Permanently
}
return Promise.resolve();
});
});
skipConfirm = true;
return servicePromise.then(undefined);
});
});
return deleteFiles(workingCopyService, textFileService, dialogService, configurationService, elements, useTrash, skipConfirm);
}
}
}
function getMoveToTrashMessage(distinctElements: ExplorerItem[]): { message: string, detail: string } {
......@@ -648,7 +641,7 @@ export class ShowActiveFileInExplorer extends Action {
super(id, label);
}
run(): Promise<any> {
async run(): Promise<any> {
const resource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER });
if (resource) {
this.commandService.executeCommand(REVEAL_IN_EXPLORER_COMMAND_ID, resource);
......@@ -656,7 +649,7 @@ export class ShowActiveFileInExplorer extends Action {
this.notificationService.info(nls.localize('openFileToShow', "Open a file first to show it in the explorer"));
}
return Promise.resolve(true);
return true;
}
}
......@@ -726,7 +719,7 @@ export class ShowOpenedFileInNewWindow extends Action {
super(id, label);
}
run(): Promise<any> {
async run(): Promise<any> {
const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER });
if (fileResource) {
if (this.fileService.canHandleResource(fileResource)) {
......@@ -738,7 +731,7 @@ export class ShowOpenedFileInNewWindow extends Action {
this.notificationService.info(nls.localize('openFileToShowInNewWindow.nofile', "Open a file first to open in new window"));
}
return Promise.resolve(true);
return true;
}
}
......@@ -822,7 +815,7 @@ export class CompareWithClipboardAction extends Action {
this.enabled = true;
}
run(): Promise<any> {
async run(): Promise<any> {
const resource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER });
if (resource && (this.fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) {
if (!this.registrationDisposal) {
......@@ -839,7 +832,7 @@ export class CompareWithClipboardAction extends Action {
});
}
return Promise.resolve(true);
return true;
}
dispose(): void {
......@@ -902,15 +895,17 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole
folder.addChild(newStat);
const onSuccess = (value: string): Promise<void> => {
const createPromise = isFolder ? fileService.createFolder(resources.joinPath(folder.resource, value)) : textFileService.create(resources.joinPath(folder.resource, value));
return createPromise.then(created => {
const onSuccess = async (value: string): Promise<void> => {
try {
const created = isFolder ? await fileService.createFolder(resources.joinPath(folder.resource, value)) : await textFileService.create(resources.joinPath(folder.resource, value));
refreshIfSeparator(value, explorerService);
return isFolder ? explorerService.select(created.resource, true)
: editorService.openEditor({ resource: created.resource, options: { pinned: true } }).then(() => undefined);
}, error => {
isFolder ?
await explorerService.select(created.resource, true) :
await editorService.openEditor({ resource: created.resource, options: { pinned: true } });
} catch (error) {
onErrorWithRetry(notificationService, error, () => onSuccess(value));
});
}
};
explorerService.setEditable(newStat, {
......@@ -974,7 +969,7 @@ export const moveFileToTrashHandler = async (accessor: ServicesAccessor) => {
const explorerService = accessor.get(IExplorerService);
const stats = explorerService.getContext(true).filter(s => !s.isRoot);
if (stats.length) {
await deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true);
await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true);
}
};
......@@ -983,7 +978,7 @@ export const deleteFileHandler = async (accessor: ServicesAccessor) => {
const stats = explorerService.getContext(true).filter(s => !s.isRoot);
if (stats.length) {
await deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false);
await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false);
}
};
......
......@@ -200,7 +200,7 @@ CommandsRegistry.registerCommand({
CommandsRegistry.registerCommand({
id: COMPARE_SELECTED_COMMAND_ID,
handler: (accessor, resource: URI | object) => {
handler: async (accessor, resource: URI | object) => {
const editorService = accessor.get(IEditorService);
const explorerService = accessor.get(IExplorerService);
const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, explorerService);
......@@ -212,7 +212,7 @@ CommandsRegistry.registerCommand({
});
}
return Promise.resolve(true);
return true;
}
});
......
......@@ -81,6 +81,8 @@ export interface IWorkingCopyService {
readonly dirtyCount: number;
readonly dirtyWorkingCopies: IWorkingCopy[];
readonly hasDirty: boolean;
isDirty(resource: URI): boolean;
......@@ -118,46 +120,6 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic
//#endregion
//#region Dirty Tracking
isDirty(resource: URI): boolean {
const workingCopies = this.mapResourceToWorkingCopy.get(resource.toString());
if (workingCopies) {
for (const workingCopy of workingCopies) {
if (workingCopy.isDirty()) {
return true;
}
}
}
return false;
}
get hasDirty(): boolean {
for (const workingCopy of this._workingCopies) {
if (workingCopy.isDirty()) {
return true;
}
}
return false;
}
get dirtyCount(): number {
let totalDirtyCount = 0;
for (const workingCopy of this._workingCopies) {
if (workingCopy.isDirty()) {
totalDirtyCount++;
}
}
return totalDirtyCount;
}
//#endregion
//#region Registry
private mapResourceToWorkingCopy = TernarySearchTree.forPaths<Set<IWorkingCopy>>();
......@@ -216,6 +178,50 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic
}
//#endregion
//#region Dirty Tracking
get hasDirty(): boolean {
for (const workingCopy of this._workingCopies) {
if (workingCopy.isDirty()) {
return true;
}
}
return false;
}
get dirtyCount(): number {
let totalDirtyCount = 0;
for (const workingCopy of this._workingCopies) {
if (workingCopy.isDirty()) {
totalDirtyCount++;
}
}
return totalDirtyCount;
}
get dirtyWorkingCopies(): IWorkingCopy[] {
return this.workingCopies.filter(workingCopy => workingCopy.isDirty());
}
isDirty(resource: URI): boolean {
const workingCopies = this.mapResourceToWorkingCopy.get(resource.toString());
if (workingCopies) {
for (const workingCopy of workingCopies) {
if (workingCopy.isDirty()) {
return true;
}
}
}
return false;
}
//#endregion
}
registerSingleton(IWorkingCopyService, WorkingCopyService, true);
......@@ -105,7 +105,10 @@ suite('WorkingCopyService', () => {
copy1.setDirty(true);
assert.equal(copy1.isDirty(), true);
assert.equal(service.dirtyCount, 1);
assert.equal(service.dirtyWorkingCopies.length, 1);
assert.equal(service.dirtyWorkingCopies[0], copy1);
assert.equal(service.isDirty(resource1), true);
assert.equal(service.hasDirty, true);
assert.equal(onDidChangeDirty.length, 1);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册