diff --git a/extensions/git/package.json b/extensions/git/package.json index 111717f86c9..00464a5d1e6 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1517,7 +1517,7 @@ "merge/toolbar": [ { "command": "git.acceptMerge", - "when": "isMergeEditor" + "when": "isMergeEditor && baseResourceScheme =~ /^git$|^file$/" } ], "scm/change/title": [ diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index cb7a553f893..ae27e08f471 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -55,6 +55,9 @@ export function isSyncData(thing: any): thing is ISyncData { export interface IResourcePreview { + readonly baseResource: URI; + readonly baseContent: string | null; + readonly remoteResource: URI; readonly remoteContent: string | null; readonly remoteChange: Change; @@ -534,6 +537,9 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa if (this.extUri.isEqual(resourcePreview.localResource, uri)) { return resourcePreview.localContent; } + if (this.extUri.isEqual(resourcePreview.baseResource, uri)) { + return resourcePreview.baseContent; + } } } return null; diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index d0caff355c1..8a82b9f4c52 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -80,6 +80,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse protected readonly version: number = 5; private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'extensions.json'); + private readonly baseResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }); private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }); private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); @@ -135,6 +136,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return [{ skippedExtensions, + baseResource: this.baseResource, + baseContent: lastSyncExtensions ? this.stringify(lastSyncExtensions, false) : null, localResource: this.localResource, localContent: this.stringify(localExtensions, false), localExtensions, @@ -285,7 +288,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return this.stringify(localExtensions, true); } - if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) { + if (this.extUri.isEqual(this.remoteResource, uri) + || this.extUri.isEqual(this.baseResource, uri) + || this.extUri.isEqual(this.localResource, uri) + || this.extUri.isEqual(this.acceptedResource, uri) + ) { const content = await this.resolvePreviewContent(uri); return content ? this.stringify(JSON.parse(content), true) : content; } diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 78a2061c12a..e4060a6b472 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -68,6 +68,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs private static readonly GLOBAL_STATE_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'globalState', path: `/globalState.json` }); protected readonly version: number = GLOBAL_STATE_DATA_VERSION; private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'globalState.json'); + private readonly baseResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }); private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }); private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); @@ -124,6 +125,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs }; return [{ + baseResource: this.baseResource, + baseContent: lastSyncGlobalState ? stringify(lastSyncGlobalState, false) : null, localResource: this.localResource, localContent: stringify(localGlobalState, false), localUserData: localGlobalState, @@ -247,7 +250,11 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return stringify(localGlobalState, true); } - if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) { + if (this.extUri.isEqual(this.remoteResource, uri) + || this.extUri.isEqual(this.baseResource, uri) + || this.extUri.isEqual(this.localResource, uri) + || this.extUri.isEqual(this.acceptedResource, uri) + ) { const content = await this.resolvePreviewContent(uri); return content ? stringify(JSON.parse(content), true) : content; } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 79410e00cca..c2b691a2d0e 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -63,6 +63,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem /* Version 2: Change settings from `sync.${setting}` to `settingsSync.{setting}` */ protected readonly version: number = 2; private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'keybindings.json'); + private readonly baseResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }); private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }); private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); @@ -139,6 +140,10 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem return [{ fileContent, + + baseResource: this.baseResource, + baseContent: lastSyncContent, + localResource: this.localResource, localContent: fileContent ? fileContent.value.toString() : null, localChange: previewResult.localChange, @@ -279,7 +284,11 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem } override async resolveContent(uri: URI): Promise { - if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) { + if (this.extUri.isEqual(this.remoteResource, uri) + || this.extUri.isEqual(this.baseResource, uri) + || this.extUri.isEqual(this.localResource, uri) + || this.extUri.isEqual(this.acceptedResource, uri) + ) { return this.resolvePreviewContent(uri); } let content = await super.resolveContent(uri); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 9d24d40eb3b..30b77c9090b 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -45,6 +45,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement /* Version 2: Change settings from `sync.${setting}` to `settingsSync.{setting}` */ protected readonly version: number = 2; readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'settings.json'); + readonly baseResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }); readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }); readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); @@ -121,6 +122,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement return [{ fileContent, + + baseResource: this.baseResource, + baseContent: lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, + localResource: this.localResource, localContent: fileContent ? fileContent.value.toString() : null, localChange: previewResult.localChange, @@ -274,7 +279,11 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement } override async resolveContent(uri: URI): Promise { - if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) { + if (this.extUri.isEqual(this.remoteResource, uri) + || this.extUri.isEqual(this.localResource, uri) + || this.extUri.isEqual(this.acceptedResource, uri) + || this.extUri.isEqual(this.baseResource, uri) + ) { return this.resolvePreviewContent(uri); } let content = await super.resolveContent(uri); diff --git a/src/vs/platform/userDataSync/common/snippetsSync.ts b/src/vs/platform/userDataSync/common/snippetsSync.ts index fd38b76b43d..61ac01d7998 100644 --- a/src/vs/platform/userDataSync/common/snippetsSync.ts +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -67,7 +67,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD } const mergeResult = merge(localSnippets, remoteSnippets, lastSyncSnippets); - return this.getResourcePreviews(mergeResult, local, remoteSnippets || {}); + return this.getResourcePreviews(mergeResult, local, remoteSnippets || {}, lastSyncSnippets || {}); } protected async hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise { @@ -165,7 +165,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD } - private getResourcePreviews(snippetsMergeResult: ISnippetsMergeResult, localFileContent: IStringDictionary, remoteSnippets: IStringDictionary): ISnippetsResourcePreview[] { + private getResourcePreviews(snippetsMergeResult: ISnippetsMergeResult, localFileContent: IStringDictionary, remoteSnippets: IStringDictionary, baseSnippets: IStringDictionary): ISnippetsResourcePreview[] { const resourcePreviews: Map = new Map(); /* Snippets added remotely -> add locally */ @@ -177,6 +177,8 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD remoteChange: Change.None, }; resourcePreviews.set(key, { + baseResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }), + baseContent: null, fileContent: null, localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), localContent: null, @@ -199,6 +201,8 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD remoteChange: Change.None, }; resourcePreviews.set(key, { + baseResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }), + baseContent: baseSnippets[key] || null, localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key], localContent: localFileContent[key].value.toString(), @@ -221,6 +225,8 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD remoteChange: Change.None, }; resourcePreviews.set(key, { + baseResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }), + baseContent: baseSnippets[key] || null, localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key], localContent: localFileContent[key].value.toString(), @@ -243,6 +249,8 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD remoteChange: Change.Added, }; resourcePreviews.set(key, { + baseResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }), + baseContent: null, localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key], localContent: localFileContent[key].value.toString(), @@ -265,6 +273,8 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD remoteChange: Change.Modified, }; resourcePreviews.set(key, { + baseResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }), + baseContent: baseSnippets[key] || null, localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key], localContent: localFileContent[key].value.toString(), @@ -287,6 +297,8 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD remoteChange: Change.Deleted, }; resourcePreviews.set(key, { + baseResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }), + baseContent: baseSnippets[key] || null, localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: null, localContent: null, @@ -309,6 +321,8 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD remoteChange: remoteSnippets[key] ? Change.Modified : Change.Added }; resourcePreviews.set(key, { + baseResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }), + baseContent: baseSnippets[key] || null, localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key] || null, localContent: localFileContent[key] ? localFileContent[key].value.toString() : null, @@ -332,6 +346,8 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD remoteChange: Change.None }; resourcePreviews.set(key, { + baseResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }), + baseContent: baseSnippets[key] || null, localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key] || null, localContent: localFileContent[key] ? localFileContent[key].value.toString() : null, @@ -371,6 +387,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD override async resolveContent(uri: URI): Promise { if (this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' })) || this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' })) + || this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' })) || this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }))) { return this.resolvePreviewContent(uri); } diff --git a/src/vs/platform/userDataSync/common/tasksSync.ts b/src/vs/platform/userDataSync/common/tasksSync.ts index d350be5e690..a174698dc0a 100644 --- a/src/vs/platform/userDataSync/common/tasksSync.ts +++ b/src/vs/platform/userDataSync/common/tasksSync.ts @@ -38,6 +38,7 @@ export class TasksSynchroniser extends AbstractFileSynchroniser implements IUser protected readonly version: number = 1; private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'tasks.json'); + private readonly baseResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }); private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }); private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); @@ -103,6 +104,10 @@ export class TasksSynchroniser extends AbstractFileSynchroniser implements IUser return [{ fileContent, + + baseResource: this.baseResource, + baseContent: lastSyncContent, + localResource: this.localResource, localContent: fileContent ? fileContent.value.toString() : null, localChange: previewResult.localChange, @@ -221,7 +226,11 @@ export class TasksSynchroniser extends AbstractFileSynchroniser implements IUser } override async resolveContent(uri: URI): Promise { - if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) { + if (this.extUri.isEqual(this.remoteResource, uri) + || this.extUri.isEqual(this.baseResource, uri) + || this.extUri.isEqual(this.localResource, uri) + || this.extUri.isEqual(this.acceptedResource, uri) + ) { return this.resolvePreviewContent(uri); } let content = await super.resolveContent(uri); diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 5c1516106bb..b65c891cd04 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -359,6 +359,7 @@ export const enum MergeState { } export interface IResourcePreview { + readonly baseResource: URI; readonly remoteResource: URI; readonly localResource: URI; readonly previewResource: URI; diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 1fd3c28791a..4845ad05da6 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -909,6 +909,7 @@ class Synchronizers extends Disposable { function toStrictResourcePreview(resourcePreview: IResourcePreview): IResourcePreview { return { + baseResource: resourcePreview.baseResource, localResource: resourcePreview.localResource, previewResource: resourcePreview.previewResource, remoteResource: resourcePreview.remoteResource, diff --git a/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts index 4a190dfd8e6..c945029e6a3 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts @@ -259,6 +259,7 @@ export class UserDataSyncChannelClient extends Disposable implements IUserDataSy conflicts.map(r => ({ ...r, + baseResource: URI.revive(r.baseResource), localResource: URI.revive(r.localResource), remoteResource: URI.revive(r.remoteResource), previewResource: URI.revive(r.previewResource), @@ -369,6 +370,7 @@ class ManualSyncTaskChannelClient extends Disposable implements IManualSyncTask isLastSyncFromCurrentMachine: preview.isLastSyncFromCurrentMachine, resourcePreviews: preview.resourcePreviews.map(r => ({ ...r, + baseResource: URI.revive(r.baseResource), localResource: URI.revive(r.localResource), remoteResource: URI.revive(r.remoteResource), previewResource: URI.revive(r.previewResource), diff --git a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts index 71724a85a0f..7a5641be626 100644 --- a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts +++ b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -64,6 +64,8 @@ class TestSynchroniser extends AbstractSynchroniser { } catch (error) { } return [{ + baseResource: this.localResource.with(({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' })), + baseContent: null, localResource: this.localResource, localContent: fileContent ? fileContent.value.toString() : null, remoteResource: this.localResource.with(({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' })), diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index fc1a79f2ad2..3b37bc263ca 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -53,6 +53,7 @@ import { EditorGutter, IGutterItemInfo, IGutterItemView } from './editorGutter'; export const ctxIsMergeEditor = new RawContextKey('isMergeEditor', false); export const ctxUsesColumnLayout = new RawContextKey('mergeEditorUsesColumnLayout', false); +export const ctxBaseResourceScheme = new RawContextKey('baseResourceScheme', ''); export class MergeEditor extends EditorPane { @@ -68,6 +69,7 @@ export class MergeEditor extends EditorPane { private readonly _ctxIsMergeEditor: IContextKey; private readonly _ctxUsesColumnLayout: IContextKey; + private readonly _ctxBaseResourceScheme: IContextKey; private _model: MergeEditorModel | undefined; public get model(): MergeEditorModel | undefined { return this._model; } @@ -91,6 +93,7 @@ export class MergeEditor extends EditorPane { this._ctxIsMergeEditor = ctxIsMergeEditor.bindTo(_contextKeyService); this._ctxUsesColumnLayout = ctxUsesColumnLayout.bindTo(_contextKeyService); + this._ctxBaseResourceScheme = ctxBaseResourceScheme.bindTo(_contextKeyService); const reentrancyBarrier = new ReentrancyBarrier(); @@ -231,6 +234,7 @@ export class MergeEditor extends EditorPane { this.input1View.setModel(model, model.input1, localize('yours', 'Yours'), model.input1Detail, model.input1Description); this.input2View.setModel(model, model.input2, localize('theirs', 'Theirs',), model.input2Detail, model.input2Description); this.inputResultView.setModel(model, model.result, localize('result', 'Result',), this._labelService.getUriLabel(model.result.uri, { relative: true }), undefined); + this._ctxBaseResourceScheme.set(model.base.uri.scheme); // TODO: Update editor options! diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 36c915f8051..a1eb6bf30e4 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -6,12 +6,10 @@ import { Action } from 'vs/base/common/actions'; import { getErrorMessage, isCancellationError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, dispose, MutableDisposable, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, MutableDisposable, toDisposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { isEqual, basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import type { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import type { IEditorContribution } from 'vs/editor/common/editorCommon'; import type { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { ILanguageService } from 'vs/editor/common/languages/language'; @@ -20,7 +18,7 @@ import { localize } from 'vs/nls'; import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyDefinedExpr, ContextKeyEqualsExpr, ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -31,7 +29,6 @@ import { SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, getSyncResourceFromLocalPreview, IResourcePreview, IUserDataSyncStoreManagementService, UserDataSyncStoreType, IUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync'; -import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; @@ -55,11 +52,16 @@ import { UserDataSyncDataViews } from 'vs/workbench/contrib/userDataSync/browser import { IUserDataSyncWorkbenchService, getSyncAreaLabel, AccountStatus, CONTEXT_SYNC_STATE, CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE, CONFIGURE_SYNC_COMMAND_ID, SHOW_SYNC_LOG_COMMAND_ID, SYNC_VIEW_CONTAINER_ID, SYNC_TITLE, SYNC_VIEW_ICON } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { Codicon } from 'vs/base/common/codicons'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { EditorResolution } from 'vs/platform/editor/common/editor'; import { CATEGORIES } from 'vs/workbench/common/actions'; import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorResolution } from 'vs/platform/editor/common/editor'; const CONTEXT_CONFLICTS_SOURCES = new RawContextKey('conflictsSources', ''); @@ -726,21 +728,35 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private async handleConflicts([syncResource, conflicts]: [SyncResource, IResourcePreview[]]): Promise { + const useMergeEditor = this.configurationService.getValue('settingsSync.mergeEditor') ?? true; for (const conflict of conflicts) { - const leftResourceName = localize({ key: 'leftResourceName', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(conflict.remoteResource)); - const rightResourceName = localize('merges', "{0} (Merges)", basename(conflict.previewResource)); - await this.editorService.openEditor({ - original: { resource: conflict.remoteResource }, - modified: { resource: conflict.previewResource }, - label: localize('sideBySideLabels', "{0} ↔ {1}", leftResourceName, rightResourceName), - description: localize('sideBySideDescription', "Settings Sync"), - options: { - preserveFocus: false, - pinned: true, - revealIfVisible: true, - override: EditorResolution.DISABLED - }, - }); + if (useMergeEditor) { + const remoteResourceName = localize({ key: 'remoteResourceName', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(conflict.remoteResource)); + const localResourceName = localize('localResourceName', "{0} (Local)", basename(conflict.remoteResource)); + const input = this.instantiationService.createInstance( + MergeEditorInput, + conflict.baseResource, + { description: localResourceName, detail: undefined, uri: conflict.localResource }, + { description: remoteResourceName, detail: undefined, uri: conflict.remoteResource }, + conflict.previewResource, + ); + await this.editorService.openEditor(input); + } else { + const leftResourceName = localize({ key: 'leftResourceName', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(conflict.remoteResource)); + const rightResourceName = localize('merges', "{0} (Merges)", basename(conflict.previewResource)); + await this.editorService.openEditor({ + original: { resource: conflict.remoteResource }, + modified: { resource: conflict.previewResource }, + label: localize('sideBySideLabels', "{0} ↔ {1}", leftResourceName, rightResourceName), + description: localize('sideBySideDescription', "Settings Sync"), + options: { + preserveFocus: false, + pinned: true, + revealIfVisible: true, + override: EditorResolution.DISABLED + }, + }); + } } } @@ -811,6 +827,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.registerHelpAction(); this.registerShowLogAction(); this.registerResetSyncDataAction(); + this.registerAcceptMergesAction(); } private registerTurnOnSyncAction(): void { @@ -1285,6 +1302,37 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); } + private registerAcceptMergesAction(): void { + const that = this; + this._register(registerAction2(class AcceptMergesAction extends Action2 { + constructor() { + super({ + id: 'workbench.userDataSync.actions.acceptMerges', + title: localize('accept merges title', "Accept Merge"), + menu: [{ + id: MenuId.MergeToolbar, + when: ContextKeyExpr.and(ContextKeyDefinedExpr.create('isMergeEditor'), ContextKeyEqualsExpr.create('baseResourceScheme', USER_DATA_SYNC_SCHEME)), + }], + }); + } + + async run(accessor: ServicesAccessor, previewResource: URI): Promise { + const textFileService = accessor.get(ITextFileService); + await textFileService.save(previewResource); + const content = await textFileService.read(previewResource); + await that.userDataSyncService.accept(this.getSyncResource(previewResource), previewResource, content.value, true); + } + + private getSyncResource(previewResource: URI): SyncResource { + const conflict = that.userDataSyncService.conflicts.find(([, conflicts]) => conflicts.some(conflict => isEqual(conflict.previewResource, previewResource))); + if (conflict) { + return conflict[0]; + } + throw new Error(`Unknown resource: ${previewResource.toString()}`); + } + })); + } + private registerViews(): void { const container = this.registerViewContainer(); this.registerDataViews(container);