conflicts.ts 3.6 KB
Newer Older
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { IFileService } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
8
import { WorkspaceEdit, WorkspaceTextEdit } from 'vs/editor/common/modes';
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
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;
}

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>();
	readonly onDidConflict: Event<this> = this._onDidConflict.event;

	constructor(
		workspaceEdit: WorkspaceEdit,
		@IFileService fileService: IFileService,
		@IModelService modelService: IModelService,
	) {

		const _workspaceEditResources = new ResourceMap<boolean>();

		for (let edit of workspaceEdit.edits) {
52
			if (WorkspaceTextEdit.is(edit)) {
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75

				_workspaceEditResources.set(edit.resource, true);

				if (typeof edit.modelVersionId === 'number') {
					const model = modelService.getModel(edit.resource);
					if (model && model.getVersionId() !== edit.modelVersionId) {
						this._conflicts.set(edit.resource, true);
						this._onDidConflict.fire(this);
					}
				}

			} else if (edit.newUri) {
				_workspaceEditResources.set(edit.newUri, true);

			} else if (edit.oldUri) {
				_workspaceEditResources.set(edit.oldUri, true);
			}
		}

		// listen to file changes
		this._disposables.add(fileService.onFileChanges(e => {
			for (let change of e.changes) {

76 77 78 79 80 81
				if (modelService.getModel(change.resource)) {
					// ignore changes for which a model exists
					// because we have a better check for models
					continue;
				}

82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
				// change
				this._changes.set(change.resource, true);

				// conflict
				if (_workspaceEditResources.has(change.resource)) {
					this._conflicts.set(change.resource, true);
					this._onDidConflict.fire(this);
				}
			}
		}));

		// listen to model changes...?
		const onDidChangeModel = (model: ITextModel) => {
			// change
			this._changes.set(model.uri, true);

			// conflict
			if (_workspaceEditResources.has(model.uri)) {
				this._conflicts.set(model.uri, true);
				this._onDidConflict.fire(this);
			}
		};
		for (let model of modelService.getModels()) {
			this._disposables.add(model.onDidChangeContent(() => onDidChangeModel(model)));
		}
	}

	dispose(): void {
		this._disposables.dispose();
		this._onDidConflict.dispose();
	}

	list(): URI[] {
		const result: URI[] = this._conflicts.keys();
116 117 118 119 120
		// this._changes.forEach((_value, key) => {
		// 	if (!this._conflicts.has(key)) {
		// 		result.push(key);
		// 	}
		// });
121 122 123
		return result;
	}
}