Skip to content

Commit

Permalink
fix #42640
Browse files Browse the repository at this point in the history
  • Loading branch information
bpasero committed Jun 15, 2018
1 parent 5f93604 commit 11c2ef6
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ class RenameFileAction extends BaseRenameAction {
renamed = this.element.parent.resource.with({ path: targetPath });
}

// Otherwise, a parent of the dirty resource got moved, so we have to reparent more complicated. Example:
// Otherwise, a parent of the dirty resource got moved, so we have to reparent more complicated
else {
renamed = this.element.parent.resource.with({ path: paths.join(targetPath, d.path.substr(this.element.resource.path.length + 1)) });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { IProgress, IProgressRunner, emptyProgressRunner } from 'vs/platform/pro
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';

abstract class Recording {

Expand Down Expand Up @@ -269,6 +270,7 @@ export class BulkEdit {
progress: IProgressRunner,
@ITextModelService private readonly _textModelService: ITextModelService,
@IFileService private readonly _fileService: IFileService,
@ITextFileService private readonly _textFileService: ITextFileService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService
) {
Expand Down Expand Up @@ -345,9 +347,9 @@ export class BulkEdit {
progress.report(undefined);

if (edit.newUri && edit.oldUri) {
await this._fileService.moveFile(edit.oldUri, edit.newUri, false);
await this._textFileService.move(edit.oldUri, edit.newUri, false);
} else if (!edit.newUri && edit.oldUri) {
await this._fileService.del(edit.oldUri, true);
await this._textFileService.delete(edit.oldUri, true);
} else if (edit.newUri && !edit.oldUri) {
await this._fileService.createFile(edit.newUri, undefined, { overwrite: false });
}
Expand Down Expand Up @@ -387,6 +389,7 @@ export class BulkEditService implements IBulkEditService {
@IEditorService private readonly _editorService: IEditorService,
@ITextModelService private readonly _textModelService: ITextModelService,
@IFileService private readonly _fileService: IFileService,
@ITextFileService private readonly _textFileService: ITextFileService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService
) {
Expand Down Expand Up @@ -419,7 +422,7 @@ export class BulkEditService implements IBulkEditService {
}
}

const bulkEdit = new BulkEdit(options.editor, options.progress, this._textModelService, this._fileService, this._environmentService, this._contextService);
const bulkEdit = new BulkEdit(options.editor, options.progress, this._textModelService, this._fileService, this._textFileService, this._environmentService, this._contextService);
bulkEdit.add(edits);
return bulkEdit.perform().then(selection => {
return {
Expand Down
52 changes: 52 additions & 0 deletions src/vs/workbench/services/textfile/common/textFileService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,58 @@ export abstract class TextFileService implements ITextFileService {
});
}

public delete(resource: URI, useTrash?: boolean): TPromise<void> {
return this.revert(resource, { soft: true }).then(() => this.fileService.del(resource, useTrash));
}

public move(source: URI, target: URI, overwrite?: boolean): TPromise<void> {

// Handle target model if existing
let handleTargetModelPromise: TPromise<any> = TPromise.as(void 0);
const targetModel = this.models.get(target);
if (targetModel) {
if (!overwrite) {
return TPromise.wrapError(new Error('Cannot move file because target file exists and we are not overwriting'));
}

// Soft revert the target file since we overwrite
handleTargetModelPromise = this.revert(target, { soft: true });
}

return handleTargetModelPromise.then(() => {

// Handle source model if existing
let handleSourceModelPromise: TPromise<boolean>;
const sourceModel = this.models.get(source);
if (sourceModel && sourceModel.isDirty()) {
// Backup to target if the source is dirty
handleSourceModelPromise = this.backupFileService.backupResource(target, sourceModel.createSnapshot(), sourceModel.getVersionId()).then((() => true));
} else {
handleSourceModelPromise = TPromise.as(false);
}

return handleSourceModelPromise.then(dirty => {

// Soft revert the source file
return this.revert(source, { soft: true }).then(() => {

// Rename to target
return this.fileService.moveFile(source, target, overwrite).then(() => {

// Load if we were dirty before
if (dirty) {
return this.models.loadOrCreate(target).then(() => void 0);
}

return void 0;
}, error => {
return this.backupFileService.discardResourceBackup(target).then(() => TPromise.wrapError(error));
});
});
});
});
}

public getAutoSaveMode(): AutoSaveMode {
if (this.configuredAutoSaveOnFocusChange) {
return AutoSaveMode.ON_FOCUS_CHANGE;
Expand Down
24 changes: 16 additions & 8 deletions src/vs/workbench/services/textfile/common/textfiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,13 +237,16 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport

export interface ITextFileService extends IDisposable {
_serviceBrand: any;
onAutoSaveConfigurationChange: Event<IAutoSaveConfiguration>;
onFilesAssociationChange: Event<void>;

readonly onAutoSaveConfigurationChange: Event<IAutoSaveConfiguration>;
readonly onFilesAssociationChange: Event<void>;

readonly isHotExitEnabled: boolean;

/**
* Access to the manager of text file editor models providing further methods to work with them.
*/
models: ITextFileEditorModelManager;
readonly models: ITextFileEditorModelManager;

/**
* Resolve the contents of a file identified by the resource.
Expand Down Expand Up @@ -307,6 +310,16 @@ export interface ITextFileService extends IDisposable {
*/
revertAll(resources?: URI[], options?: IRevertOptions): TPromise<ITextFileOperationResult>;

/**
* Delete a file. If the file is dirty, it will get reverted and then deleted from disk.
*/
delete(resource: URI, useTrash?: boolean): TPromise<void>;

/**
* Move a file. If the file is dirty, its contents will be preserved and restored.
*/
move(source: URI, target: URI, overwrite?: boolean): TPromise<void>;

/**
* Brings up the confirm dialog to either save, don't save or cancel.
*
Expand All @@ -324,9 +337,4 @@ export interface ITextFileService extends IDisposable {
* Convinient fast access to the raw configured auto save settings.
*/
getAutoSaveConfiguration(): IAutoSaveConfiguration;

/**
* Convinient fast access to the hot exit file setting.
*/
isHotExitEnabled: boolean;
}
39 changes: 39 additions & 0 deletions src/vs/workbench/services/textfile/test/textFileService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,45 @@ suite('Files - TextFileService', () => {
});
});

test('delete - dirty file', function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);

const service = accessor.textFileService;

return model.load().then(() => {
model.textEditorModel.setValue('foo');

assert.ok(service.isDirty(model.getResource()));

return service.delete(model.getResource()).then(() => {
assert.ok(!service.isDirty(model.getResource()));
});
});
});

test('move - dirty file', function () {
let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
let targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_target.txt'), 'utf8');
(<TextFileEditorModelManager>accessor.textFileService.models).add(sourceModel.getResource(), sourceModel);
(<TextFileEditorModelManager>accessor.textFileService.models).add(targetModel.getResource(), targetModel);

const service = accessor.textFileService;

return sourceModel.load().then(() => {
sourceModel.textEditorModel.setValue('foo');

assert.ok(service.isDirty(sourceModel.getResource()));

return service.move(sourceModel.getResource(), targetModel.getResource(), true).then(() => {
assert.ok(!service.isDirty(sourceModel.getResource()));

sourceModel.dispose();
targetModel.dispose();
});
});
});

suite('Hot Exit', () => {
suite('"onExit" setting', () => {
test('should hot exit on non-Mac (reason: CLOSE, windows: single, workspace)', function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,23 @@ suite('MainThreadEditors', () => {
deletedResources.clear();

const fileService = new class extends TestFileService {
async moveFile(from: URI, target: URI): TPromise<IFileStat> {
movedResources.set(from, target);
return createMockFileStat(target);
}
async createFile(uri: URI): TPromise<IFileStat> {
createdResources.add(uri);
return createMockFileStat(uri);
}
async del(uri: URI): TPromise<any> {
deletedResources.add(uri);
}
};


const textFileService = new class extends mock<ITextFileService>() {
isDirty() { return false; }
delete(resource: URI) {
deletedResources.add(resource);
return TPromise.as(void 0);
}
move(source: URI, target: URI) {
movedResources.set(source, target);
return TPromise.as(void 0);
}
models = <any>{
onModelSaved: Event.None,
onModelReverted: Event.None,
Expand All @@ -73,7 +74,7 @@ suite('MainThreadEditors', () => {
const workbenchEditorService = new TestEditorService();
const editorGroupService = new TestEditorGroupsService();

const bulkEditService = new BulkEditService(modelService, new TestEditorService(), null, fileService, TestEnvironmentService, new TestContextService());
const bulkEditService = new BulkEditService(modelService, new TestEditorService(), null, fileService, textFileService, TestEnvironmentService, new TestContextService());

const rpcProtocol = new TestRPCProtocol();
rpcProtocol.set(ExtHostContext.ExtHostDocuments, new class extends mock<ExtHostDocumentsShape>() {
Expand Down

0 comments on commit 11c2ef6

Please sign in to comment.