diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 4ec972830fc15..757d62ebf9c3a 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -91,14 +91,13 @@ export const SETTINGS_PREVIEW_RESOURCE = URI.file('Settings-Preview').with({ sch export interface ISynchroniser { + readonly conflicts: URI | null; readonly status: SyncStatus; readonly onDidChangeStatus: Event; - readonly onDidChangeLocal: Event; sync(): Promise; continueSync(): Promise; - handleConflicts(): boolean; } export const IUserDataSyncService = createDecorator('IUserDataSyncService'); diff --git a/src/vs/workbench/contrib/userData/browser/userData.contribution.ts b/src/vs/workbench/contrib/userData/browser/userData.contribution.ts index 8d721195a3378..02c6bdc3797b8 100644 --- a/src/vs/workbench/contrib/userData/browser/userData.contribution.ts +++ b/src/vs/workbench/contrib/userData/browser/userData.contribution.ts @@ -24,6 +24,7 @@ import { ResourceContextKey } from 'vs/workbench/common/resources'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { Event } from 'vs/base/common/event'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; const CONTEXT_SYNC_STATE = new RawContextKey('syncStatus', SyncStatus.Uninitialized); @@ -93,6 +94,7 @@ class SyncActionsContribution extends Disposable implements IWorkbenchContributi @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, @ITextFileService private readonly textFileService: ITextFileService, + @IHistoryService private readonly historyService: IHistoryService, ) { super(); this.syncEnablementContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService); @@ -126,7 +128,7 @@ class SyncActionsContribution extends Disposable implements IWorkbenchContributi [ { label: localize('resolve', "Resolve Conflicts"), - run: () => this.userDataSyncService.handleConflicts() + run: () => this.handleConflicts() } ]); this.conflictsWarningDisposable.value = toDisposable(() => handle.close()); @@ -160,6 +162,22 @@ class SyncActionsContribution extends Disposable implements IWorkbenchContributi } } + private async handleConflicts(): Promise { + const resource = this.userDataSyncService.conflicts; + if (resource) { + const resourceInput = { + resource, + options: { + preserveFocus: false, + pinned: false, + revealIfVisible: true, + }, + mode: 'jsonc' + }; + this.editorService.openEditor(resourceInput).then(() => this.historyService.remove(resourceInput)); + } + } + private registerActions(): void { const startSyncMenuItem: IMenuItem = { @@ -194,7 +212,7 @@ class SyncActionsContribution extends Disposable implements IWorkbenchContributi }, when: CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), }; - CommandsRegistry.registerCommand(resolveConflictsMenuItem.command.id, serviceAccessor => serviceAccessor.get(IUserDataSyncService).handleConflicts()); + CommandsRegistry.registerCommand(resolveConflictsMenuItem.command.id, () => this.handleConflicts()); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, resolveConflictsMenuItem); MenuRegistry.appendMenuItem(MenuId.CommandPalette, resolveConflictsMenuItem); diff --git a/src/vs/workbench/services/userData/common/settingsSync.ts b/src/vs/workbench/services/userData/common/settingsSync.ts index 3b98368d8a01e..fee382b9750d0 100644 --- a/src/vs/workbench/services/userData/common/settingsSync.ts +++ b/src/vs/workbench/services/userData/common/settingsSync.ts @@ -6,16 +6,15 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, SETTINGS_PREVIEW_RESOURCE, ISettingsMergeService, IUserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, ISettingsMergeService, IUserDataSyncStoreService, SETTINGS_PREVIEW_RESOURCE } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { localize } from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; import { ILogService } from 'vs/platform/log/common/log'; -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'; +import { URI } from 'vs/base/common/uri'; interface ISyncPreviewResult { readonly fileContent: IFileContent | null; @@ -41,15 +40,15 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { private _onDidChangeLocal: Emitter = this._register(new Emitter()); readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; + readonly conflicts: URI = SETTINGS_PREVIEW_RESOURCE; + constructor( @IFileService private readonly fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IStorageService private readonly storageService: IStorageService, @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, - @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(500)); @@ -111,24 +110,6 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { } } - handleConflicts(): boolean { - if (this.status !== SyncStatus.HasConflicts) { - return false; - } - const resourceInput = { - resource: SETTINGS_PREVIEW_RESOURCE, - label: localize('Settings Conflicts', "Local ↔ Remote (Settings Conflicts)"), - options: { - preserveFocus: false, - pinned: false, - revealIfVisible: true, - }, - mode: 'jsonc' - }; - this.editorService.openEditor(resourceInput).then(() => this.historyService.remove(resourceInput)); - return true; - } - async continueSync(): Promise { if (this.status !== SyncStatus.HasConflicts) { return false; @@ -142,8 +123,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { return; } - if (await this.fileService.exists(SETTINGS_PREVIEW_RESOURCE)) { - const settingsPreivew = await this.fileService.readFile(SETTINGS_PREVIEW_RESOURCE); + if (await this.fileService.exists(this.conflicts)) { + const settingsPreivew = await this.fileService.readFile(this.conflicts); const content = settingsPreivew.value.toString(); if (this.hasErrors(content)) { return Promise.reject(localize('errorInvalidSettings', "Unable to sync settings. Please resolve conflicts without any errors/warnings and try again.")); @@ -162,7 +143,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { } // Delete the preview - await this.fileService.del(SETTINGS_PREVIEW_RESOURCE); + await this.fileService.del(this.conflicts); } this.syncPreviewResultPromise = null; @@ -194,7 +175,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { if (fileContent && !remoteUserData) { this.logService.trace('Settings Sync: Remote contents does not exist. So sync with settings file.'); hasRemoteChanged = true; - await this.fileService.writeFile(SETTINGS_PREVIEW_RESOURCE, VSBuffer.fromString(fileContent.value.toString())); + await this.fileService.writeFile(this.conflicts, VSBuffer.fromString(fileContent.value.toString())); return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; } @@ -202,7 +183,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { if (remoteUserData && !fileContent) { this.logService.trace('Settings Sync: Settings file does not exist. So sync with remote contents'); hasLocalChanged = true; - await this.fileService.writeFile(SETTINGS_PREVIEW_RESOURCE, VSBuffer.fromString(remoteUserData.content)); + await this.fileService.writeFile(this.conflicts, VSBuffer.fromString(remoteUserData.content)); return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; } @@ -221,7 +202,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { if (hasLocalChanged || hasRemoteChanged) { // Sync only if there are changes hasConflicts = this.hasErrors(mergeContent); - await this.fileService.writeFile(SETTINGS_PREVIEW_RESOURCE, VSBuffer.fromString(mergeContent)); + await this.fileService.writeFile(this.conflicts, VSBuffer.fromString(mergeContent)); return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; } } diff --git a/src/vs/workbench/services/userData/common/userDataSyncService.ts b/src/vs/workbench/services/userData/common/userDataSyncService.ts index 18b1a975a1086..9a048f2678dce 100644 --- a/src/vs/workbench/services/userData/common/userDataSyncService.ts +++ b/src/vs/workbench/services/userData/common/userDataSyncService.ts @@ -11,6 +11,7 @@ import { SettingsSynchroniser } from 'vs/workbench/services/userData/common/sett import { Emitter, Event } from 'vs/base/common/event'; import { IFileService } from 'vs/platform/files/common/files'; import { InMemoryFileSystemProvider } from 'vs/workbench/services/userData/common/inMemoryUserDataProvider'; +import { URI } from 'vs/base/common/uri'; export class UserDataSyncService extends Disposable implements IUserDataSyncService { @@ -40,6 +41,11 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal)); } + get conflicts(): URI | null { + const synchroniser = this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts)[0]; + return synchroniser ? synchroniser.conflicts : null; + } + async sync(): Promise { if (!this.userDataSyncStoreService.enabled) { throw new Error('Not enabled'); @@ -64,18 +70,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return false; } - handleConflicts(): boolean { - if (!this.userDataSyncStoreService.enabled) { - throw new Error('Not enabled'); - } - for (const synchroniser of this.synchronisers) { - if (synchroniser.handleConflicts()) { - return true; - } - } - return false; - } - private updateStatus(): void { this.setStatus(this.computeStatus()); }