提交 9b6ae0ca 编写于 作者: B Benjamin Pasero

textfiles - reduce service methods that are not needed anymore

上级 3d5b61c8
......@@ -22,6 +22,7 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura
import { IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs';
import { BackupOnShutdown } from 'vs/workbench/contrib/backup/electron-browser/backupOnShutdown';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
class ServiceAccessor {
constructor(
......@@ -34,7 +35,8 @@ class ServiceAccessor {
@IFileService public fileService: TestFileService,
@IElectronService public electronService: TestElectronService,
@IFileDialogService public fileDialogService: TestFileDialogService,
@IBackupFileService public backupFileService: TestBackupFileService
@IBackupFileService public backupFileService: TestBackupFileService,
@IWorkingCopyService public workingCopyService: IWorkingCopyService
) {
}
}
......@@ -94,7 +96,7 @@ suite('BackupOnShutdown', () => {
await model.load();
model.textEditorModel!.setValue('foo');
assert.equal(accessor.textFileService.getDirty().length, 1);
assert.equal(accessor.workingCopyService.dirtyCount, 1);
const event = new BeforeShutdownEventImpl();
accessor.lifecycleService.fireWillShutdown(event);
......@@ -110,7 +112,7 @@ suite('BackupOnShutdown', () => {
await model.load();
model.textEditorModel!.setValue('foo');
assert.equal(accessor.textFileService.getDirty().length, 1);
assert.equal(accessor.workingCopyService.dirtyCount, 1);
const event = new BeforeShutdownEventImpl();
accessor.lifecycleService.fireWillShutdown(event);
......@@ -135,7 +137,7 @@ suite('BackupOnShutdown', () => {
await model.load();
model.textEditorModel!.setValue('foo');
assert.equal(accessor.textFileService.getDirty().length, 1);
assert.equal(accessor.workingCopyService.dirtyCount, 1);
const event = new BeforeShutdownEventImpl();
accessor.lifecycleService.fireWillShutdown(event);
......@@ -269,7 +271,7 @@ suite('BackupOnShutdown', () => {
await model.load();
model.textEditorModel!.setValue('foo');
assert.equal(accessor.textFileService.getDirty().length, 1);
assert.equal(accessor.workingCopyService.dirtyCount, 1);
const event = new BeforeShutdownEventImpl();
event.reason = shutdownReason;
......
......@@ -55,6 +55,7 @@ import { ILabelService } from 'vs/platform/label/common/label';
import { isNumber } from 'vs/base/common/types';
import { domEvent } from 'vs/base/browser/event';
import { IEditableData } from 'vs/workbench/common/views';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
export class ExplorerDelegate implements IListVirtualDelegate<ExplorerItem> {
......@@ -642,7 +643,8 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
@IInstantiationService private instantiationService: IInstantiationService,
@ITextFileService private textFileService: ITextFileService,
@IHostService private hostService: IHostService,
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService,
@IWorkingCopyService private workingCopyService: IWorkingCopyService
) {
this.toDispose = [];
......@@ -946,8 +948,8 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
// if the target exists and is dirty, make sure to revert it. otherwise the dirty contents
// of the target file would replace the contents of the added file. since we already
// confirmed the overwrite before, this is OK.
if (this.textFileService.isDirty(targetFile)) {
await this.textFileService.revertAll([targetFile], { soft: true });
if (this.workingCopyService.isDirty(targetFile)) {
await Promise.all(this.workingCopyService.getWorkingCopies(targetFile).map(workingCopy => workingCopy.revert({ soft: true })));
}
const copyTarget = joinPath(target.resource, basename(sourceFile));
......
......@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as errors from 'vs/base/common/errors';
import { URI } from 'vs/base/common/uri';
import * as network from 'vs/base/common/network';
import { Disposable } from 'vs/base/common/lifecycle';
......@@ -65,21 +64,19 @@ class ReplacePreviewModel extends Disposable {
super();
}
resolve(replacePreviewUri: URI): Promise<ITextModel> {
async resolve(replacePreviewUri: URI): Promise<ITextModel> {
const fileResource = toFileResource(replacePreviewUri);
const fileMatch = <FileMatch>this.searchWorkbenchService.searchModel.searchResult.matches().filter(match => match.resource.toString() === fileResource.toString())[0];
return this.textModelResolverService.createModelReference(fileResource).then(ref => {
ref = this._register(ref);
const sourceModel = ref.object.textEditorModel;
const sourceModelModeId = sourceModel.getLanguageIdentifier().language;
const replacePreviewModel = this.modelService.createModel(createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), this.modeService.create(sourceModelModeId), replacePreviewUri);
this._register(fileMatch.onChange(modelChange => this.update(sourceModel, replacePreviewModel, fileMatch, modelChange)));
this._register(this.searchWorkbenchService.searchModel.onReplaceTermChanged(() => this.update(sourceModel, replacePreviewModel, fileMatch)));
this._register(fileMatch.onDispose(() => replacePreviewModel.dispose())); // TODO@Sandeep we should not dispose a model directly but rather the reference (depends on https://github.com/Microsoft/vscode/issues/17073)
this._register(replacePreviewModel.onWillDispose(() => this.dispose()));
this._register(sourceModel.onWillDispose(() => this.dispose()));
return replacePreviewModel;
});
const ref = this._register(await this.textModelResolverService.createModelReference(fileResource));
const sourceModel = ref.object.textEditorModel;
const sourceModelModeId = sourceModel.getLanguageIdentifier().language;
const replacePreviewModel = this.modelService.createModel(createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), this.modeService.create(sourceModelModeId), replacePreviewUri);
this._register(fileMatch.onChange(modelChange => this.update(sourceModel, replacePreviewModel, fileMatch, modelChange)));
this._register(this.searchWorkbenchService.searchModel.onReplaceTermChanged(() => this.update(sourceModel, replacePreviewModel, fileMatch)));
this._register(fileMatch.onDispose(() => replacePreviewModel.dispose())); // TODO@Sandeep we should not dispose a model directly but rather the reference (depends on https://github.com/Microsoft/vscode/issues/17073)
this._register(replacePreviewModel.onWillDispose(() => this.dispose()));
this._register(sourceModel.onWillDispose(() => this.dispose()));
return replacePreviewModel;
}
private update(sourceModel: ITextModel, replacePreviewModel: ITextModel, fileMatch: FileMatch, override: boolean = false): void {
......@@ -103,15 +100,24 @@ export class ReplaceService implements IReplaceService {
replace(match: Match): Promise<any>;
replace(files: FileMatch[], progress?: IProgress<IProgressStep>): Promise<any>;
replace(match: FileMatchOrMatch, progress?: IProgress<IProgressStep>, resource?: URI): Promise<any>;
replace(arg: any, progress: IProgress<IProgressStep> | undefined = undefined, resource: URI | null = null): Promise<any> {
async replace(arg: any, progress: IProgress<IProgressStep> | undefined = undefined, resource: URI | null = null): Promise<any> {
const edits: ResourceTextEdit[] = this.createEdits(arg, resource);
return this.bulkEditorService.apply({ edits }, { progress }).then(() => this.textFileService.saveAll(edits.map(e => e.resource)));
await this.bulkEditorService.apply({ edits }, { progress });
return Promise.all(edits.map(e => {
const model = this.textFileService.models.get(e.resource);
if (model) {
return model.save();
}
return Promise.resolve(undefined);
}));
}
openReplacePreview(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<any> {
async openReplacePreview(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<any> {
const fileMatch = element instanceof Match ? element.parent() : element;
return this.editorService.openEditor({
const editor = await this.editorService.openEditor({
leftResource: fileMatch.resource,
rightResource: toReplaceResource(fileMatch.resource),
label: nls.localize('fileReplaceChanges', "{0} ↔ {1} (Replace Preview)", fileMatch.name(), fileMatch.name()),
......@@ -120,46 +126,39 @@ export class ReplaceService implements IReplaceService {
pinned,
revealIfVisible: true
}
}).then(editor => {
const input = editor?.input;
const disposable = fileMatch.onDispose(() => {
if (input) {
input.dispose();
}
disposable.dispose();
});
this.updateReplacePreview(fileMatch).then(() => {
if (editor) {
const editorControl = editor.getControl();
if (element instanceof Match && editorControl) {
editorControl.revealLineInCenter(element.range().startLineNumber, ScrollType.Immediate);
}
}
});
}, errors.onUnexpectedError);
});
const input = editor?.input;
const disposable = fileMatch.onDispose(() => {
if (input) {
input.dispose();
}
disposable.dispose();
});
await this.updateReplacePreview(fileMatch);
if (editor) {
const editorControl = editor.getControl();
if (element instanceof Match && editorControl) {
editorControl.revealLineInCenter(element.range().startLineNumber, ScrollType.Immediate);
}
}
}
updateReplacePreview(fileMatch: FileMatch, override: boolean = false): Promise<void> {
async updateReplacePreview(fileMatch: FileMatch, override: boolean = false): Promise<void> {
const replacePreviewUri = toReplaceResource(fileMatch.resource);
return Promise.all([this.textModelResolverService.createModelReference(fileMatch.resource), this.textModelResolverService.createModelReference(replacePreviewUri)])
.then(([sourceModelRef, replaceModelRef]) => {
const sourceModel = sourceModelRef.object.textEditorModel;
const replaceModel = replaceModelRef.object.textEditorModel;
const returnValue = Promise.resolve(null);
// If model is disposed do not update
if (sourceModel && replaceModel) {
if (override) {
replaceModel.setValue(sourceModel.getValue());
} else {
replaceModel.undo();
}
this.applyEditsToPreview(fileMatch, replaceModel);
}
return returnValue.then(() => {
sourceModelRef.dispose();
replaceModelRef.dispose();
});
});
const [sourceModelRef, replaceModelRef] = await Promise.all([this.textModelResolverService.createModelReference(fileMatch.resource), this.textModelResolverService.createModelReference(replacePreviewUri)]);
const sourceModel = sourceModelRef.object.textEditorModel;
const replaceModel = replaceModelRef.object.textEditorModel;
// If model is disposed do not update
if (sourceModel && replaceModel) {
if (override) {
replaceModel.setValue(sourceModel.getValue());
} else {
replaceModel.undo();
}
this.applyEditsToPreview(fileMatch, replaceModel);
}
sourceModelRef.dispose();
replaceModelRef.dispose();
}
private applyEditsToPreview(fileMatch: FileMatch, replaceModel: ITextModel): void {
......
......@@ -487,7 +487,7 @@ export class ConfigurationEditingService {
}
// Target cannot be dirty if not writing into buffer
if (checkDirty && this.textFileService.isDirty(operation.resource)) {
if (checkDirty && operation.resource && this.textFileService.isDirty(operation.resource)) {
return this.reject<typeof reference>(ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY, target, operation);
}
return reference;
......
......@@ -177,7 +177,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
await this._onWillRunOperation.fireAsync({ operation: FileOperation.DELETE, target: resource }, CancellationToken.None);
const dirtyFiles = this.getDirty().filter(dirty => isEqualOrParent(dirty, resource));
await this.revertAll(dirtyFiles, { soft: true });
await this.doRevertAll(dirtyFiles, { soft: true });
await this.fileService.del(resource, options);
......@@ -243,7 +243,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
// in order to move and copy, we need to soft revert all dirty models,
// both from the source as well as the target if any
const dirtyModels = [...sourceModels, ...conflictingModels].filter(model => model.isDirty());
await this.revertAll(dirtyModels.map(dirtyModel => dirtyModel.resource), { soft: true });
await this.doRevertAll(dirtyModels.map(dirtyModel => dirtyModel.resource), { soft: true });
// now we can rename the source to target via file operation
let stat: IFileStatWithMetadata;
......@@ -290,85 +290,44 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
//#region save
async save(resource: URI, options?: ITextFileSaveOptions): Promise<boolean> {
return !(await this.saveAll([resource], options)).results.some(result => result.error);
}
saveAll(includeUntitled?: boolean, options?: ITextFileSaveOptions): Promise<ITextFileOperationResult>;
saveAll(resources: URI[], options?: ITextFileSaveOptions): Promise<ITextFileOperationResult>;
saveAll(arg1?: boolean | URI[], options?: ITextFileSaveOptions): Promise<ITextFileOperationResult> {
// Extract the resources to save
let resourcesToSave: URI[] = [];
if (Array.isArray(arg1)) {
// if specific resources are given, we consider even
// non-dirty ones if options.force is provided
if (options?.force) {
resourcesToSave = arg1;
} else {
resourcesToSave = this.getDirty(arg1);
}
} else {
// if no resources are given, we only consider dirty
// resources even if options.force is provided
resourcesToSave = this.getDirty();
}
// split up between files and untitled
const filesToSave: URI[] = [];
const untitledToSave: URI[] = [];
resourcesToSave.forEach(resourceToSave => {
if ((Array.isArray(arg1) || arg1 === true /* includeUntitled */) && resourceToSave.scheme === Schemas.untitled) {
untitledToSave.push(resourceToSave);
} else if (this.fileService.canHandleResource(resourceToSave)) {
filesToSave.push(resourceToSave);
}
});
return this.doSaveAll(filesToSave, untitledToSave, options);
}
private async doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ITextFileSaveOptions): Promise<ITextFileOperationResult> {
// Handle files first that can just be saved
const result = await this.doSaveAllFiles(fileResources, options);
// Preflight for untitled to handle cancellation from the dialog
const targetsForUntitled: URI[] = [];
for (const untitled of untitledResources) {
if (this.untitledTextEditorService.exists(untitled)) {
let targetUri: URI;
// Untitled
if (resource.scheme === Schemas.untitled) {
if (this.untitledTextEditorService.exists(resource)) {
let targetUri: URI | undefined;
// Untitled with associated file path don't need to prompt
if (this.untitledTextEditorService.hasAssociatedFilePath(untitled)) {
targetUri = toLocalResource(untitled, this.environmentService.configuration.remoteAuthority);
if (this.untitledTextEditorService.hasAssociatedFilePath(resource)) {
targetUri = toLocalResource(resource, this.environmentService.configuration.remoteAuthority);
}
// Otherwise ask user
else {
const targetPath = await this.promptForPath(untitled, this.suggestFileName(untitled));
if (!targetPath) {
return { results: [...fileResources, ...untitledResources].map(r => ({ source: r })) };
}
targetUri = targetPath;
targetUri = await this.promptForPath(resource, this.suggestFileName(resource));
}
targetsForUntitled.push(targetUri);
// Save as if target provided
if (targetUri) {
await this.saveAs(resource, targetUri, options);
return true;
}
}
}
// Handle untitled
await Promise.all(targetsForUntitled.map(async (target, index) => {
const uri = await this.saveAs(untitledResources[index], target);
// File
else {
const model = this.models.get(resource);
if (model) {
result.results.push({
source: untitledResources[index],
target: uri,
error: !uri // the operation was canceled or failed, so mark as error
});
}));
// Save with options
await model.save(options);
return result;
return !model.isDirty();
}
}
return false;
}
protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: readonly string[]): Promise<URI | undefined> {
......@@ -432,32 +391,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
return options;
}
private async doSaveAllFiles(resources?: URI[], options: ITextFileSaveOptions = Object.create(null)): Promise<ITextFileOperationResult> {
const fileModelsToSave = this.getFileModels(resources);
const mapResourceToResult = new ResourceMap<IResult>();
for (const fileModelToSave of fileModelsToSave) {
mapResourceToResult.set(fileModelToSave.resource, { source: fileModelToSave.resource });
}
// Save all in parallel
await Promise.all(fileModelsToSave.map(async model => {
// Save with options
await model.save(options);
// If model is still dirty, mark the resulting operation as error
if (model.isDirty()) {
const result = mapResourceToResult.get(model.resource);
if (result) {
result.error = true;
}
}
}));
return { results: mapResourceToResult.values() };
}
private getFileModels(arg1?: URI | URI[]): ITextFileEditorModel[] {
if (Array.isArray(arg1)) {
const models: ITextFileEditorModel[] = [];
......@@ -682,10 +615,10 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
//#region revert
async revert(resource: URI, options?: IRevertOptions): Promise<boolean> {
return !(await this.revertAll([resource], options)).results.some(result => result.error);
return !(await this.doRevertAll([resource], options)).results.some(result => result.error);
}
async revertAll(resources?: URI[], options?: IRevertOptions): Promise<ITextFileOperationResult> {
private async doRevertAll(resources?: URI[], options?: IRevertOptions): Promise<ITextFileOperationResult> {
// Revert files first
const revertOperationResult = await this.doRevertAllFiles(resources, options);
......@@ -739,18 +672,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
//#region dirty
getDirty(resources?: URI[]): URI[] {
// Collect files
const dirty = this.getDirtyFileModels(resources).map(dirtyFileModel => dirtyFileModel.resource);
// Add untitled ones
dirty.push(...this.untitledTextEditorService.getDirty(resources));
return dirty;
}
isDirty(resource?: URI): boolean {
isDirty(resource: URI): boolean {
// Check for dirty file
if (this.models.getAll(resource).some(model => model.isDirty())) {
......@@ -761,5 +683,16 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
return this.untitledTextEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString());
}
protected getDirty(resources?: URI[]): URI[] {
// Collect files
const dirty = this.getDirtyFileModels(resources).map(dirtyFileModel => dirtyFileModel.resource);
// Add untitled ones
dirty.push(...this.untitledTextEditorService.getDirty(resources));
return dirty;
}
//#endregion
}
......@@ -45,18 +45,9 @@ export interface ITextFileService extends IDisposable {
/**
* A resource is dirty if it has unsaved changes or is an untitled file not yet saved.
*
* @param resource the resource to check for being dirty. If it is not specified, will check for
* all dirty resources.
* @param resource the resource to check for being dirty
*/
isDirty(resource?: URI): boolean;
/**
* Returns all resources that are currently dirty matching the provided resources or all dirty resources.
*
* @param resources the resources to check for being dirty. If it is not specified, will check for
* all dirty resources.
*/
getDirty(resources?: URI[]): URI[];
isDirty(resource: URI): boolean;
/**
* Saves the resource.
......@@ -77,15 +68,6 @@ export interface ITextFileService extends IDisposable {
*/
saveAs(resource: URI, targetResource?: URI, options?: ISaveOptions): Promise<URI | undefined>;
/**
* Saves the set of resources and returns a promise with the operation result.
*
* @param resources can be null to save all.
* @param includeUntitled to save all resources and optionally exclude untitled ones.
*/
saveAll(includeUntitled?: boolean, options?: ISaveOptions): Promise<ITextFileOperationResult>;
saveAll(resources: URI[], options?: ISaveOptions): Promise<ITextFileOperationResult>;
/**
* Reverts the provided resource.
*
......@@ -94,11 +76,6 @@ export interface ITextFileService extends IDisposable {
*/
revert(resource: URI, options?: IRevertOptions): Promise<boolean>;
/**
* Reverts all the provided resources and returns a promise with the operation result.
*/
revertAll(resources?: URI[], options?: IRevertOptions): Promise<ITextFileOperationResult>;
/**
* Create a file. If the file exists it will be overwritten with the contents if
* the options enable to overwrite.
......
......@@ -386,7 +386,6 @@ suite('Files - TextFileEditorModel', () => {
assert.ok(m1Mtime > 0);
assert.ok(m2Mtime > 0);
assert.ok(accessor.textFileService.isDirty());
assert.ok(accessor.textFileService.isDirty(toResource.call(this, '/path/index_async2.txt')));
assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt')));
......@@ -394,7 +393,8 @@ suite('Files - TextFileEditorModel', () => {
assert.ok(accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt')));
await timeout(10);
await accessor.textFileService.saveAll();
await accessor.textFileService.save(toResource.call(this, '/path/index_async.txt'));
await accessor.textFileService.save(toResource.call(this, '/path/index_async2.txt'));
assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt')));
assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async2.txt')));
assert.ok(assertIsDefined(model1.getStat()).mtime > m1Mtime);
......
......@@ -60,42 +60,38 @@ suite('Files - TextFileService', () => {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.resource, model);
const service = accessor.textFileService;
await model.load();
assert.ok(!service.isDirty(model.resource));
assert.ok(!accessor.textFileService.isDirty(model.resource));
model.textEditorModel!.setValue('foo');
assert.ok(service.isDirty(model.resource));
assert.equal(service.getDirty().length, 1);
assert.equal(service.getDirty([model.resource])[0].toString(), model.resource.toString());
assert.ok(accessor.textFileService.isDirty(model.resource));
assert.equal(accessor.textFileService.getDirty().length, 1);
assert.equal(accessor.textFileService.getDirty([model.resource])[0].toString(), model.resource.toString());
const untitled = accessor.untitledTextEditorService.createOrGet();
const untitledModel = await untitled.resolve();
assert.ok(!service.isDirty(untitled.getResource()));
assert.equal(service.getDirty().length, 1);
assert.ok(!accessor.textFileService.isDirty(untitled.getResource()));
assert.equal(accessor.textFileService.getDirty().length, 1);
untitledModel.textEditorModel!.setValue('changed');
assert.ok(service.isDirty(untitled.getResource()));
assert.equal(service.getDirty().length, 2);
assert.equal(service.getDirty([untitled.getResource()])[0].toString(), untitled.getResource().toString());
assert.ok(accessor.textFileService.isDirty(untitled.getResource()));
assert.equal(accessor.textFileService.getDirty().length, 2);
assert.equal(accessor.textFileService.getDirty([untitled.getResource()])[0].toString(), untitled.getResource().toString());
});
test('save - file', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.resource, model);
const service = accessor.textFileService;
await model.load();
model.textEditorModel!.setValue('foo');
assert.ok(service.isDirty(model.resource));
assert.ok(accessor.textFileService.isDirty(model.resource));
const res = await service.save(model.resource);
const res = await accessor.textFileService.save(model.resource);
assert.ok(res);
assert.ok(!service.isDirty(model.resource));
assert.ok(!accessor.textFileService.isDirty(model.resource));
});
test('save - UNC path', async function () {
......@@ -114,76 +110,62 @@ suite('Files - TextFileService', () => {
await model.load();
model.textEditorModel!.setValue('foo');
const res = await accessor.textFileService.saveAll(true);
const res = await accessor.textFileService.save(untitledUncUri);
assert.ok(loadOrCreateStub.calledOnce);
assert.equal(res.results.length, 1);
assert.ok(!res.results[0].error);
assert.equal(res.results[0].target!.scheme, Schemas.file);
assert.equal(res.results[0].target!.authority, untitledUncUri.authority);
assert.equal(res.results[0].target!.path, untitledUncUri.path);
assert.ok(res);
});
test('saveAll - file', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.resource, model);
const service = accessor.textFileService;
await model.load();
model.textEditorModel!.setValue('foo');
assert.ok(service.isDirty(model.resource));
assert.ok(accessor.textFileService.isDirty(model.resource));
const res = await service.saveAll([model.resource]);
const res = await accessor.textFileService.save(model.resource);
assert.ok(res);
assert.ok(!service.isDirty(model.resource));
assert.equal(res.results.length, 1);
assert.equal(res.results[0].source.toString(), model.resource.toString());
assert.ok(!accessor.textFileService.isDirty(model.resource));
});
test('saveAs - file', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.resource, model);
const service = accessor.textFileService;
service.setPromptPath(model.resource);
accessor.textFileService.setPromptPath(model.resource);
await model.load();
model.textEditorModel!.setValue('foo');
assert.ok(service.isDirty(model.resource));
assert.ok(accessor.textFileService.isDirty(model.resource));
const res = await service.saveAs(model.resource);
const res = await accessor.textFileService.saveAs(model.resource);
assert.equal(res!.toString(), model.resource.toString());
assert.ok(!service.isDirty(model.resource));
assert.ok(!accessor.textFileService.isDirty(model.resource));
});
test('revert - file', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.resource, model);
const service = accessor.textFileService;
service.setPromptPath(model.resource);
accessor.textFileService.setPromptPath(model.resource);
await model.load();
model!.textEditorModel!.setValue('foo');
assert.ok(service.isDirty(model.resource));
assert.ok(accessor.textFileService.isDirty(model.resource));
const res = await service.revert(model.resource);
const res = await accessor.textFileService.revert(model.resource);
assert.ok(res);
assert.ok(!service.isDirty(model.resource));
assert.ok(!accessor.textFileService.isDirty(model.resource));
});
test('delete - dirty file', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.resource, model);
const service = accessor.textFileService;
await model.load();
model!.textEditorModel!.setValue('foo');
assert.ok(service.isDirty(model.resource));
assert.ok(accessor.textFileService.isDirty(model.resource));
await service.delete(model.resource);
assert.ok(!service.isDirty(model.resource));
await accessor.textFileService.delete(model.resource);
assert.ok(!accessor.textFileService.isDirty(model.resource));
});
test('move - dirty file', async function () {
......@@ -200,24 +182,22 @@ suite('Files - TextFileService', () => {
(<TextFileEditorModelManager>accessor.textFileService.models).add(sourceModel.resource, sourceModel);
(<TextFileEditorModelManager>accessor.textFileService.models).add(targetModel.resource, targetModel);
const service = accessor.textFileService;
await sourceModel.load();
sourceModel.textEditorModel!.setValue('foo');
assert.ok(service.isDirty(sourceModel.resource));
assert.ok(accessor.textFileService.isDirty(sourceModel.resource));
if (targetDirty) {
await targetModel.load();
targetModel.textEditorModel!.setValue('bar');
assert.ok(service.isDirty(targetModel.resource));
assert.ok(accessor.textFileService.isDirty(targetModel.resource));
}
await service.move(sourceModel.resource, targetModel.resource, true);
await accessor.textFileService.move(sourceModel.resource, targetModel.resource, true);
assert.equal(targetModel.textEditorModel!.getValue(), 'foo');
assert.ok(!service.isDirty(sourceModel.resource));
assert.ok(service.isDirty(targetModel.resource));
assert.ok(!accessor.textFileService.isDirty(sourceModel.resource));
assert.ok(accessor.textFileService.isDirty(targetModel.resource));
sourceModel.dispose();
targetModel.dispose();
......
......@@ -94,6 +94,8 @@ export interface IWorkingCopyService {
readonly workingCopies: IWorkingCopy[];
getWorkingCopies(resource: URI): IWorkingCopy[];
registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable;
//#endregion
......@@ -127,6 +129,12 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic
get workingCopies(): IWorkingCopy[] { return values(this._workingCopies); }
private _workingCopies = new Set<IWorkingCopy>();
getWorkingCopies(resource: URI): IWorkingCopy[] {
const workingCopies = this.mapResourceToWorkingCopy.get(resource.toString());
return workingCopies ? values(workingCopies) : [];
}
registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable {
const disposables = new DisposableStore();
......
......@@ -109,6 +109,8 @@ suite('WorkingCopyService', () => {
assert.equal(service.dirtyCount, 1);
assert.equal(service.dirtyWorkingCopies.length, 1);
assert.equal(service.dirtyWorkingCopies[0], copy1);
assert.equal(service.getWorkingCopies(copy1.resource).length, 1);
assert.equal(service.getWorkingCopies(copy1.resource)[0], copy1);
assert.equal(service.isDirty(resource1), true);
assert.equal(service.hasDirty, true);
assert.equal(onDidChangeDirty.length, 1);
......@@ -176,6 +178,10 @@ suite('WorkingCopyService', () => {
const copy2 = new TestWorkingCopy(resource);
const unregister2 = service.registerWorkingCopy(copy2);
assert.equal(service.getWorkingCopies(copy1.resource).length, 2);
assert.equal(service.getWorkingCopies(copy1.resource)[0], copy1);
assert.equal(service.getWorkingCopies(copy1.resource)[1], copy2);
copy1.setDirty(true);
assert.equal(service.dirtyCount, 1);
......
......@@ -261,6 +261,10 @@ export class TestTextFileService extends NativeTextFileService {
promptForPath(_resource: URI, _defaultPath: URI): Promise<URI> {
return Promise.resolve(this.promptPath);
}
getDirty(resources?: URI[]): URI[] {
return super.getDirty(resources);
}
}
export interface ITestInstantiationService extends IInstantiationService {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册