mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
fix #89780
This commit is contained in:
parent
920559ace1
commit
30b939c18f
|
@ -25,7 +25,8 @@ import { ILabelService } from 'vs/platform/label/common/label';
|
|||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { Recording } from 'vs/workbench/services/bulkEdit/browser/conflicts';
|
||||
import { ConflictDetector } from 'vs/workbench/services/bulkEdit/browser/conflicts';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
|
||||
type ValidationResult = { canApply: true } | { canApply: false, reason: URI };
|
||||
|
@ -234,10 +235,9 @@ class BulkEdit {
|
|||
editor: ICodeEditor | undefined,
|
||||
progress: IProgress<IProgressStep> | undefined,
|
||||
edits: Edit[],
|
||||
@IInstantiationService private readonly _instaService: IInstantiationService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@ITextModelService private readonly _textModelService: ITextModelService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@IEditorWorkerService private readonly _workerService: IEditorWorkerService,
|
||||
@ITextFileService private readonly _textFileService: ITextFileService,
|
||||
@ILabelService private readonly _uriLabelServie: ILabelService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService
|
||||
|
@ -288,14 +288,15 @@ class BulkEdit {
|
|||
// for child operations
|
||||
this._progress.report({ total });
|
||||
|
||||
let progress: IProgress<void> = { report: _ => this._progress.report({ increment: 1 }) };
|
||||
const conflicts = this._instaService.createInstance(ConflictDetector, { edits: this._edits });
|
||||
const progress: IProgress<void> = { report: _ => this._progress.report({ increment: 1 }) };
|
||||
|
||||
// do it.
|
||||
for (const group of groups) {
|
||||
if (WorkspaceFileEdit.is(group[0])) {
|
||||
await this._performFileEdits(<WorkspaceFileEdit[]>group, progress);
|
||||
} else {
|
||||
await this._performTextEdits(<WorkspaceTextEdit[]>group, progress);
|
||||
await this._performTextEdits(<WorkspaceTextEdit[]>group, conflicts, progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -335,25 +336,14 @@ class BulkEdit {
|
|||
}
|
||||
}
|
||||
|
||||
private async _performTextEdits(edits: WorkspaceTextEdit[], progress: IProgress<void>): Promise<void> {
|
||||
private async _performTextEdits(edits: WorkspaceTextEdit[], conflicts: ConflictDetector, progress: IProgress<void>): Promise<void> {
|
||||
this._logService.debug('_performTextEdits', JSON.stringify(edits));
|
||||
|
||||
const recording = Recording.start(this._fileService);
|
||||
const model = new BulkEditModel(this._editor, progress, edits, this._workerService, this._textModelService);
|
||||
const model = this._instaService.createInstance(BulkEditModel, this._editor, progress, edits);
|
||||
|
||||
await model.prepare();
|
||||
|
||||
const conflicts = edits
|
||||
.filter(edit => recording.hasChanged(edit.resource))
|
||||
.map(edit => this._uriLabelServie.getUriLabel(edit.resource, { relative: true }));
|
||||
|
||||
recording.stop();
|
||||
|
||||
if (conflicts.length > 0) {
|
||||
model.dispose();
|
||||
throw new Error(localize('conflict', "These files have changed in the meantime: {0}", conflicts.join(', ')));
|
||||
}
|
||||
|
||||
this._throwIfConflicts(conflicts);
|
||||
const validationResult = model.validate();
|
||||
if (validationResult.canApply === false) {
|
||||
model.dispose();
|
||||
|
@ -363,6 +353,13 @@ class BulkEdit {
|
|||
model.apply();
|
||||
model.dispose();
|
||||
}
|
||||
|
||||
private _throwIfConflicts(conflicts: ConflictDetector) {
|
||||
if (conflicts.hasConflicts()) {
|
||||
const paths = conflicts.list().map(uri => this._uriLabelServie.getUriLabel(uri, { relative: true }));
|
||||
throw new Error(localize('conflict', "These files have changed in the meantime: {0}", paths.join(', ')));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class BulkEditService implements IBulkEditService {
|
||||
|
@ -372,15 +369,10 @@ export class BulkEditService implements IBulkEditService {
|
|||
private _previewHandler?: IBulkEditPreviewHandler;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly _instaService: IInstantiationService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IModelService private readonly _modelService: IModelService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@IEditorWorkerService private readonly _workerService: IEditorWorkerService,
|
||||
@ITextModelService private readonly _textModelService: ITextModelService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@ITextFileService private readonly _textFileService: ITextFileService,
|
||||
@ILabelService private readonly _labelService: ILabelService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService
|
||||
) { }
|
||||
|
||||
setPreviewHandler(handler: IBulkEditPreviewHandler): IDisposable {
|
||||
|
@ -433,10 +425,7 @@ export class BulkEditService implements IBulkEditService {
|
|||
// If the code editor is readonly still allow bulk edits to be applied #68549
|
||||
codeEditor = undefined;
|
||||
}
|
||||
const bulkEdit = new BulkEdit(
|
||||
codeEditor, options?.progress, edits,
|
||||
this._logService, this._textModelService, this._fileService, this._workerService, this._textFileService, this._labelService, this._configurationService
|
||||
);
|
||||
const bulkEdit = this._instaService.createInstance(BulkEdit, codeEditor, options?.progress, edits);
|
||||
return bulkEdit.perform().then(() => {
|
||||
return { ariaSummary: bulkEdit.ariaMessage() };
|
||||
}).catch(err => {
|
||||
|
|
|
@ -10,31 +10,11 @@ import { IModelService } from 'vs/editor/common/services/modelService';
|
|||
import { ResourceMap } from 'vs/base/common/map';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import type { ITextModel } from 'vs/editor/common/model';
|
||||
|
||||
export abstract class Recording {
|
||||
|
||||
static start(fileService: IFileService): Recording {
|
||||
|
||||
let _changes = new Set<string>();
|
||||
let subscription = fileService.onAfterOperation(e => {
|
||||
_changes.add(e.resource.toString());
|
||||
});
|
||||
|
||||
return {
|
||||
stop() { return subscription.dispose(); },
|
||||
hasChanged(resource) { return _changes.has(resource.toString()); }
|
||||
};
|
||||
}
|
||||
|
||||
abstract stop(): void;
|
||||
abstract hasChanged(resource: URI): boolean;
|
||||
}
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
|
||||
export class ConflictDetector {
|
||||
|
||||
private readonly _conflicts = new ResourceMap<boolean>();
|
||||
private readonly _changes = new ResourceMap<boolean>();
|
||||
private readonly _disposables = new DisposableStore();
|
||||
|
||||
private readonly _onDidConflict = new Emitter<this>();
|
||||
|
@ -79,9 +59,6 @@ export class ConflictDetector {
|
|||
continue;
|
||||
}
|
||||
|
||||
// change
|
||||
this._changes.set(change.resource, true);
|
||||
|
||||
// conflict
|
||||
if (_workspaceEditResources.has(change.resource)) {
|
||||
this._conflicts.set(change.resource, true);
|
||||
|
@ -92,8 +69,6 @@ export class ConflictDetector {
|
|||
|
||||
// listen to model changes...?
|
||||
const onDidChangeModel = (model: ITextModel) => {
|
||||
// change
|
||||
this._changes.set(model.uri, true);
|
||||
|
||||
// conflict
|
||||
if (_workspaceEditResources.has(model.uri)) {
|
||||
|
@ -112,12 +87,10 @@ export class ConflictDetector {
|
|||
}
|
||||
|
||||
list(): URI[] {
|
||||
const result: URI[] = this._conflicts.keys();
|
||||
// this._changes.forEach((_value, key) => {
|
||||
// if (!this._conflicts.has(key)) {
|
||||
// result.push(key);
|
||||
// }
|
||||
// });
|
||||
return result;
|
||||
return this._conflicts.keys();
|
||||
}
|
||||
|
||||
hasConflicts(): boolean {
|
||||
return this._conflicts.size > 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,13 +21,25 @@ import { IModelService } from 'vs/editor/common/services/modelService';
|
|||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { TestFileService, TestEditorService, TestEditorGroupsService, TestEnvironmentService, TestContextService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices';
|
||||
import { BulkEditService } from 'vs/workbench/services/bulkEdit/browser/bulkEditService';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
import { NullLogService, ILogService } from 'vs/platform/log/common/log';
|
||||
import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
|
||||
import { IReference, ImmortalReference } from 'vs/base/common/lifecycle';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { LabelService } from 'vs/workbench/services/label/common/labelService';
|
||||
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
|
||||
suite('MainThreadEditors', () => {
|
||||
|
||||
|
@ -42,18 +54,30 @@ suite('MainThreadEditors', () => {
|
|||
const deletedResources = new Set<URI>();
|
||||
|
||||
setup(() => {
|
||||
const configService = new TestConfigurationService();
|
||||
modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService());
|
||||
const codeEditorService = new TestCodeEditorService();
|
||||
|
||||
movedResources.clear();
|
||||
copiedResources.clear();
|
||||
createdResources.clear();
|
||||
deletedResources.clear();
|
||||
|
||||
const fileService = new TestFileService();
|
||||
|
||||
const textFileService = new class extends mock<ITextFileService>() {
|
||||
const configService = new TestConfigurationService();
|
||||
modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService());
|
||||
|
||||
|
||||
const services = new ServiceCollection();
|
||||
services.set(IBulkEditService, new SyncDescriptor(BulkEditService));
|
||||
services.set(ILabelService, new SyncDescriptor(LabelService));
|
||||
services.set(ILogService, new NullLogService());
|
||||
services.set(IWorkspaceContextService, new TestContextService());
|
||||
services.set(IWorkbenchEnvironmentService, TestEnvironmentService);
|
||||
services.set(IConfigurationService, configService);
|
||||
services.set(IModelService, modelService);
|
||||
services.set(ICodeEditorService, new TestCodeEditorService());
|
||||
services.set(IFileService, new TestFileService());
|
||||
services.set(IEditorService, new TestEditorService());
|
||||
services.set(IEditorGroupsService, new TestEditorGroupsService());
|
||||
services.set(ITextFileService, new class extends mock<ITextFileService>() {
|
||||
isDirty() { return false; }
|
||||
create(uri: URI, contents?: string, options?: any) {
|
||||
createdResources.add(uri);
|
||||
|
@ -76,10 +100,8 @@ suite('MainThreadEditors', () => {
|
|||
onDidRevert: Event.None,
|
||||
onDidChangeDirty: Event.None
|
||||
};
|
||||
};
|
||||
const workbenchEditorService = new TestEditorService();
|
||||
const editorGroupService = new TestEditorGroupsService();
|
||||
const textModelService = new class extends mock<ITextModelService>() {
|
||||
});
|
||||
services.set(ITextModelService, new class extends mock<ITextModelService>() {
|
||||
createModelReference(resource: URI): Promise<IReference<IResolvedTextEditorModel>> {
|
||||
const textEditorModel = new class extends mock<IResolvedTextEditorModel>() {
|
||||
textEditorModel = modelService.getModel(resource)!;
|
||||
|
@ -87,13 +109,20 @@ suite('MainThreadEditors', () => {
|
|||
textEditorModel.isReadonly = () => false;
|
||||
return Promise.resolve(new ImmortalReference(textEditorModel));
|
||||
}
|
||||
};
|
||||
});
|
||||
services.set(IEditorWorkerService, new class extends mock<IEditorWorkerService>() {
|
||||
|
||||
const editorWorkerService = new class extends mock<IEditorWorkerService>() {
|
||||
});
|
||||
services.set(IPanelService, new class extends mock<IPanelService>() implements IPanelService {
|
||||
_serviceBrand: undefined;
|
||||
onDidPanelOpen = Event.None;
|
||||
onDidPanelClose = Event.None;
|
||||
getActivePanel() {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
const bulkEditService = new BulkEditService(new NullLogService(), modelService, new TestEditorService(), editorWorkerService, textModelService, new TestFileService(), textFileService, new LabelService(TestEnvironmentService, new TestContextService()), configService);
|
||||
const instaService = new InstantiationService(services);
|
||||
|
||||
const rpcProtocol = new TestRPCProtocol();
|
||||
rpcProtocol.set(ExtHostContext.ExtHostDocuments, new class extends mock<ExtHostDocumentsShape>() {
|
||||
|
@ -105,35 +134,9 @@ suite('MainThreadEditors', () => {
|
|||
}
|
||||
});
|
||||
|
||||
const documentAndEditor = new MainThreadDocumentsAndEditors(
|
||||
rpcProtocol,
|
||||
modelService,
|
||||
textFileService,
|
||||
workbenchEditorService,
|
||||
codeEditorService,
|
||||
fileService,
|
||||
null!,
|
||||
editorGroupService,
|
||||
bulkEditService,
|
||||
new class extends mock<IPanelService>() implements IPanelService {
|
||||
_serviceBrand: undefined;
|
||||
onDidPanelOpen = Event.None;
|
||||
onDidPanelClose = Event.None;
|
||||
getActivePanel() {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
TestEnvironmentService
|
||||
);
|
||||
const documentAndEditor = instaService.createInstance(MainThreadDocumentsAndEditors, rpcProtocol);
|
||||
|
||||
editors = new MainThreadTextEditors(
|
||||
documentAndEditor,
|
||||
SingleProxyRPCProtocol(null),
|
||||
codeEditorService,
|
||||
bulkEditService,
|
||||
workbenchEditorService,
|
||||
editorGroupService,
|
||||
);
|
||||
editors = instaService.createInstance(MainThreadTextEditors, documentAndEditor, SingleProxyRPCProtocol(null));
|
||||
});
|
||||
|
||||
test(`applyWorkspaceEdit returns false if model is changed by user`, () => {
|
||||
|
|
Loading…
Reference in a new issue