Skip to content

Commit d4dfc8b

Browse files
committed
Show buttons to accept local or remote changes
1 parent 616c96a commit d4dfc8b

2 files changed

Lines changed: 118 additions & 37 deletions

File tree

src/vs/platform/userDataSync/common/userDataSync.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
1818
import { IStringDictionary } from 'vs/base/common/collections';
1919
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
2020
import { URI } from 'vs/base/common/uri';
21+
import { isEqual } from 'vs/base/common/resources';
2122

2223
export const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store';
2324

@@ -256,4 +257,11 @@ export interface ISettingsSyncService extends IUserDataSynchroniser {
256257
}
257258

258259
export const CONTEXT_SYNC_STATE = new RawContextKey<string>('syncStatus', SyncStatus.Uninitialized);
260+
259261
export const USER_DATA_SYNC_SCHEME = 'vscode-userdata-sync';
262+
export function toRemoteContentResource(source: SyncSource): URI {
263+
return URI.from({ scheme: USER_DATA_SYNC_SCHEME, path: `${source}/remoteContent` });
264+
}
265+
export function getSyncSourceFromRemoteContentResource(uri: URI): SyncSource | undefined {
266+
return [SyncSource.Settings, SyncSource.Keybindings, SyncSource.Extensions, SyncSource.UIState].filter(source => isEqual(uri, toRemoteContentResource(source)))[0];
267+
}

src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts

Lines changed: 110 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
7-
import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync';
7+
import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, USER_DATA_SYNC_SCHEME, toRemoteContentResource, getSyncSourceFromRemoteContentResource } from 'vs/platform/userDataSync/common/userDataSync';
88
import { localize } from 'vs/nls';
9-
import { Disposable, MutableDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
9+
import { Disposable, MutableDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
1010
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
1111
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
1212
import { MenuRegistry, MenuId, IMenuItem } from 'vs/platform/actions/common/actions';
@@ -15,8 +15,6 @@ import { IActivityService, IBadge, NumberBadge, ProgressBadge } from 'vs/workben
1515
import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity';
1616
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
1717
import { URI } from 'vs/base/common/uri';
18-
import { registerAndGetAmdImageURL } from 'vs/base/common/amd';
19-
import { ResourceContextKey } from 'vs/workbench/common/resources';
2018
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
2119
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
2220
import { Event } from 'vs/base/common/event';
@@ -41,15 +39,19 @@ import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/s
4139
import { IModelService } from 'vs/editor/common/services/modelService';
4240
import { IModeService } from 'vs/editor/common/services/modeService';
4341
import type { ITextModel } from 'vs/editor/common/model';
42+
import type { IEditorContribution } from 'vs/editor/common/editorCommon';
43+
import type { ICodeEditor } from 'vs/editor/browser/editorBrowser';
44+
import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets';
45+
import { IFileService } from 'vs/platform/files/common/files';
46+
import { VSBuffer } from 'vs/base/common/buffer';
47+
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
4448

4549
const enum AuthStatus {
4650
Initializing = 'Initializing',
4751
SignedIn = 'SignedIn',
4852
SignedOut = 'SignedOut'
4953
}
5054
const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey<string>('authTokenStatus', AuthStatus.Initializing);
51-
const SYNC_PUSH_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-light.svg`));
52-
const SYNC_PUSH_DARK_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-dark.svg`));
5355

5456
type ConfigureSyncQuickPickItem = { id: string, label: string, description?: string };
5557

@@ -105,6 +107,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
105107
});
106108

107109
textModelResolverService.registerTextModelContentProvider(USER_DATA_SYNC_SCHEME, instantiationService.createInstance(UserDataRemoteContentProvider));
110+
registerEditorContribution(AcceptChangesContribution.ID, AcceptChangesContribution);
108111
}
109112
}
110113

@@ -459,7 +462,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
459462
}
460463
if (rightResource) {
461464
await this.editorService.openEditor({
462-
leftResource: URI.from({ scheme: USER_DATA_SYNC_SCHEME, path: `${this.userDataSyncService.conflictsSource}/remoteContent` }),
465+
leftResource: toRemoteContentResource(this.userDataSyncService.conflictsSource!),
463466
rightResource,
464467
label,
465468
options: {
@@ -548,14 +551,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
548551
group: '5_sync',
549552
command: {
550553
id: resolveConflictsCommandId,
551-
title: localize('resolveConflicts_global', "Resolve sync conflicts (1)"),
554+
title: localize('resolveConflicts_global', "Show sync conflicts (1)"),
552555
},
553556
when: resolveConflictsWhenContext,
554557
});
555558
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
556559
command: {
557560
id: resolveConflictsCommandId,
558-
title: localize('resolveConflicts', "Sync: Resolve sync conflicts"),
561+
title: localize('showConflicts', "Sync: Show sync conflicts"),
559562
},
560563
when: resolveConflictsWhenContext,
561564
});
@@ -569,32 +572,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
569572
},
570573
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts)),
571574
});
572-
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
573-
command: {
574-
id: continueSyncCommandId,
575-
title: localize('continue sync', "Sync: Continue sync"),
576-
icon: {
577-
light: SYNC_PUSH_LIGHT_ICON_URI,
578-
dark: SYNC_PUSH_DARK_ICON_URI
579-
}
580-
},
581-
group: 'navigation',
582-
order: 1,
583-
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), ResourceContextKey.Resource.isEqualTo(this.workbenchEnvironmentService.settingsSyncPreviewResource.toString())),
584-
});
585-
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
586-
command: {
587-
id: continueSyncCommandId,
588-
title: localize('continue sync', "Sync: Continue sync"),
589-
icon: {
590-
light: SYNC_PUSH_LIGHT_ICON_URI,
591-
dark: SYNC_PUSH_DARK_ICON_URI
592-
}
593-
},
594-
group: 'navigation',
595-
order: 1,
596-
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), ResourceContextKey.Resource.isEqualTo(this.workbenchEnvironmentService.keybindingsSyncPreviewResource.toString())),
597-
});
598575

599576
const signOutMenuItem: IMenuItem = {
600577
group: '5_sync',
@@ -650,10 +627,10 @@ class UserDataRemoteContentProvider implements ITextModelContentProvider {
650627

651628
provideTextContent(uri: URI): Promise<ITextModel> | null {
652629
let promise: Promise<string | null> | undefined;
653-
if (uri.path === `${SyncSource.Settings}/remoteContent`) {
630+
if (isEqual(uri, toRemoteContentResource(SyncSource.Settings))) {
654631
promise = this.userDataSyncService.getRemoteContent(SyncSource.Settings);
655632
}
656-
if (uri.path === `${SyncSource.Keybindings}/remoteContent`) {
633+
if (isEqual(uri, toRemoteContentResource(SyncSource.Keybindings))) {
657634
promise = this.userDataSyncService.getRemoteContent(SyncSource.Keybindings);
658635
}
659636
if (promise) {
@@ -663,3 +640,99 @@ class UserDataRemoteContentProvider implements ITextModelContentProvider {
663640
}
664641
}
665642

643+
class AcceptChangesContribution extends Disposable implements IEditorContribution {
644+
645+
static get(editor: ICodeEditor): AcceptChangesContribution {
646+
return editor.getContribution<AcceptChangesContribution>(AcceptChangesContribution.ID);
647+
}
648+
649+
public static readonly ID = 'editor.contrib.acceptChangesButton';
650+
651+
private acceptChangesButton: FloatingClickWidget | undefined;
652+
653+
constructor(
654+
private editor: ICodeEditor,
655+
@IInstantiationService private readonly instantiationService: IInstantiationService,
656+
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
657+
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
658+
@IFileService private readonly fileService: IFileService
659+
) {
660+
super();
661+
662+
this.update();
663+
this.registerListeners();
664+
}
665+
666+
private registerListeners(): void {
667+
this._register(this.editor.onDidChangeModel(e => this.update()));
668+
}
669+
670+
private update(): void {
671+
if (!this.shouldShowButton(this.editor)) {
672+
this.disposeAcceptChangesWidgetRenderer();
673+
return;
674+
}
675+
676+
this.createAcceptChangesWidgetRenderer();
677+
}
678+
679+
private shouldShowButton(editor: ICodeEditor): boolean {
680+
const model = editor.getModel();
681+
if (!model) {
682+
return false; // we need a model
683+
}
684+
685+
if (isEqual(model.uri, this.environmentService.settingsSyncPreviewResource)) {
686+
return true;
687+
}
688+
689+
if (isEqual(model.uri, this.environmentService.keybindingsSyncPreviewResource)) {
690+
return true;
691+
}
692+
693+
if (getSyncSourceFromRemoteContentResource(model.uri) !== undefined) {
694+
return true;
695+
}
696+
697+
return false;
698+
}
699+
700+
private createAcceptChangesWidgetRenderer(): void {
701+
if (!this.acceptChangesButton) {
702+
this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, getSyncSourceFromRemoteContentResource(this.editor.getModel()!.uri) !== undefined ? localize('accept remote', "Accept (Remote)") : localize('accept local', "Accept (Local)"), null);
703+
this._register(this.acceptChangesButton.onClick(async () => {
704+
const model = this.editor.getModel();
705+
if (model) {
706+
const syncSource = getSyncSourceFromRemoteContentResource(model.uri);
707+
if (syncSource === SyncSource.Settings) {
708+
const remoteContent = await this.userDataSyncService.getRemoteContent(SyncSource.Settings);
709+
if (remoteContent) {
710+
await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(remoteContent));
711+
}
712+
}
713+
else if (syncSource === SyncSource.Keybindings) {
714+
const remoteContent = await this.userDataSyncService.getRemoteContent(SyncSource.Keybindings);
715+
if (remoteContent) {
716+
await this.fileService.writeFile(this.environmentService.keybindingsSyncPreviewResource, VSBuffer.fromString(remoteContent));
717+
}
718+
}
719+
await this.userDataSyncService.sync(true);
720+
}
721+
}));
722+
723+
this.acceptChangesButton.render();
724+
}
725+
}
726+
727+
private disposeAcceptChangesWidgetRenderer(): void {
728+
dispose(this.acceptChangesButton);
729+
this.acceptChangesButton = undefined;
730+
}
731+
732+
dispose(): void {
733+
this.disposeAcceptChangesWidgetRenderer();
734+
735+
super.dispose();
736+
}
737+
}
738+

0 commit comments

Comments
 (0)