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

Keep dirty contents when renaming a dirty file or its parent (fixes #11997)

上级 998bf16d
......@@ -46,6 +46,8 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { Keybinding, KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { getCodeEditor } from 'vs/editor/common/services/codeEditorService';
import { IEditorViewState } from 'vs/editor/common/editorCommon';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { ITextModelResolverService } from 'vs/editor/common/services/resolverService';
export interface IEditableData {
action: IAction;
......@@ -271,7 +273,9 @@ class RenameFileAction extends BaseRenameAction {
element: FileStat,
@IFileService fileService: IFileService,
@IMessageService messageService: IMessageService,
@ITextFileService textFileService: ITextFileService
@ITextFileService textFileService: ITextFileService,
@ITextModelResolverService private textModelResolverService: ITextModelResolverService,
@IBackupFileService private backupFileService: IBackupFileService
) {
super(RenameFileAction.ID, nls.localize('rename', "Rename"), element, fileService, messageService, textFileService);
......@@ -280,40 +284,44 @@ class RenameFileAction extends BaseRenameAction {
public runAction(newName: string): TPromise<any> {
// Handle dirty
let revertPromise: TPromise<any> = TPromise.as(null);
// 1. check for dirty files that are being moved and backup to new target
const dirty = this.textFileService.getDirty().filter(d => paths.isEqualOrParent(d.fsPath, this.element.resource.fsPath));
if (dirty.length) {
let message: string;
if (this.element.isDirectory) {
if (dirty.length === 1) {
message = nls.localize('dirtyMessageFolderOne', "You are renaming a folder with unsaved changes in 1 file. Do you want to continue?");
} else {
message = nls.localize('dirtyMessageFolder', "You are renaming a folder with unsaved changes in {0} files. Do you want to continue?", dirty.length);
}
} else {
message = nls.localize('dirtyMessageFile', "You are renaming a file with unsaved changes. Do you want to continue?");
const dirtyRenamed: URI[] = [];
return TPromise.join(dirty.map(d => {
const targetPath = paths.join(this.element.parent.resource.fsPath, newName);
let renamed: URI;
// If the dirty file itself got moved, just reparent it to the target folder
if (this.element.resource.fsPath === d.fsPath) {
renamed = URI.file(targetPath);
}
const res = this.messageService.confirm({
message,
type: 'warning',
detail: nls.localize('dirtyWarning', "Your changes will be lost if you don't save them."),
primaryButton: nls.localize({ key: 'renameLabel', comment: ['&& denotes a mnemonic'] }, "&&Rename")
});
if (!res) {
return TPromise.as(null);
// Otherwise, a parent of the dirty resource got moved, so we have to reparent more complicated. Example:
else {
renamed = URI.file(paths.join(targetPath, d.fsPath.substr(this.element.resource.fsPath.length + 1)));
}
revertPromise = this.textFileService.revertAll(dirty);
}
dirtyRenamed.push(renamed);
return revertPromise.then(() => {
return this.fileService.rename(this.element.resource, newName).then(null, (error: Error) => {
this.onErrorWithRetry(error, () => this.runAction(newName));
const model = this.textFileService.models.get(d);
return this.backupFileService.backupResource(renamed, model.getValue(), model.getVersionId());
}))
// 2. soft revert all dirty since we have backed up their contents
.then(() => this.textFileService.revertAll(dirty, { soft: true /* do not attempt to load content from disk */ }))
// 3.) run the rename operation
.then(() => this.fileService.rename(this.element.resource, newName).then(null, (error: Error) => {
return TPromise.join(dirtyRenamed.map(d => this.backupFileService.discardResourceBackup(d))).then(() => {
this.onErrorWithRetry(error, () => this.runAction(newName));
});
}))
// 4.) resolve those that were dirty to load their previous dirty contents from disk
.then(() => {
return TPromise.join(dirtyRenamed.map(t => this.textModelResolverService.createModelReference(t)));
});
});
}
}
......
......@@ -49,6 +49,8 @@ import { Keybinding, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions';
import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { ITextModelResolverService } from 'vs/editor/common/services/resolverService';
export class FileDataSource implements IDataSource {
constructor(
......@@ -723,7 +725,9 @@ export class FileDragAndDrop implements IDragAndDrop {
@IFileService private fileService: IFileService,
@IConfigurationService private configurationService: IConfigurationService,
@IInstantiationService private instantiationService: IInstantiationService,
@ITextFileService private textFileService: ITextFileService
@ITextFileService private textFileService: ITextFileService,
@ITextModelResolverService private textModelResolverService: ITextModelResolverService,
@IBackupFileService private backupFileService: IBackupFileService
) {
this.toDispose = [];
......@@ -868,70 +872,80 @@ export class FileDragAndDrop implements IDragAndDrop {
promise = tree.expand(target).then(() => {
// Reuse action if user copies
// Reuse duplicate action if user copies
if (isCopy) {
const copyAction = this.instantiationService.createInstance(DuplicateFileAction, tree, source, target);
return copyAction.run();
return this.instantiationService.createInstance(DuplicateFileAction, tree, source, target).run();
}
// Handle dirty (in file or inside the folder if any)
let revertPromise: TPromise<any> = TPromise.as(null);
const dirty = this.textFileService.getDirty().filter(d => paths.isEqualOrParent(d.fsPath, source.resource.fsPath));
if (dirty.length) {
let message: string;
if (source.isDirectory) {
if (dirty.length === 1) {
message = nls.localize('dirtyMessageFolderOne', "You are moving a folder with unsaved changes in 1 file. Do you want to continue?");
} else {
message = nls.localize('dirtyMessageFolder', "You are moving a folder with unsaved changes in {0} files. Do you want to continue?", dirty.length);
}
} else {
message = nls.localize('dirtyMessageFile', "You are moving a file with unsaved changes. Do you want to continue?");
const dirtyMoved: URI[] = [];
// Success: load all files that are dirty again to restore their dirty contents
// Error: discard any backups created during the process
const onSuccess = () => TPromise.join(dirtyMoved.map(t => this.textModelResolverService.createModelReference(t)));
const onError = (error?: Error, showError?: boolean) => {
if (showError) {
this.messageService.show(Severity.Error, error);
}
const res = this.messageService.confirm({
message,
type: 'warning',
detail: nls.localize('dirtyWarning', "Your changes will be lost if you don't save them."),
primaryButton: nls.localize({ key: 'moveLabel', comment: ['&& denotes a mnemonic'] }, "&&Move")
});
return TPromise.join(dirtyMoved.map(d => this.backupFileService.discardResourceBackup(d)));
};
// 1. check for dirty files that are being moved and backup to new target
const dirty = this.textFileService.getDirty().filter(d => paths.isEqualOrParent(d.fsPath, source.resource.fsPath));
return TPromise.join(dirty.map(d => {
let moved: URI;
if (!res) {
return TPromise.as(null);
// If the dirty file itself got moved, just reparent it to the target folder
if (source.resource.fsPath === d.fsPath) {
moved = URI.file(paths.join(target.resource.fsPath, source.name));
}
revertPromise = this.textFileService.revertAll(dirty);
}
// Otherwise, a parent of the dirty resource got moved, so we have to reparent more complicated. Example:
else {
moved = URI.file(paths.join(target.resource.fsPath, d.fsPath.substr(source.parent.resource.fsPath.length + 1)));
}
return revertPromise.then(() => {
const targetResource = URI.file(paths.join(target.resource.fsPath, source.name));
let didHandleConflict = false;
dirtyMoved.push(moved);
// Move File/Folder
return this.fileService.moveFile(source.resource, targetResource).then(null, error => {
const model = this.textFileService.models.get(d);
// Conflict
if ((<IFileOperationResult>error).fileOperationResult === FileOperationResult.FILE_MOVE_CONFLICT) {
didHandleConflict = true;
return this.backupFileService.backupResource(moved, model.getValue(), model.getVersionId());
}))
const confirm: IConfirmation = {
message: nls.localize('confirmOverwriteMessage', "'{0}' already exists in the destination folder. Do you want to replace it?", source.name),
detail: nls.localize('irreversible', "This action is irreversible!"),
primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace")
};
// 2. soft revert all dirty since we have backed up their contents
.then(() => this.textFileService.revertAll(dirty, { soft: true /* do not attempt to load content from disk */ }))
if (this.messageService.confirm(confirm)) {
return this.fileService.moveFile(source.resource, targetResource, true).then(null, (error) => {
this.messageService.show(Severity.Error, error);
});
// 3.) run the move operation
.then(() => {
const targetResource = URI.file(paths.join(target.resource.fsPath, source.name));
let didHandleConflict = false;
return this.fileService.moveFile(source.resource, targetResource).then(null, error => {
// Conflict
if ((<IFileOperationResult>error).fileOperationResult === FileOperationResult.FILE_MOVE_CONFLICT) {
didHandleConflict = true;
const confirm: IConfirmation = {
message: nls.localize('confirmOverwriteMessage', "'{0}' already exists in the destination folder. Do you want to replace it?", source.name),
detail: nls.localize('irreversible', "This action is irreversible!"),
primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace")
};
// Move with overwrite if the user confirms
if (this.messageService.confirm(confirm)) {
return this.fileService.moveFile(source.resource, targetResource, true).then(onSuccess, error => onError(error, true));
}
return onError();
}
return;
}
return onError(error, true);
});
})
this.messageService.show(Severity.Error, error);
});
});
// 4.) resolve those that were dirty to load their previous dirty contents from disk
.then(onSuccess, onError);
}, errors.onUnexpectedError);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册