mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 21:09:43 +00:00
extract settings merge as a service
This commit is contained in:
parent
4e0de7ce57
commit
44eb607fc6
|
@ -92,3 +92,13 @@ export const IUserDataSyncService = createDecorator<IUserDataSyncService>('IUser
|
|||
export interface IUserDataSyncService extends ISynchroniser {
|
||||
_serviceBrand: any;
|
||||
}
|
||||
|
||||
export const ISettingsMergeService = createDecorator<ISettingsMergeService>('ISettingsMergeService');
|
||||
|
||||
export interface ISettingsMergeService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<string>;
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { parse, findNodeAtLocation, parseTree } from 'vs/base/common/json';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { setProperty } from 'vs/base/common/jsonEdit';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { ISettingsMergeService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
|
||||
class SettingsMergeService implements ISettingsMergeService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
constructor(
|
||||
@IModelService private readonly modelService: IModelService,
|
||||
@IModeService private readonly modeService: IModeService
|
||||
) { }
|
||||
|
||||
async merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<string> {
|
||||
const local = parse(localContent);
|
||||
const remote = parse(remoteContent);
|
||||
const base = baseContent ? parse(baseContent) : null;
|
||||
const { changes, conflicts } = this.getChanges(local, remote, base);
|
||||
|
||||
if (!changes.length && !conflicts.length) {
|
||||
return localContent;
|
||||
}
|
||||
|
||||
const settingsPreviewModel = this.modelService.createModel(localContent, this.modeService.create('jsonc'));
|
||||
for (const change of changes) {
|
||||
this.editSetting(settingsPreviewModel, change.key, change.value);
|
||||
}
|
||||
for (const key of conflicts) {
|
||||
const tree = parseTree(settingsPreviewModel.getValue());
|
||||
const valueNode = findNodeAtLocation(tree, [key]);
|
||||
const eol = settingsPreviewModel.getEOL();
|
||||
const remoteEdit = setProperty(`{${eol}\t${eol}}`, [key], remote[key], { tabSize: 4, insertSpaces: false, eol: eol })[0];
|
||||
const remoteContent = remoteEdit ? `${remoteEdit.content.substring(remoteEdit.offset + remoteEdit.length + 1)},${eol}` : '';
|
||||
if (valueNode) {
|
||||
// Updated in Local and Remote with different value
|
||||
const keyPosition = settingsPreviewModel.getPositionAt(valueNode.parent!.offset);
|
||||
const valuePosition = settingsPreviewModel.getPositionAt(valueNode.offset + valueNode.length);
|
||||
const editOperations = [
|
||||
EditOperation.insert(new Position(keyPosition.lineNumber - 1, settingsPreviewModel.getLineMaxColumn(keyPosition.lineNumber - 1)), `${eol}<<<<<<< local`),
|
||||
EditOperation.insert(new Position(valuePosition.lineNumber, settingsPreviewModel.getLineMaxColumn(valuePosition.lineNumber)), `${eol}=======${eol}${remoteContent}>>>>>>> remote`)
|
||||
];
|
||||
settingsPreviewModel.pushEditOperations([new Selection(keyPosition.lineNumber, keyPosition.column, keyPosition.lineNumber, keyPosition.column)], editOperations, () => []);
|
||||
} else {
|
||||
// Removed in Local, but updated in Remote
|
||||
const position = new Position(settingsPreviewModel.getLineCount() - 1, settingsPreviewModel.getLineMaxColumn(settingsPreviewModel.getLineCount() - 1));
|
||||
const editOperations = [
|
||||
EditOperation.insert(position, `${eol}<<<<<<< local${eol}=======${eol}${remoteContent}>>>>>>> remote`)
|
||||
];
|
||||
settingsPreviewModel.pushEditOperations([new Selection(position.lineNumber, position.column, position.lineNumber, position.column)], editOperations, () => []);
|
||||
}
|
||||
}
|
||||
return settingsPreviewModel.getValue();
|
||||
}
|
||||
|
||||
private editSetting(model: ITextModel, key: string, value: any | undefined): void {
|
||||
const insertSpaces = false;
|
||||
const tabSize = 4;
|
||||
const eol = model.getEOL();
|
||||
const edit = setProperty(model.getValue(), [key], value, { tabSize, insertSpaces, eol })[0];
|
||||
if (edit) {
|
||||
const startPosition = model.getPositionAt(edit.offset);
|
||||
const endPosition = model.getPositionAt(edit.offset + edit.length);
|
||||
const range = new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column);
|
||||
let currentText = model.getValueInRange(range);
|
||||
if (edit.content !== currentText) {
|
||||
const editOperation = currentText ? EditOperation.replace(range, edit.content) : EditOperation.insert(startPosition, edit.content);
|
||||
model.pushEditOperations([new Selection(startPosition.lineNumber, startPosition.column, startPosition.lineNumber, startPosition.column)], [editOperation], () => []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getChanges(local: { [key: string]: any }, remote: { [key: string]: any }, base: { [key: string]: any } | null): { changes: { key: string; value: any | undefined; }[], conflicts: string[] } {
|
||||
const localToRemote = this.compare(local, remote);
|
||||
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
|
||||
// No changes found between local and remote.
|
||||
return { changes: [], conflicts: [] };
|
||||
}
|
||||
|
||||
const changes: { key: string, value: any | undefined }[] = [];
|
||||
const conflicts: Set<string> = new Set<string>();
|
||||
const baseToLocal = base ? this.compare(base, local) : { added: Object.keys(local).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
const baseToRemote = base ? this.compare(base, remote) : { added: Object.keys(remote).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
|
||||
// Removed settings in Local
|
||||
for (const key of baseToLocal.removed.keys()) {
|
||||
// Got updated in remote
|
||||
if (baseToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Removed settings in Remote
|
||||
for (const key of baseToRemote.removed.keys()) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in local
|
||||
if (baseToLocal.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
} else {
|
||||
changes.push({ key, value: undefined });
|
||||
}
|
||||
}
|
||||
|
||||
// Added settings in Local
|
||||
for (const key of baseToLocal.added.keys()) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got added in remote
|
||||
if (baseToRemote.added.has(key)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Added settings in remote
|
||||
for (const key of baseToRemote.added.keys()) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got added in local
|
||||
if (baseToLocal.added.has(key)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
} else {
|
||||
changes.push({ key, value: remote[key] });
|
||||
}
|
||||
}
|
||||
|
||||
// Updated settings in Local
|
||||
for (const key of baseToLocal.updated.keys()) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in remote
|
||||
if (baseToRemote.updated.has(key)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Updated settings in Remote
|
||||
for (const key of baseToRemote.updated.keys()) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in local
|
||||
if (baseToLocal.updated.has(key)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
} else {
|
||||
changes.push({ key, value: remote[key] });
|
||||
}
|
||||
}
|
||||
|
||||
return { changes, conflicts: values(conflicts) };
|
||||
}
|
||||
|
||||
private compare(from: { [key: string]: any }, to: { [key: string]: any }): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
const fromKeys = Object.keys(from);
|
||||
const toKeys = Object.keys(to);
|
||||
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const updated: Set<string> = new Set<string>();
|
||||
|
||||
for (const key of fromKeys) {
|
||||
if (removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const value1 = from[key];
|
||||
const value2 = to[key];
|
||||
if (!objects.equals(value1, value2)) {
|
||||
updated.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return { added, removed, updated };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(ISettingsMergeService, SettingsMergeService);
|
|
@ -3,29 +3,20 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, SETTINGS_PREVIEW_RESOURCE } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, SETTINGS_PREVIEW_RESOURCE, ISettingsMergeService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncStoreService } from 'vs/workbench/services/userData/common/userData';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { parse, findNodeAtLocation, parseTree, ParseError } from 'vs/base/common/json';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { parse, ParseError } from 'vs/base/common/json';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { setProperty } from 'vs/base/common/jsonEdit';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs/base/common/async';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
||||
interface ISyncPreviewResult {
|
||||
readonly fileContent: IFileContent | null;
|
||||
|
@ -53,18 +44,17 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
|||
|
||||
constructor(
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
|
||||
@IModelService private readonly modelService: IModelService,
|
||||
@IModeService private readonly modeService: IModeService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@ISettingsMergeService private readonly settingsMergeService: ISettingsMergeService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IHistoryService private readonly historyService: IHistoryService,
|
||||
) {
|
||||
super();
|
||||
this.throttledDelayer = this._register(new ThrottledDelayer<void>(500));
|
||||
this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.workbenchEnvironmentService.settingsResource))(() => this.throttledDelayer.trigger(() => this.onDidChangeSettings())));
|
||||
this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.settingsResource))(() => this.throttledDelayer.trigger(() => this.onDidChangeSettings())));
|
||||
}
|
||||
|
||||
private async onDidChangeSettings(): Promise<void> {
|
||||
|
@ -197,34 +187,24 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
|||
const remoteUserData = await this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY);
|
||||
// Get file content last to get the latest
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
const { settingsPreview, hasLocalChanged, hasRemoteChanged, hasConflicts } = this.computeChanges(fileContent, remoteUserData);
|
||||
if (hasLocalChanged || hasRemoteChanged) {
|
||||
await this.fileService.writeFile(SETTINGS_PREVIEW_RESOURCE, VSBuffer.fromString(settingsPreview));
|
||||
}
|
||||
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
}
|
||||
|
||||
private computeChanges(fileContent: IFileContent | null, remoteUserData: IUserData | null): { settingsPreview: string, hasLocalChanged: boolean, hasRemoteChanged: boolean, hasConflicts: boolean } {
|
||||
|
||||
let hasLocalChanged: boolean = false;
|
||||
let hasRemoteChanged: boolean = false;
|
||||
let hasConflicts: boolean = false;
|
||||
let settingsPreview: string = '';
|
||||
|
||||
// First time sync to remote
|
||||
if (fileContent && !remoteUserData) {
|
||||
this.logService.trace('Settings Sync: Remote contents does not exist. So sync with settings file.');
|
||||
hasRemoteChanged = true;
|
||||
settingsPreview = fileContent.value.toString();
|
||||
return { settingsPreview, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
await this.fileService.writeFile(SETTINGS_PREVIEW_RESOURCE, VSBuffer.fromString(fileContent.value.toString()));
|
||||
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
}
|
||||
|
||||
// Settings file does not exist, so sync with remote contents.
|
||||
if (remoteUserData && !fileContent) {
|
||||
this.logService.trace('Settings Sync: Settings file does not exist. So sync with remote contents');
|
||||
hasLocalChanged = true;
|
||||
settingsPreview = remoteUserData.content;
|
||||
return { settingsPreview, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
await this.fileService.writeFile(SETTINGS_PREVIEW_RESOURCE, VSBuffer.fromString(remoteUserData.content));
|
||||
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
}
|
||||
|
||||
if (fileContent && remoteUserData) {
|
||||
|
@ -236,182 +216,20 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
|||
|| lastSyncData.content !== remoteContent // Remote has moved forwarded
|
||||
) {
|
||||
this.logService.trace('Settings Sync: Merging remote contents with settings file.');
|
||||
const { settingsPreview, hasChanges, hasConflicts } = this.mergeContents(localContent, remoteContent, lastSyncData ? lastSyncData.content : null);
|
||||
if (hasChanges) {
|
||||
const mergeContent = await this.settingsMergeService.merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null);
|
||||
hasLocalChanged = mergeContent !== localContent;
|
||||
hasRemoteChanged = mergeContent !== remoteContent;
|
||||
if (hasLocalChanged || hasRemoteChanged) {
|
||||
// Sync only if there are changes
|
||||
hasLocalChanged = settingsPreview !== localContent; // Local has changed
|
||||
hasRemoteChanged = settingsPreview !== remoteContent; // Remote has changed
|
||||
return { settingsPreview, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
hasConflicts = this.hasErrors(mergeContent);
|
||||
await this.fileService.writeFile(SETTINGS_PREVIEW_RESOURCE, VSBuffer.fromString(mergeContent));
|
||||
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.logService.trace('Settings Sync: No changes.');
|
||||
return { settingsPreview, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
|
||||
}
|
||||
|
||||
private mergeContents(localContent: string, remoteContent: string, lastSyncedContent: string | null): { settingsPreview: string, hasChanges: boolean; hasConflicts: boolean } {
|
||||
const local = parse(localContent);
|
||||
const remote = parse(remoteContent);
|
||||
const localToRemote = this.compare(local, remote);
|
||||
|
||||
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
|
||||
// No changes found between local and remote.
|
||||
return { settingsPreview: localContent, hasChanges: false, hasConflicts: false };
|
||||
}
|
||||
|
||||
const settingsPreviewModel = this.modelService.createModel(localContent, this.modeService.create('jsonc'));
|
||||
const base = lastSyncedContent ? parse(lastSyncedContent) : null;
|
||||
const baseToLocal = base ? this.compare(base, local) : { added: Object.keys(local).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
const baseToRemote = base ? this.compare(base, remote) : { added: Object.keys(remote).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
|
||||
const conflicts: Set<string> = new Set<string>();
|
||||
|
||||
// Removed settings in Local
|
||||
for (const key of baseToLocal.removed.keys()) {
|
||||
// Got updated in remote
|
||||
if (baseToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Removed settings in Remote
|
||||
for (const key of baseToRemote.removed.keys()) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in local
|
||||
if (baseToLocal.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
} else {
|
||||
this.editSetting(settingsPreviewModel, key, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
// Added settings in Local
|
||||
for (const key of baseToLocal.added.keys()) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got added in remote
|
||||
if (baseToRemote.added.has(key)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Added settings in remote
|
||||
for (const key of baseToRemote.added.keys()) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got added in local
|
||||
if (baseToLocal.added.has(key)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
} else {
|
||||
this.editSetting(settingsPreviewModel, key, remote[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// Updated settings in Local
|
||||
for (const key of baseToLocal.updated.keys()) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in remote
|
||||
if (baseToRemote.updated.has(key)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Updated settings in Remote
|
||||
for (const key of baseToRemote.updated.keys()) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in local
|
||||
if (baseToLocal.updated.has(key)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
} else {
|
||||
this.editSetting(settingsPreviewModel, key, remote[key]);
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of conflicts.keys()) {
|
||||
const tree = parseTree(settingsPreviewModel.getValue());
|
||||
const valueNode = findNodeAtLocation(tree, [key]);
|
||||
const remoteEdit = setProperty(`{${settingsPreviewModel.getEOL()}\t${settingsPreviewModel.getEOL()}}`, [key], remote[key], { tabSize: 4, insertSpaces: false, eol: settingsPreviewModel.getEOL() })[0];
|
||||
const remoteContent = remoteEdit ? `${remoteEdit.content.substring(remoteEdit.offset + remoteEdit.length + 1)},${settingsPreviewModel.getEOL()}` : '';
|
||||
if (valueNode) {
|
||||
// Updated in Local and Remote with different value
|
||||
const keyPosition = settingsPreviewModel.getPositionAt(valueNode.parent!.offset);
|
||||
const valuePosition = settingsPreviewModel.getPositionAt(valueNode.offset + valueNode.length);
|
||||
const editOperations = [
|
||||
EditOperation.insert(new Position(keyPosition.lineNumber - 1, settingsPreviewModel.getLineMaxColumn(keyPosition.lineNumber - 1)), `${settingsPreviewModel.getEOL()}<<<<<<< local`),
|
||||
EditOperation.insert(new Position(valuePosition.lineNumber, settingsPreviewModel.getLineMaxColumn(valuePosition.lineNumber)), `${settingsPreviewModel.getEOL()}=======${settingsPreviewModel.getEOL()}${remoteContent}>>>>>>> remote`)
|
||||
];
|
||||
settingsPreviewModel.pushEditOperations([new Selection(keyPosition.lineNumber, keyPosition.column, keyPosition.lineNumber, keyPosition.column)], editOperations, () => []);
|
||||
} else {
|
||||
// Removed in Local, but updated in Remote
|
||||
const position = new Position(settingsPreviewModel.getLineCount() - 1, settingsPreviewModel.getLineMaxColumn(settingsPreviewModel.getLineCount() - 1));
|
||||
const editOperations = [
|
||||
EditOperation.insert(position, `${settingsPreviewModel.getEOL()}<<<<<<< local${settingsPreviewModel.getEOL()}=======${settingsPreviewModel.getEOL()}${remoteContent}>>>>>>> remote`)
|
||||
];
|
||||
settingsPreviewModel.pushEditOperations([new Selection(position.lineNumber, position.column, position.lineNumber, position.column)], editOperations, () => []);
|
||||
}
|
||||
}
|
||||
|
||||
return { settingsPreview: settingsPreviewModel.getValue(), hasChanges: true, hasConflicts: conflicts.size > 0 };
|
||||
}
|
||||
|
||||
private compare(from: { [key: string]: any }, to: { [key: string]: any }): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
const fromKeys = Object.keys(from);
|
||||
const toKeys = Object.keys(to);
|
||||
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const updated: Set<string> = new Set<string>();
|
||||
|
||||
for (const key of fromKeys) {
|
||||
if (removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const value1 = from[key];
|
||||
const value2 = to[key];
|
||||
if (!objects.equals(value1, value2)) {
|
||||
updated.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return { added, removed, updated };
|
||||
}
|
||||
|
||||
private editSetting(model: ITextModel, key: string, value: any | undefined): void {
|
||||
const insertSpaces = false;
|
||||
const tabSize = 4;
|
||||
const eol = model.getEOL();
|
||||
const edit = setProperty(model.getValue(), [key], value, { tabSize, insertSpaces, eol })[0];
|
||||
if (edit) {
|
||||
const startPosition = model.getPositionAt(edit.offset);
|
||||
const endPosition = model.getPositionAt(edit.offset + edit.length);
|
||||
const range = new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column);
|
||||
let currentText = model.getValueInRange(range);
|
||||
if (edit.content !== currentText) {
|
||||
const editOperation = currentText ? EditOperation.replace(range, edit.content) : EditOperation.insert(startPosition, edit.content);
|
||||
model.pushEditOperations([new Selection(startPosition.lineNumber, startPosition.column, startPosition.lineNumber, startPosition.column)], [editOperation], () => []);
|
||||
}
|
||||
}
|
||||
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
}
|
||||
|
||||
private getLastSyncUserData(): IUserData | null {
|
||||
|
@ -424,7 +242,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
|||
|
||||
private async getLocalFileContent(): Promise<IFileContent | null> {
|
||||
try {
|
||||
return await this.fileService.readFile(this.workbenchEnvironmentService.settingsResource);
|
||||
return await this.fileService.readFile(this.environmentService.settingsResource);
|
||||
} catch (error) {
|
||||
if (error instanceof FileSystemProviderError && error.code !== FileSystemProviderErrorCode.FileNotFound) {
|
||||
return null;
|
||||
|
@ -440,10 +258,10 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
|||
private async writeToLocal(newContent: string, oldContent: IFileContent | null): Promise<void> {
|
||||
if (oldContent) {
|
||||
// file exists already
|
||||
await this.fileService.writeFile(this.workbenchEnvironmentService.settingsResource, VSBuffer.fromString(newContent), oldContent);
|
||||
await this.fileService.writeFile(this.environmentService.settingsResource, VSBuffer.fromString(newContent), oldContent);
|
||||
} else {
|
||||
// file does not exist
|
||||
await this.fileService.createFile(this.workbenchEnvironmentService.settingsResource, VSBuffer.fromString(newContent), { overwrite: false });
|
||||
await this.fileService.createFile(this.environmentService.settingsResource, VSBuffer.fromString(newContent), { overwrite: false });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -81,6 +81,7 @@ import 'vs/workbench/services/notification/common/notificationService';
|
|||
import 'vs/workbench/services/extensions/common/staticExtensions';
|
||||
import 'vs/workbench/services/userData/common/userDataSyncStoreService';
|
||||
import 'vs/workbench/services/userData/common/userDataSyncService';
|
||||
import 'vs/workbench/services/userData/common/settingsMergeService';
|
||||
import 'vs/workbench/services/workspace/browser/workspaceEditingService';
|
||||
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
|
|
Loading…
Reference in a new issue