Merge from vscode 3c6f6af7347d38e87bc6406024e8dcf9e9bce229 (#8962)

* Merge from vscode 3c6f6af7347d38e87bc6406024e8dcf9e9bce229

* skip failing tests

* update mac build image
This commit is contained in:
Anthony Dresser
2020-01-27 15:28:17 -08:00
committed by Karl Burtram
parent 0eaee18dc4
commit fefe1454de
481 changed files with 12764 additions and 7836 deletions

View File

@@ -4,26 +4,21 @@
*--------------------------------------------------------------------------------------------*/
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, USER_DATA_SYNC_SCHEME, toRemoteContentResource, getSyncSourceFromRemoteContentResource, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync';
import { localize } from 'vs/nls';
import { Disposable, MutableDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Disposable, MutableDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { MenuRegistry, MenuId, IMenuItem } from 'vs/platform/actions/common/actions';
import { IContextKeyService, IContextKey, ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IActivityService, IBadge, NumberBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { URI } from 'vs/base/common/uri';
import { registerAndGetAmdImageURL } from 'vs/base/common/amd';
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';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { isEqual } from 'vs/base/common/resources';
import { IEditorInput } from 'vs/workbench/common/editor';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { isWeb } from 'vs/base/common/platform';
@@ -37,6 +32,19 @@ import { IAuthenticationService } from 'vs/workbench/services/authentication/bro
import { Session } from 'vs/editor/common/modes';
import { isPromiseCanceledError, canceled } from 'vs/base/common/errors';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import type { ITextModel } from 'vs/editor/common/model';
import type { IEditorContribution } from 'vs/editor/common/editorCommon';
import type { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import type { IEditorInput } from 'vs/workbench/common/editor';
import { Action } from 'vs/base/common/actions';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
const enum AuthStatus {
Initializing = 'Initializing',
@@ -44,11 +52,26 @@ const enum AuthStatus {
SignedOut = 'SignedOut'
}
const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey<string>('authTokenStatus', AuthStatus.Initializing);
const SYNC_PUSH_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-light.svg`));
const SYNC_PUSH_DARK_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-dark.svg`));
type ConfigureSyncQuickPickItem = { id: string, label: string, description?: string };
function getSyncAreaLabel(source: SyncSource): string {
switch (source) {
case SyncSource.Settings: return localize('settings', "Settings");
case SyncSource.Keybindings: return localize('keybindings', "Keybindings");
case SyncSource.Extensions: return localize('extensions', "Extensions");
case SyncSource.GlobalState: return localize('ui state label', "UI State");
}
}
type FirstTimeSyncClassification = {
action: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
};
type SyncErrorClassification = {
source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
};
export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution {
private static readonly ENABLEMENT_SETTING = 'sync.enable';
@@ -69,8 +92,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
@INotificationService private readonly notificationService: INotificationService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEditorService private readonly editorService: IEditorService,
@ITextFileService private readonly textFileService: ITextFileService,
@IHistoryService private readonly historyService: IHistoryService,
@IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService,
@IDialogService private readonly dialogService: IDialogService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@@ -78,6 +99,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
@IOutputService private readonly outputService: IOutputService,
@IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService,
@IUserDataAutoSyncService userDataAutoSyncService: IUserDataAutoSyncService,
@ITextModelService textModelResolverService: ITextModelService,
@IPreferencesService private readonly preferencesService: IPreferencesService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
) {
super();
this.userDataSyncStore = getUserDataSyncStore(configurationService);
@@ -91,6 +115,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
this._register(this.authenticationService.onDidRegisterAuthenticationProvider(e => this.onDidRegisterAuthenticationProvider(e)));
this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(e => this.onDidUnregisterAuthenticationProvider(e)));
this._register(this.authenticationService.onDidChangeSessions(e => this.onDidChangeSessions(e)));
this._register(userDataAutoSyncService.onError(({ code, source }) => this.onAutoSyncError(code, source)));
this.registerActions();
this.initializeActiveAccount().then(_ => {
if (isWeb) {
@@ -99,6 +124,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(() => userDataAutoSyncService.triggerAutoSync()));
}
});
textModelResolverService.registerTextModelContentProvider(USER_DATA_SYNC_SCHEME, instantiationService.createInstance(UserDataRemoteContentProvider));
registerEditorContribution(AcceptChangesContribution.ID, AcceptChangesContribution);
}
}
@@ -186,12 +214,17 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}
if (this.userDataSyncService.status === SyncStatus.HasConflicts) {
if (!this.conflictsWarningDisposable.value) {
const handle = this.notificationService.prompt(Severity.Warning, localize('conflicts detected', "Unable to sync due to conflicts. Please resolve them to continue."),
const conflictsEditorInput = this.getConflictsEditorInput(this.userDataSyncService.conflictsSource!);
if (!conflictsEditorInput && !this.conflictsWarningDisposable.value) {
const conflictsArea = getSyncAreaLabel(this.userDataSyncService.conflictsSource!);
const handle = this.notificationService.prompt(Severity.Warning, localize('conflicts detected', "Unable to sync due to conflicts in {0}. Please resolve them to continue.", conflictsArea),
[
{
label: localize('resolve', "Resolve Conflicts"),
run: () => this.handleConflicts()
label: localize('show conflicts', "Show Conflicts"),
run: () => {
this.telemetryService.publicLog2('sync/showConflicts');
this.handleConflicts();
}
}
],
{
@@ -202,10 +235,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
handle.onDidClose(() => this.conflictsWarningDisposable.clear());
}
} else {
const previewEditorInput = this.getPreviewEditorInput();
if (previewEditorInput) {
previewEditorInput.dispose();
}
this.getAllConflictsEditorInputs().forEach(input => input.dispose());
this.conflictsWarningDisposable.clear();
}
}
@@ -215,7 +245,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
const enabled = this.configurationService.getValue<boolean>(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING);
if (enabled) {
if (this.authenticationState.get() === AuthStatus.SignedOut) {
const handle = this.notificationService.prompt(Severity.Info, localize('sign in message', "Please sign in with your {0} account to continue sync", this.userDataSyncStore!.account),
const displayName = this.authenticationService.getDisplayName(this.userDataSyncStore!.authenticationProviderId);
const handle = this.notificationService.prompt(Severity.Info, localize('sign in message', "Please sign in with your {0} account to continue sync", displayName),
[
{
label: localize('Sign in', "Sign in"),
@@ -230,6 +261,37 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}
}
private onAutoSyncError(code: UserDataSyncErrorCode, source?: SyncSource): void {
switch (code) {
case UserDataSyncErrorCode.TooManyFailures:
this.telemetryService.publicLog2('sync/errorTooMany');
this.disableSync();
this.notificationService.notify({
severity: Severity.Error,
message: localize('too many errors', "Turned off sync because of too many failure attempts. Please open Sync log to check the failures and turn on sync."),
actions: {
primary: [new Action('open sync log', localize('open log', "Show logs"), undefined, true, () => this.showSyncLog())]
}
});
return;
case UserDataSyncErrorCode.TooLarge:
this.telemetryService.publicLog2<{ source: string }, SyncErrorClassification>('sync/errorTooLarge', { source: source! });
if (source === SyncSource.Keybindings || source === SyncSource.Settings) {
const sourceArea = getSyncAreaLabel(source);
this.disableSync();
this.notificationService.notify({
severity: Severity.Error,
message: localize('too large', "Turned off sync because size of the {0} file to sync is larger than {1}. Please open the file and reduce the size and turn on sync", sourceArea, '1MB'),
actions: {
primary: [new Action('open sync log', localize('open file', "Show {0} file", sourceArea), undefined, true,
() => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))]
}
});
}
return;
}
}
private async updateBadge(): Promise<void> {
this.badgeDisposable.clear();
@@ -240,7 +302,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.configurationService.getValue<boolean>(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING) && this.authenticationState.get() === AuthStatus.SignedOut) {
badge = new NumberBadge(1, () => localize('sign in to sync', "Sign in to Sync"));
} else if (this.userDataSyncService.status === SyncStatus.HasConflicts) {
badge = new NumberBadge(1, () => localize('resolve conflicts', "Resolve Conflicts"));
badge = new NumberBadge(1, () => localize('show conflicts', "Show Conflicts"));
} else if (this.userDataSyncService.status === SyncStatus.Syncing) {
badge = new ProgressBadge(() => localize('syncing', "Synchronizing User Configuration..."));
clazz = 'progress-badge';
@@ -261,10 +323,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
quickPick.ok = false;
quickPick.customButton = true;
if (this.authenticationState.get() === AuthStatus.SignedIn) {
quickPick.description = localize('turn on sync detail', "Turn on to synchronize your following data across all your devices.");
quickPick.customLabel = localize('turn on', "Turn on");
} else {
quickPick.description = localize('sign in and turn on sync detail', "Please sign in with your {0} account to synchronize your following data across all your devices.", this.userDataSyncStore!.account);
const displayName = this.authenticationService.getDisplayName(this.userDataSyncStore!.authenticationProviderId);
quickPick.description = localize('sign in and turn on sync detail', "Please sign in with your {0} account to synchronize your following data across all your devices.", displayName);
quickPick.customLabel = localize('sign in and turn on sync', "Sign in & Turn on");
}
quickPick.placeholder = localize('configure sync placeholder', "Choose what to sync");
@@ -296,16 +358,16 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
private getConfigureSyncQuickPickItems(): ConfigureSyncQuickPickItem[] {
return [{
id: 'sync.enableSettings',
label: localize('settings', "Settings")
label: getSyncAreaLabel(SyncSource.Settings)
}, {
id: 'sync.enableKeybindings',
label: localize('keybindings', "Keybindings")
label: getSyncAreaLabel(SyncSource.Keybindings)
}, {
id: 'sync.enableExtensions',
label: localize('extensions', "Extensions")
label: getSyncAreaLabel(SyncSource.Extensions)
}, {
id: 'sync.enableUIState',
label: localize('ui state label', "UI State"),
label: getSyncAreaLabel(SyncSource.GlobalState),
description: localize('ui state description', "Display Language (Only)")
}];
}
@@ -369,9 +431,17 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}
);
switch (result.choice) {
case 0: await this.userDataSyncService.sync(); break;
case 1: throw canceled();
case 2: await this.userDataSyncService.pull(); break;
case 0:
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'merge' });
await this.userDataSyncService.sync();
break;
case 1:
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'cancelled' });
throw canceled();
case 2:
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'replace-local' });
await this.userDataSyncService.pull();
break;
}
}
@@ -383,23 +453,28 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
const result = await this.dialogService.confirm({
type: 'info',
message: localize('turn off sync confirmation', "Turn off Sync"),
detail: localize('turn off sync detail', "Your settings, keybindings, extensions and more will no longer be synced."),
detail: localize('turn off sync detail', "Your settings, keybindings, extensions and UI State will no longer be synced."),
primaryButton: localize('turn off', "Turn off"),
checkbox: {
label: localize('turn off sync everywhere', "Turn off sync in all your devices and clear the data in cloud.")
label: localize('turn off sync everywhere', "Turn off sync on all your devices and clear the data from the cloud.")
}
});
if (result.confirmed) {
await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, false);
await this.disableSync();
if (result.checkboxChecked) {
this.telemetryService.publicLog2('sync/turnOffEveryWhere');
await this.userDataSyncService.reset();
}
}
}
private disableSync(): Promise<void> {
return this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, undefined, ConfigurationTarget.USER);
}
private async signIn(): Promise<void> {
try {
this.activeAccount = await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId);
this.activeAccount = await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId, ['https://management.core.windows.net/.default', 'offline_access']);
} catch (e) {
this.notificationService.error(e);
throw e;
@@ -413,66 +488,45 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}
}
private async continueSync(): Promise<void> {
// Get the preview editor
const previewEditorInput = this.getPreviewEditorInput();
// Save the preview
if (previewEditorInput && previewEditorInput.isDirty()) {
await this.textFileService.save(previewEditorInput.getResource()!);
}
try {
// Continue Sync
await this.userDataSyncService.sync(true);
} catch (error) {
this.notificationService.error(error);
return;
}
// Close the preview editor
if (previewEditorInput) {
previewEditorInput.dispose();
}
private getConflictsEditorInput(source: SyncSource): IEditorInput | undefined {
const previewResource = source === SyncSource.Settings ? this.workbenchEnvironmentService.settingsSyncPreviewResource
: source === SyncSource.Keybindings ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource
: null;
return previewResource ? this.editorService.editors.filter(input => input instanceof DiffEditorInput && isEqual(previewResource, input.master.getResource()))[0] : undefined;
}
private getPreviewEditorInput(): IEditorInput | undefined {
return this.editorService.editors.filter(input => isEqual(input.getResource(), this.workbenchEnvironmentService.settingsSyncPreviewResource) || isEqual(input.getResource(), this.workbenchEnvironmentService.keybindingsSyncPreviewResource))[0];
private getAllConflictsEditorInputs(): IEditorInput[] {
return this.editorService.editors.filter(input => {
const resource = input instanceof DiffEditorInput ? input.master.getResource() : input.getResource();
return isEqual(resource, this.workbenchEnvironmentService.settingsSyncPreviewResource) || isEqual(resource, this.workbenchEnvironmentService.keybindingsSyncPreviewResource);
});
}
private async handleConflicts(): Promise<void> {
const conflictsResource = this.getConflictsResource();
if (conflictsResource) {
const resourceInput = {
resource: conflictsResource,
let previewResource: URI | undefined = undefined;
let label: string = '';
if (this.userDataSyncService.conflictsSource === SyncSource.Settings) {
previewResource = this.workbenchEnvironmentService.settingsSyncPreviewResource;
label = localize('settings conflicts preview', "Settings Conflicts (Remote ↔ Local)");
} else if (this.userDataSyncService.conflictsSource === SyncSource.Keybindings) {
previewResource = this.workbenchEnvironmentService.keybindingsSyncPreviewResource;
label = localize('keybindings conflicts preview', "Keybindings Conflicts (Remote ↔ Local)");
}
if (previewResource) {
const remoteContentResource = toRemoteContentResource(this.userDataSyncService.conflictsSource!);
await this.editorService.openEditor({
leftResource: remoteContentResource,
rightResource: previewResource,
label,
options: {
preserveFocus: false,
pinned: false,
pinned: true,
revealIfVisible: true,
},
mode: 'jsonc'
};
this.editorService.openEditor(resourceInput)
.then(editor => {
this.historyService.remove(resourceInput);
if (editor && editor.input) {
// Trigger sync after closing the conflicts editor.
const disposable = editor.input.onDispose(() => {
disposable.dispose();
this.userDataSyncService.sync(true);
});
}
});
});
}
}
private getConflictsResource(): URI | null {
if (this.userDataSyncService.conflictsSource === SyncSource.Settings) {
return this.workbenchEnvironmentService.settingsSyncPreviewResource;
}
if (this.userDataSyncService.conflictsSource === SyncSource.Keybindings) {
return this.workbenchEnvironmentService.keybindingsSyncPreviewResource;
}
return null;
}
private showSyncLog(): Promise<void> {
return this.outputService.showChannel(Constants.userDataSyncLogChannelId);
}
@@ -494,14 +548,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
group: '5_sync',
command: {
id: turnOnSyncCommandId,
title: localize('global activity turn on sync', "Turn on sync...")
title: localize('global activity turn on sync', "Turn on Sync...")
},
when: turnOnSyncWhenContext,
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: turnOnSyncCommandId,
title: localize('turn on sync...', "Sync: Turn on sync...")
title: localize('turn on sync...', "Sync: Turn on Sync...")
},
when: turnOnSyncWhenContext,
});
@@ -513,7 +567,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
group: '5_sync',
command: {
id: signInCommandId,
title: localize('global activity sign in', "Sign in to sync... (1)")
title: localize('global activity sign in', "Sign in to Sync... (1)")
},
when: signInWhenContext,
});
@@ -531,14 +585,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
group: '5_sync',
command: {
id: stopSyncCommandId,
title: localize('global activity stop sync', "Turn off sync")
title: localize('global activity stop sync', "Turn off Sync")
},
when: ContextKeyExpr.and(ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.HasConflicts))
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: stopSyncCommandId,
title: localize('stop sync', "Sync: Turn off sync")
title: localize('stop sync', "Sync: Turn off Sync")
},
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`)),
});
@@ -550,54 +604,18 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
group: '5_sync',
command: {
id: resolveConflictsCommandId,
title: localize('resolveConflicts_global', "Resolve sync conflicts (1)"),
title: localize('resolveConflicts_global', "Show Sync Conflicts (1)"),
},
when: resolveConflictsWhenContext,
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: resolveConflictsCommandId,
title: localize('resolveConflicts', "Sync: Resolve sync conflicts"),
title: localize('showConflicts', "Sync: Show Sync Conflicts"),
},
when: resolveConflictsWhenContext,
});
const continueSyncCommandId = 'workbench.userData.actions.continueSync';
CommandsRegistry.registerCommand(continueSyncCommandId, () => this.continueSync());
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: continueSyncCommandId,
title: localize('continue sync', "Sync: Continue sync")
},
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts)),
});
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: continueSyncCommandId,
title: localize('continue sync', "Sync: Continue sync"),
icon: {
light: SYNC_PUSH_LIGHT_ICON_URI,
dark: SYNC_PUSH_DARK_ICON_URI
}
},
group: 'navigation',
order: 1,
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), ResourceContextKey.Resource.isEqualTo(this.workbenchEnvironmentService.settingsSyncPreviewResource.toString())),
});
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: continueSyncCommandId,
title: localize('continue sync', "Sync: Continue sync"),
icon: {
light: SYNC_PUSH_LIGHT_ICON_URI,
dark: SYNC_PUSH_DARK_ICON_URI
}
},
group: 'navigation',
order: 1,
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), ResourceContextKey.Resource.isEqualTo(this.workbenchEnvironmentService.keybindingsSyncPreviewResource.toString())),
});
const signOutMenuItem: IMenuItem = {
group: '5_sync',
command: {
@@ -640,3 +658,151 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
});
}
}
class UserDataRemoteContentProvider implements ITextModelContentProvider {
constructor(
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
@IModelService private readonly modelService: IModelService,
@IModeService private readonly modeService: IModeService,
) {
}
provideTextContent(uri: URI): Promise<ITextModel> | null {
let promise: Promise<string | null> | undefined;
if (isEqual(uri, toRemoteContentResource(SyncSource.Settings))) {
promise = this.userDataSyncService.getRemoteContent(SyncSource.Settings);
}
if (isEqual(uri, toRemoteContentResource(SyncSource.Keybindings))) {
promise = this.userDataSyncService.getRemoteContent(SyncSource.Keybindings);
}
if (promise) {
return promise.then(content => this.modelService.createModel(content || '', this.modeService.create('jsonc'), uri));
}
return null;
}
}
type SyncConflictsClassification = {
source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
action: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
};
class AcceptChangesContribution extends Disposable implements IEditorContribution {
static get(editor: ICodeEditor): AcceptChangesContribution {
return editor.getContribution<AcceptChangesContribution>(AcceptChangesContribution.ID);
}
public static readonly ID = 'editor.contrib.acceptChangesButton';
private acceptChangesButton: FloatingClickWidget | undefined;
constructor(
private editor: ICodeEditor,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
@INotificationService private readonly notificationService: INotificationService,
@IDialogService private readonly dialogService: IDialogService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@ITelemetryService private readonly telemetryService: ITelemetryService
) {
super();
this.update();
this.registerListeners();
}
private registerListeners(): void {
this._register(this.editor.onDidChangeModel(e => this.update()));
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('diffEditor.renderSideBySide'))(() => this.update()));
}
private update(): void {
if (!this.shouldShowButton(this.editor)) {
this.disposeAcceptChangesWidgetRenderer();
return;
}
this.createAcceptChangesWidgetRenderer();
}
private shouldShowButton(editor: ICodeEditor): boolean {
const model = editor.getModel();
if (!model) {
return false; // we need a model
}
if (this.isSyncPreviewResource(model.uri)) {
return true;
}
if (getSyncSourceFromRemoteContentResource(model.uri) !== undefined) {
return this.configurationService.getValue<boolean>('diffEditor.renderSideBySide');
}
return false;
}
private isSyncPreviewResource(uri: URI): boolean {
if (isEqual(uri, this.environmentService.settingsSyncPreviewResource)) {
return true;
}
if (isEqual(uri, this.environmentService.keybindingsSyncPreviewResource)) {
return true;
}
return false;
}
private createAcceptChangesWidgetRenderer(): void {
if (!this.acceptChangesButton) {
const replaceLabel = localize('accept remote', "Replace (Overwrite Local)");
const applyLabel = localize('accept local', "Apply");
this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, getSyncSourceFromRemoteContentResource(this.editor.getModel()!.uri) !== undefined ? replaceLabel : applyLabel, null);
this._register(this.acceptChangesButton.onClick(async () => {
const model = this.editor.getModel();
if (model) {
const conflictsSource = this.userDataSyncService.conflictsSource;
const syncSource = getSyncSourceFromRemoteContentResource(model.uri);
this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: conflictsSource!, action: syncSource !== undefined ? 'replaceLocal' : 'apply' });
if (syncSource !== undefined) {
const syncAreaLabel = getSyncAreaLabel(syncSource);
const result = await this.dialogService.confirm({
type: 'info',
title: localize('Sync overwrite local', "Sync: {0}", replaceLabel),
message: localize('confirm replace and overwrite local', "Would you like to replace Local {0} with Remote {1}?", syncAreaLabel, syncAreaLabel),
primaryButton: replaceLabel
});
if (!result.confirmed) {
return;
}
}
try {
await this.userDataSyncService.resolveConflictsAndContinueSync(model.getValue());
} catch (e) {
this.userDataSyncService.restart().then(() => {
if (conflictsSource === this.userDataSyncService.conflictsSource) {
this.notificationService.warn(localize('update conflicts', "Could not resolve conflicts as there is new local version available. Please try again."));
}
});
}
}
}));
this.acceptChangesButton.render();
}
}
private disposeAcceptChangesWidgetRenderer(): void {
dispose(this.acceptChangesButton);
this.acceptChangesButton = undefined;
}
dispose(): void {
this.disposeAcceptChangesWidgetRenderer();
super.dispose();
}
}