userDataSync.ts 48.5 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6 7
import { Action } from 'vs/base/common/actions';
import { toErrorMessage } from 'vs/base/common/errorMessage';
8
import { isPromiseCanceledError } from 'vs/base/common/errors';
9
import { Event } from 'vs/base/common/event';
10
import { Disposable, DisposableStore, dispose, MutableDisposable, toDisposable, IDisposable } from 'vs/base/common/lifecycle';
11
import { isEqual, basename } from 'vs/base/common/resources';
12 13
import { URI } from 'vs/base/common/uri';
import type { ICodeEditor } from 'vs/editor/browser/editorBrowser';
14
import { registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
15 16
import type { IEditorContribution } from 'vs/editor/common/editorCommon';
import type { ITextModel } from 'vs/editor/common/model';
S
Sandeep Somavarapu 已提交
17 18
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
19 20
import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService';
import { localize } from 'vs/nls';
21 22
import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
23
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
24
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
25 26 27
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';
28
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
29
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
30
import {
31 32 33
	IUserDataAutoSyncService, IUserDataSyncService, registerConfiguration,
	SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService,
	SyncResourceConflicts, Conflict, getSyncResourceFromLocalPreview
34
} from 'vs/platform/userDataSync/common/userDataSync';
35
import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets';
36
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
S
Sandeep Somavarapu 已提交
37
import { IEditorInput, toResource, SideBySideEditor } from 'vs/workbench/common/editor';
38 39 40
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import * as Constants from 'vs/workbench/contrib/logs/common/logConstants';
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
41
import { IActivityService, IBadge, NumberBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
42 43
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
S
Sandeep Somavarapu 已提交
44
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
45
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
46
import { fromNow } from 'vs/base/common/date';
47
import { IProductService } from 'vs/platform/product/common/productService';
48 49
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IOpenerService } from 'vs/platform/opener/common/opener';
50
import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
51 52 53
import { Registry } from 'vs/platform/registry/common/platform';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { Codicon } from 'vs/base/common/codicons';
S
#93960  
Sandeep Somavarapu 已提交
54 55
import { ViewContainerLocation, IViewContainersRegistry, Extensions, ViewContainer } from 'vs/workbench/common/views';
import { UserDataSyncViewPaneContainer, UserDataSyncDataViews } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncViews';
56
import { IUserDataSyncWorkbenchService, CONTEXT_ENABLE_VIEWS, getSyncAreaLabel, AccountStatus, CONTEXT_SYNC_STATE, CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE, CONFIGURE_SYNC_COMMAND_ID, ENABLE_SYNC_VIEWS_COMMAND_ID, SHOW_SYNC_LOG_COMMAND_ID } from 'vs/workbench/services/userDataSync/common/userDataSync';
57

58
const CONTEXT_CONFLICTS_SOURCES = new RawContextKey<string>('conflictsSources', '');
59

S
Sandeep Somavarapu 已提交
60
type ConfigureSyncQuickPickItem = { id: SyncResource, label: string, description?: string };
61

S
Sandeep Somavarapu 已提交
62 63 64 65 66
type SyncConflictsClassification = {
	source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
	action?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
};

67 68
const turnOnSyncCommand = { id: 'workbench.userDataSync.actions.turnOn', title: localize('turn on sync with category', "Preferences Sync: Turn On...") };
const turnOffSyncCommand = { id: 'workbench.userDataSync.actions.turnOff', title: localize('stop sync', "Preferences Sync: Turn Off") };
S
#93960  
Sandeep Somavarapu 已提交
69
const configureSyncCommand = { id: CONFIGURE_SYNC_COMMAND_ID, title: localize('configure sync', "Preferences Sync: Configure...") };
70 71 72
const resolveSettingsConflictsCommand = { id: 'workbench.userDataSync.actions.resolveSettingsConflicts', title: localize('showConflicts', "Preferences Sync: Show Settings Conflicts") };
const resolveKeybindingsConflictsCommand = { id: 'workbench.userDataSync.actions.resolveKeybindingsConflicts', title: localize('showKeybindingsConflicts', "Preferences Sync: Show Keybindings Conflicts") };
const resolveSnippetsConflictsCommand = { id: 'workbench.userDataSync.actions.resolveSnippetsConflicts', title: localize('showSnippetsConflicts', "Preferences Sync: Show User Snippets Conflicts") };
S
Sandeep Somavarapu 已提交
73
const syncNowCommand = {
74
	id: 'workbench.userDataSync.actions.syncNow',
S
Sandeep Somavarapu 已提交
75
	title: localize('sync now', "Preferences Sync: Sync Now"),
S
Sandeep Somavarapu 已提交
76 77 78 79 80 81 82 83
	description(userDataSyncService: IUserDataSyncService): string | undefined {
		if (userDataSyncService.status === SyncStatus.Syncing) {
			return localize('sync is on with syncing', "syncing");
		}
		if (userDataSyncService.lastSyncTime) {
			return localize('sync is on with time', "synced {0}", fromNow(userDataSyncService.lastSyncTime, true));
		}
		return undefined;
84 85
	}
};
86
const showSyncSettingsCommand = { id: 'workbench.userDataSync.actions.settings', title: localize('sync settings', "Preferences Sync: Show Settings"), };
87

S
Sandeep Somavarapu 已提交
88
const CONTEXT_TURNING_ON_STATE = new RawContextKey<false>('userDataSyncTurningOn', false);
89

90 91
export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution {

S
Sandeep Somavarapu 已提交
92
	private readonly turningOnSyncContext: IContextKey<boolean>;
93
	private readonly conflictsSources: IContextKey<string>;
S
#93960  
Sandeep Somavarapu 已提交
94
	private readonly viewsEnablementContext: IContextKey<boolean>;
S
Sandeep Somavarapu 已提交
95

96 97 98
	private readonly badgeDisposable = this._register(new MutableDisposable());

	constructor(
S
Sandeep Somavarapu 已提交
99
		@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
100
		@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
101
		@IUserDataSyncWorkbenchService private readonly userDataSyncWorkbenchService: IUserDataSyncWorkbenchService,
102 103 104 105 106
		@IContextKeyService contextKeyService: IContextKeyService,
		@IActivityService private readonly activityService: IActivityService,
		@INotificationService private readonly notificationService: INotificationService,
		@IEditorService private readonly editorService: IEditorService,
		@IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService,
107
		@IDialogService private readonly dialogService: IDialogService,
108
		@IQuickInputService private readonly quickInputService: IQuickInputService,
S
#93960  
Sandeep Somavarapu 已提交
109
		@IInstantiationService private readonly instantiationService: IInstantiationService,
S
Sandeep Somavarapu 已提交
110
		@IOutputService private readonly outputService: IOutputService,
111
		@IAuthenticationTokenService readonly authTokenService: IAuthenticationTokenService,
S
Sandeep Somavarapu 已提交
112
		@IUserDataAutoSyncService userDataAutoSyncService: IUserDataAutoSyncService,
113
		@ITextModelService private readonly textModelResolverService: ITextModelService,
S
Sandeep Somavarapu 已提交
114
		@IPreferencesService private readonly preferencesService: IPreferencesService,
S
Sandeep Somavarapu 已提交
115
		@ITelemetryService private readonly telemetryService: ITelemetryService,
116
		@IProductService private readonly productService: IProductService,
117 118
		@IStorageService private readonly storageService: IStorageService,
		@IOpenerService private readonly openerService: IOpenerService,
119
		@IAuthenticationService private readonly authenticationService: IAuthenticationService,
120 121
	) {
		super();
122

S
Sandeep Somavarapu 已提交
123
		this.turningOnSyncContext = CONTEXT_TURNING_ON_STATE.bindTo(contextKeyService);
124
		this.conflictsSources = CONTEXT_CONFLICTS_SOURCES.bindTo(contextKeyService);
S
#93960  
Sandeep Somavarapu 已提交
125
		this.viewsEnablementContext = CONTEXT_ENABLE_VIEWS.bindTo(contextKeyService);
126

127
		if (this.userDataSyncWorkbenchService.authenticationProviders.length) {
128
			registerConfiguration();
129

130
			this.updateBadge();
131
			this.onDidChangeConflicts(this.userDataSyncService.conflicts);
132

133 134 135 136 137
			this._register(Event.any(
				Event.debounce(userDataSyncService.onDidChangeStatus, () => undefined, 500),
				this.userDataSyncEnablementService.onDidChangeEnablement,
				this.userDataSyncWorkbenchService.onDidChangeAccountStatus
			)(() => this.updateBadge()));
138
			this._register(userDataSyncService.onDidChangeConflicts(() => this.onDidChangeConflicts(this.userDataSyncService.conflicts)));
139
			this._register(userDataSyncService.onSyncErrors(errors => this.onSynchronizerErrors(errors)));
140
			this._register(userDataAutoSyncService.onError(error => this.onAutoSyncError(error)));
141

142
			this.registerActions();
S
#93960  
Sandeep Somavarapu 已提交
143
			this.registerViews();
S
Sandeep Somavarapu 已提交
144 145

			textModelResolverService.registerTextModelContentProvider(USER_DATA_SYNC_SCHEME, instantiationService.createInstance(UserDataRemoteContentProvider));
146
			registerEditorContribution(AcceptChangesContribution.ID, AcceptChangesContribution);
147
		}
148 149
	}

S
Sandeep Somavarapu 已提交
150
	private readonly conflictsDisposables = new Map<SyncResource, IDisposable>();
151
	private onDidChangeConflicts(conflicts: SyncResourceConflicts[]) {
152 153
		this.updateBadge();
		if (conflicts.length) {
154 155
			const conflictsSources: SyncResource[] = conflicts.map(conflict => conflict.syncResource);
			this.conflictsSources.set(conflictsSources.join(','));
156 157 158
			if (conflictsSources.indexOf(SyncResource.Snippets) !== -1) {
				this.registerShowSnippetsConflictsAction();
			}
159 160 161

			// Clear and dispose conflicts those were cleared
			this.conflictsDisposables.forEach((disposable, conflictsSource) => {
162
				if (conflictsSources.indexOf(conflictsSource) === -1) {
163 164 165 166 167
					disposable.dispose();
					this.conflictsDisposables.delete(conflictsSource);
				}
			});

168
			for (const { syncResource, conflicts } of this.userDataSyncService.conflicts) {
169 170 171 172 173 174 175 176 177 178 179 180 181
				const conflictsEditorInputs = this.getConflictsEditorInputs(syncResource);

				// close stale conflicts editor previews
				if (conflictsEditorInputs.length) {
					conflictsEditorInputs.forEach(input => {
						if (!conflicts.some(({ local }) => isEqual(local, input.master.resource))) {
							input.dispose();
						}
					});
				}

				// Show conflicts notification if not shown before
				else if (!this.conflictsDisposables.has(syncResource)) {
182
					const conflictsArea = getSyncAreaLabel(syncResource);
183
					const handle = this.notificationService.prompt(Severity.Warning, localize('conflicts detected', "Unable to sync due to conflicts in {0}. Please resolve them to continue.", conflictsArea.toLowerCase()),
184
						[
185 186
							{
								label: localize('accept remote', "Accept Remote"),
S
Sandeep Somavarapu 已提交
187
								run: () => {
188 189
									this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: syncResource, action: 'acceptRemote' });
									this.acceptRemote(syncResource, conflicts);
S
Sandeep Somavarapu 已提交
190
								}
191 192 193
							},
							{
								label: localize('accept local', "Accept Local"),
S
Sandeep Somavarapu 已提交
194
								run: () => {
195 196
									this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: syncResource, action: 'acceptLocal' });
									this.acceptLocal(syncResource, conflicts);
S
Sandeep Somavarapu 已提交
197
								}
198
							},
199 200 201
							{
								label: localize('show conflicts', "Show Conflicts"),
								run: () => {
202 203
									this.telemetryService.publicLog2<{ source: string, action?: string }, SyncConflictsClassification>('sync/showConflicts', { source: syncResource });
									this.handleConflicts({ syncResource, conflicts });
204
								}
S
Sandeep Somavarapu 已提交
205
							}
206 207 208
						],
						{
							sticky: true
209
						}
210
					);
211
					this.conflictsDisposables.set(syncResource, toDisposable(() => {
212 213 214 215 216

						// close the conflicts warning notification
						handle.close();

						// close opened conflicts editor previews
217 218 219
						const conflictsEditorInputs = this.getConflictsEditorInputs(syncResource);
						if (conflictsEditorInputs.length) {
							conflictsEditorInputs.forEach(input => input.dispose());
220 221
						}

222
						this.conflictsDisposables.delete(syncResource);
223 224
					}));
				}
225 226
			}
		} else {
227
			this.conflictsSources.reset();
S
Sandeep Somavarapu 已提交
228
			this.getAllConflictsEditorInputs().forEach(input => input.dispose());
229 230
			this.conflictsDisposables.forEach(disposable => disposable.dispose());
			this.conflictsDisposables.clear();
231 232 233
		}
	}

234
	private async acceptRemote(syncResource: SyncResource, conflicts: Conflict[]) {
S
Sandeep Somavarapu 已提交
235
		try {
236 237
			for (const conflict of conflicts) {
				const modelRef = await this.textModelResolverService.createModelReference(conflict.remote);
238 239 240 241 242
				try {
					await this.userDataSyncService.acceptConflict(conflict.remote, modelRef.object.textEditorModel.getValue());
				} finally {
					modelRef.dispose();
				}
S
Sandeep Somavarapu 已提交
243 244 245 246 247 248
			}
		} catch (e) {
			this.notificationService.error(e);
		}
	}

249
	private async acceptLocal(syncResource: SyncResource, conflicts: Conflict[]): Promise<void> {
S
Sandeep Somavarapu 已提交
250
		try {
251 252
			for (const conflict of conflicts) {
				const modelRef = await this.textModelResolverService.createModelReference(conflict.local);
253 254 255 256 257
				try {
					await this.userDataSyncService.acceptConflict(conflict.local, modelRef.object.textEditorModel.getValue());
				} finally {
					modelRef.dispose();
				}
S
Sandeep Somavarapu 已提交
258 259 260 261 262 263
			}
		} catch (e) {
			this.notificationService.error(e);
		}
	}

264
	private onAutoSyncError(error: UserDataSyncError): boolean {
265
		switch (error.code) {
S
Sandeep Somavarapu 已提交
266 267 268 269
			case UserDataSyncErrorCode.TurnedOff:
			case UserDataSyncErrorCode.SessionExpired:
				this.notificationService.notify({
					severity: Severity.Info,
S
Sandeep Somavarapu 已提交
270
					message: localize('turned off', "Preferences sync was turned off from another device."),
S
Sandeep Somavarapu 已提交
271
					actions: {
S
Sandeep Somavarapu 已提交
272
						primary: [new Action('turn on sync', localize('turn on sync', "Turn on Preferences Sync..."), undefined, true, () => this.turnOn())]
273 274
					}
				});
275
				return true;
S
Sandeep Somavarapu 已提交
276
			case UserDataSyncErrorCode.TooLarge:
S
Sandeep Somavarapu 已提交
277 278 279
				if (error.resource === SyncResource.Keybindings || error.resource === SyncResource.Settings) {
					this.disableSync(error.resource);
					const sourceArea = getSyncAreaLabel(error.resource);
S
Sandeep Somavarapu 已提交
280 281
					this.notificationService.notify({
						severity: Severity.Error,
S
Sandeep Somavarapu 已提交
282
						message: localize('too large', "Disabled syncing {0} because size of the {1} file to sync is larger than {2}. Please open the file and reduce the size and enable sync", sourceArea.toLowerCase(), sourceArea.toLowerCase(), '100kb'),
S
Sandeep Somavarapu 已提交
283
						actions: {
S
Sandeep Somavarapu 已提交
284
							primary: [new Action('open sync file', localize('open file', "Open {0} File", sourceArea), undefined, true,
S
Sandeep Somavarapu 已提交
285
								() => error.resource === SyncResource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))]
S
Sandeep Somavarapu 已提交
286 287 288
						}
					});
				}
289
				return true;
290
			case UserDataSyncErrorCode.Incompatible:
S
Sandeep Somavarapu 已提交
291
			case UserDataSyncErrorCode.UpgradeRequired:
292 293 294
				this.disableSync();
				this.notificationService.notify({
					severity: Severity.Error,
295
					message: localize('error upgrade required', "Turned off sync because the current version ({0}) of {1} is not compatible with the Preferences Sync Service. Please update and turn on sync to continue syncing.", this.productService.version, this.productService.nameLong),
296
				});
297
				return true;
S
Sandeep Somavarapu 已提交
298
		}
299
		return false;
S
Sandeep Somavarapu 已提交
300 301
	}

S
Sandeep Somavarapu 已提交
302
	private readonly invalidContentErrorDisposables = new Map<SyncResource, IDisposable>();
303
	private onSynchronizerErrors(errors: [SyncResource, UserDataSyncError][]): void {
S
Sandeep Somavarapu 已提交
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
		if (errors.length) {
			for (const [source, error] of errors) {
				switch (error.code) {
					case UserDataSyncErrorCode.LocalInvalidContent:
						this.handleInvalidContentError(source);
						break;
					default:
						const disposable = this.invalidContentErrorDisposables.get(source);
						if (disposable) {
							disposable.dispose();
							this.invalidContentErrorDisposables.delete(source);
						}
				}
			}
		} else {
			this.invalidContentErrorDisposables.forEach(disposable => disposable.dispose());
			this.invalidContentErrorDisposables.clear();
		}
	}

S
Sandeep Somavarapu 已提交
324
	private handleInvalidContentError(source: SyncResource): void {
S
Sandeep Somavarapu 已提交
325 326 327
		if (this.invalidContentErrorDisposables.has(source)) {
			return;
		}
S
Sandeep Somavarapu 已提交
328
		if (source !== SyncResource.Settings && source !== SyncResource.Keybindings) {
S
Sandeep Somavarapu 已提交
329 330
			return;
		}
S
Sandeep Somavarapu 已提交
331
		const resource = source === SyncResource.Settings ? this.workbenchEnvironmentService.settingsResource : this.workbenchEnvironmentService.keybindingsResource;
S
Sandeep Somavarapu 已提交
332
		if (isEqual(resource, toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }))) {
S
Sandeep Somavarapu 已提交
333 334
			// Do not show notification if the file in error is active
			return;
S
Sandeep Somavarapu 已提交
335
		}
S
Sandeep Somavarapu 已提交
336 337 338 339 340 341
		const errorArea = getSyncAreaLabel(source);
		const handle = this.notificationService.notify({
			severity: Severity.Error,
			message: localize('errorInvalidConfiguration', "Unable to sync {0} because there are some errors/warnings in the file. Please open the file to correct errors/warnings in it.", errorArea.toLowerCase()),
			actions: {
				primary: [new Action('open sync file', localize('open file', "Open {0} File", errorArea), undefined, true,
S
Sandeep Somavarapu 已提交
342
					() => source === SyncResource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))]
S
Sandeep Somavarapu 已提交
343 344 345 346 347 348 349
			}
		});
		this.invalidContentErrorDisposables.set(source, toDisposable(() => {
			// close the error warning notification
			handle.close();
			this.invalidContentErrorDisposables.delete(source);
		}));
S
Sandeep Somavarapu 已提交
350 351
	}

352
	private async updateBadge(): Promise<void> {
353 354 355 356
		this.badgeDisposable.clear();

		let badge: IBadge | undefined = undefined;
		let clazz: string | undefined;
S
Sandeep Somavarapu 已提交
357
		let priority: number | undefined = undefined;
358

359
		if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.userDataSyncEnablementService.isEnabled() && this.userDataSyncWorkbenchService.accountStatus === AccountStatus.Unavailable) {
360 361
			badge = new NumberBadge(1, () => localize('sign in to sync preferences', "Sign in to Sync Preferences"));
		} else if (this.userDataSyncService.conflicts.length) {
362
			badge = new NumberBadge(this.userDataSyncService.conflicts.reduce((result, syncResourceConflict) => { return result + syncResourceConflict.conflicts.length; }, 0), () => localize('has conflicts', "Preferences Sync: Conflicts Detected"));
S
Sandeep Somavarapu 已提交
363
		} else if (this.turningOnSync) {
364 365 366
			badge = new ProgressBadge(() => localize('turning on syncing', "Turning on Preferences Sync..."));
			clazz = 'progress-badge';
			priority = 1;
367 368 369
		}

		if (badge) {
S
Sandeep Somavarapu 已提交
370
			this.badgeDisposable.value = this.activityService.showGlobalActivity({ badge, clazz, priority });
371 372 373
		}
	}

S
Sandeep Somavarapu 已提交
374 375 376 377 378 379 380 381 382
	private get turningOnSync(): boolean {
		return !!this.turningOnSyncContext.get();
	}

	private set turningOnSync(turningOn: boolean) {
		this.turningOnSyncContext.set(turningOn);
		this.updateBadge();
	}

383
	private async turnOn(): Promise<void> {
S
Sandeep Somavarapu 已提交
384
		this.turningOnSync = true;
S
Sandeep Somavarapu 已提交
385 386 387 388
		try {
			if (!this.storageService.getBoolean('sync.donotAskPreviewConfirmation', StorageScope.GLOBAL, false)) {
				if (!await this.askForConfirmation()) {
					return;
389 390
				}
			}
S
Sandeep Somavarapu 已提交
391 392 393 394
			const turnOn = await this.askToConfigure();
			if (!turnOn) {
				return;
			}
395
			await this.userDataSyncWorkbenchService.turnOn();
S
Sandeep Somavarapu 已提交
396
			this.storageService.store('sync.donotAskPreviewConfirmation', true, StorageScope.GLOBAL);
397 398 399 400 401 402 403 404
		} catch (e) {
			if (isPromiseCanceledError(e)) {
				return;
			}
			if (e instanceof UserDataSyncError && this.onAutoSyncError(e)) {
				return;
			}
			this.notificationService.error(localize('turn on failed', "Error while starting Sync: {0}", toErrorMessage(e)));
S
Sandeep Somavarapu 已提交
405
		} finally {
S
Sandeep Somavarapu 已提交
406
			this.turningOnSync = false;
407
		}
S
Sandeep Somavarapu 已提交
408
	}
409

S
Sandeep Somavarapu 已提交
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431
	private async askForConfirmation(): Promise<boolean> {
		const result = await this.dialogService.show(
			Severity.Info,
			localize('sync preview message', "Synchronizing your preferences is a preview feature, please read the documentation before turning it on."),
			[
				localize('open doc', "Open Documentation"),
				localize('turn on', "Turn On"),
				localize('cancel', "Cancel"),
			],
			{
				cancelId: 2
			}
		);
		switch (result.choice) {
			case 0: this.openerService.open(URI.parse('https://aka.ms/vscode-settings-sync-help')); return false;
			case 2: return false;
		}
		return true;
	}

	private async askToConfigure(): Promise<boolean> {
		return new Promise<boolean>((c, e) => {
432 433 434
			const disposables: DisposableStore = new DisposableStore();
			const quickPick = this.quickInputService.createQuickPick<ConfigureSyncQuickPickItem>();
			disposables.add(quickPick);
S
Sandeep Somavarapu 已提交
435
			quickPick.title = localize('Preferences Sync Title', "Preferences Sync");
436 437
			quickPick.ok = false;
			quickPick.customButton = true;
438
			if (this.userDataSyncWorkbenchService.all.length) {
439 440
				quickPick.customLabel = localize('turn on', "Turn On");
			} else {
441
				const orTerm = localize({ key: 'or', comment: ['Here is the context where it is used - Sign in with your A or B or C account to synchronize your data across devices.'] }, "or");
442 443 444
				const displayName = this.userDataSyncWorkbenchService.authenticationProviders.length === 1
					? this.authenticationService.getDisplayName(this.userDataSyncWorkbenchService.authenticationProviders[0].id)
					: this.userDataSyncWorkbenchService.authenticationProviders.map(({ id }) => this.authenticationService.getDisplayName(id)).join(` ${orTerm} `);
S
Sandeep Somavarapu 已提交
445
				quickPick.description = localize('sign in and turn on sync detail', "Sign in with your {0} account to synchronize your data across devices.", displayName);
446 447 448 449 450 451 452
				quickPick.customLabel = localize('sign in and turn on sync', "Sign in & Turn on");
			}
			quickPick.placeholder = localize('configure sync placeholder', "Choose what to sync");
			quickPick.canSelectMany = true;
			quickPick.ignoreFocusOut = true;
			const items = this.getConfigureSyncQuickPickItems();
			quickPick.items = items;
S
Sandeep Somavarapu 已提交
453
			quickPick.selectedItems = items.filter(item => this.userDataSyncEnablementService.isResourceEnabled(item.id));
S
Sandeep Somavarapu 已提交
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
			let accepted: boolean = false;
			disposables.add(Event.any(quickPick.onDidAccept, quickPick.onDidCustom)(() => {
				accepted = true;
				quickPick.hide();
			}));
			disposables.add(quickPick.onDidHide(() => {
				try {
					if (accepted) {
						this.updateConfiguration(items, quickPick.selectedItems);
					}
					c(accepted);
				} catch (error) {
					e(error);
				} finally {
					disposables.dispose();
469 470 471 472 473 474 475 476
				}
			}));
			quickPick.show();
		});
	}

	private getConfigureSyncQuickPickItems(): ConfigureSyncQuickPickItem[] {
		return [{
S
Sandeep Somavarapu 已提交
477 478
			id: SyncResource.Settings,
			label: getSyncAreaLabel(SyncResource.Settings)
479
		}, {
S
Sandeep Somavarapu 已提交
480 481
			id: SyncResource.Keybindings,
			label: getSyncAreaLabel(SyncResource.Keybindings)
482 483 484
		}, {
			id: SyncResource.Snippets,
			label: getSyncAreaLabel(SyncResource.Snippets)
485
		}, {
S
Sandeep Somavarapu 已提交
486 487
			id: SyncResource.Extensions,
			label: getSyncAreaLabel(SyncResource.Extensions)
488
		}, {
S
Sandeep Somavarapu 已提交
489 490
			id: SyncResource.GlobalState,
			label: getSyncAreaLabel(SyncResource.GlobalState),
491 492 493
		}];
	}

S
Sandeep Somavarapu 已提交
494
	private updateConfiguration(items: ConfigureSyncQuickPickItem[], selectedItems: ReadonlyArray<ConfigureSyncQuickPickItem>): void {
495
		for (const item of items) {
S
Sandeep Somavarapu 已提交
496
			const wasEnabled = this.userDataSyncEnablementService.isResourceEnabled(item.id);
497 498
			const isEnabled = !!selectedItems.filter(selected => selected.id === item.id)[0];
			if (wasEnabled !== isEnabled) {
S
Sandeep Somavarapu 已提交
499
				this.userDataSyncEnablementService.setResourceEnablement(item.id!, isEnabled);
500
			}
S
Sandeep Somavarapu 已提交
501 502 503
		}
	}

S
Sandeep Somavarapu 已提交
504
	private async configureSyncOptions(): Promise<void> {
505 506
		return new Promise((c, e) => {
			const disposables: DisposableStore = new DisposableStore();
507
			const quickPick = this.quickInputService.createQuickPick<ConfigureSyncQuickPickItem>();
508
			disposables.add(quickPick);
509
			quickPick.title = localize('configure sync', "Preferences Sync: Configure...");
510
			quickPick.placeholder = localize('configure sync placeholder', "Choose what to sync");
511
			quickPick.canSelectMany = true;
S
Sandeep Somavarapu 已提交
512
			quickPick.ignoreFocusOut = true;
S
Sandeep Somavarapu 已提交
513
			quickPick.ok = true;
514
			const items = this.getConfigureSyncQuickPickItems();
515
			quickPick.items = items;
S
Sandeep Somavarapu 已提交
516
			quickPick.selectedItems = items.filter(item => this.userDataSyncEnablementService.isResourceEnabled(item.id));
517 518
			disposables.add(quickPick.onDidAccept(async () => {
				if (quickPick.selectedItems.length) {
S
Sandeep Somavarapu 已提交
519
					this.updateConfiguration(items, quickPick.selectedItems);
520
					quickPick.hide();
521
				}
S
Sandeep Somavarapu 已提交
522 523
			}));
			disposables.add(quickPick.onDidHide(() => {
524 525 526 527 528
				disposables.dispose();
				c();
			}));
			quickPick.show();
		});
529 530 531
	}

	private async turnOff(): Promise<void> {
532 533
		const result = await this.dialogService.confirm({
			type: 'info',
534
			message: localize('turn off sync confirmation', "Do you want to turn off sync?"),
S
Sandeep Somavarapu 已提交
535
			detail: localize('turn off sync detail', "Your settings, keybindings, extensions and UI State will no longer be synced."),
S
Sandeep Somavarapu 已提交
536
			primaryButton: localize('turn off', "Turn Off"),
S
Sandeep Somavarapu 已提交
537
			checkbox: {
S
Sandeep Somavarapu 已提交
538
				label: localize('turn off sync everywhere', "Turn off sync on all your devices and clear the data from the cloud.")
S
Sandeep Somavarapu 已提交
539
			}
540 541
		});
		if (result.confirmed) {
542
			return this.userDataSyncWorkbenchService.turnoff(!!result.checkboxChecked);
S
Sandeep Somavarapu 已提交
543
		}
544 545
	}

S
Sandeep Somavarapu 已提交
546
	private disableSync(source?: SyncResource): void {
S
Sandeep Somavarapu 已提交
547 548 549 550
		if (source === undefined) {
			this.userDataSyncEnablementService.setEnablement(false);
		} else {
			switch (source) {
S
Sandeep Somavarapu 已提交
551 552
				case SyncResource.Settings: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.Settings, false);
				case SyncResource.Keybindings: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.Keybindings, false);
553
				case SyncResource.Snippets: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.Snippets, false);
S
Sandeep Somavarapu 已提交
554 555
				case SyncResource.Extensions: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.Extensions, false);
				case SyncResource.GlobalState: return this.userDataSyncEnablementService.setResourceEnablement(SyncResource.GlobalState, false);
S
Sandeep Somavarapu 已提交
556
			}
S
Sandeep Somavarapu 已提交
557
		}
S
Sandeep Somavarapu 已提交
558 559
	}

560 561 562
	private getConflictsEditorInputs(syncResource: SyncResource): DiffEditorInput[] {
		return this.editorService.editors.filter(input => {
			const resource = input instanceof DiffEditorInput ? input.master.resource : input.resource;
563
			return resource && getSyncResourceFromLocalPreview(resource!, this.workbenchEnvironmentService) === syncResource;
564
		}) as DiffEditorInput[];
565 566
	}

S
Sandeep Somavarapu 已提交
567
	private getAllConflictsEditorInputs(): IEditorInput[] {
S
Sandeep Somavarapu 已提交
568
		return this.editorService.editors.filter(input => {
569
			const resource = input instanceof DiffEditorInput ? input.master.resource : input.resource;
570
			return resource && getSyncResourceFromLocalPreview(resource!, this.workbenchEnvironmentService) !== undefined;
S
Sandeep Somavarapu 已提交
571
		});
S
Sandeep Somavarapu 已提交
572 573
	}

574 575 576 577
	private async handleSyncResourceConflicts(resource: SyncResource): Promise<void> {
		const syncResourceCoflicts = this.userDataSyncService.conflicts.filter(({ syncResource }) => syncResource === resource)[0];
		if (syncResourceCoflicts) {
			this.handleConflicts(syncResourceCoflicts);
S
Sandeep Somavarapu 已提交
578
		}
579 580 581 582 583 584 585 586 587
	}

	private async handleConflicts({ syncResource, conflicts }: SyncResourceConflicts): Promise<void> {
		for (const conflict of conflicts) {
			let label: string | undefined = undefined;
			if (syncResource === SyncResource.Settings) {
				label = localize('settings conflicts preview', "Settings Conflicts (Remote ↔ Local)");
			} else if (syncResource === SyncResource.Keybindings) {
				label = localize('keybindings conflicts preview', "Keybindings Conflicts (Remote ↔ Local)");
588 589
			} else if (syncResource === SyncResource.Snippets) {
				label = localize('snippets conflicts preview', "User Snippet Conflicts (Remote ↔ Local) - {0}", basename(conflict.local));
590
			}
S
Sandeep Somavarapu 已提交
591
			await this.editorService.openEditor({
592 593
				leftResource: conflict.remote,
				rightResource: conflict.local,
S
Sandeep Somavarapu 已提交
594 595 596
				label,
				options: {
					preserveFocus: false,
S
Sandeep Somavarapu 已提交
597
					pinned: true,
S
Sandeep Somavarapu 已提交
598 599 600
					revealIfVisible: true,
				},
			});
S
Sandeep Somavarapu 已提交
601
		}
602 603
	}

604
	private showSyncActivity(): Promise<void> {
S
Sandeep Somavarapu 已提交
605 606 607
		return this.outputService.showChannel(Constants.userDataSyncLogChannelId);
	}

608
	private registerActions(): void {
S
Sandeep Somavarapu 已提交
609 610 611 612
		if (this.userDataSyncEnablementService.canToggleEnablement()) {
			this.registerTurnOnSyncAction();
			this.registerTurnOffSyncAction();
		}
S
Sandeep Somavarapu 已提交
613
		this.registerTurninOnSyncAction();
614
		this.registerSignInAction(); // When Sync is turned on from CLI
615 616
		this.registerShowSettingsConflictsAction();
		this.registerShowKeybindingsConflictsAction();
617
		this.registerShowSnippetsConflictsAction();
618

S
#93960  
Sandeep Somavarapu 已提交
619 620 621
		this.registerEnableSyncViewsAction();
		this.registerManageSyncAction();
		this.registerSyncNowAction();
622 623
		this.registerConfigureSyncAction();
		this.registerShowSettingsAction();
S
#93960  
Sandeep Somavarapu 已提交
624
		this.registerShowLogAction();
625 626 627
	}

	private registerTurnOnSyncAction(): void {
S
Sandeep Somavarapu 已提交
628
		const turnOnSyncWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT.toNegated(), CONTEXT_ACCOUNT_STATE.notEqualsTo(AccountStatus.Uninitialized), CONTEXT_TURNING_ON_STATE.negate());
629
		CommandsRegistry.registerCommand(turnOnSyncCommand.id, () => this.turnOn());
S
Sandeep Somavarapu 已提交
630
		MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
631 632
			group: '5_sync',
			command: {
633
				id: turnOnSyncCommand.id,
S
Sandeep Somavarapu 已提交
634
				title: localize('global activity turn on sync', "Turn on Preferences Sync...")
635
			},
S
Sandeep Somavarapu 已提交
636
			when: turnOnSyncWhenContext,
637
			order: 1
S
Sandeep Somavarapu 已提交
638 639
		});
		MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
640
			command: turnOnSyncCommand,
S
Sandeep Somavarapu 已提交
641 642
			when: turnOnSyncWhenContext,
		});
S
Sandeep Somavarapu 已提交
643 644 645
		MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
			group: '5_sync',
			command: {
646
				id: turnOnSyncCommand.id,
S
Sandeep Somavarapu 已提交
647
				title: localize('global activity turn on sync', "Turn on Preferences Sync...")
S
Sandeep Somavarapu 已提交
648 649 650
			},
			when: turnOnSyncWhenContext,
		});
651 652 653 654 655 656
		MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
			group: '1_sync',
			command: {
				id: turnOnSyncCommand.id,
				title: localize('global activity turn on sync', "Turn on Preferences Sync...")
			},
657
			when: turnOnSyncWhenContext
658
		});
659
	}
660

S
Sandeep Somavarapu 已提交
661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
	private registerTurninOnSyncAction(): void {
		const when = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT.toNegated(), CONTEXT_ACCOUNT_STATE.notEqualsTo(AccountStatus.Uninitialized), CONTEXT_TURNING_ON_STATE);
		this._register(registerAction2(class TurningOnSyncAction extends Action2 {
			constructor() {
				super({
					id: 'workbench.userData.actions.turningOn',
					title: localize('turnin on sync', "Turning on Preferences Sync..."),
					precondition: ContextKeyExpr.false(),
					menu: [{
						group: '5_sync',
						id: MenuId.GlobalActivity,
						when,
						order: 2
					}, {
						group: '1_sync',
						id: MenuId.AccountsContext,
						when,
					}]
				});
			}
			async run(): Promise<any> { }
		}));
	}

685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703
	private registerSignInAction(): void {
		const that = this;
		const id = 'workbench.userData.actions.signin';
		const when = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Unavailable));
		this._register(registerAction2(class StopSyncAction extends Action2 {
			constructor() {
				super({
					id: 'workbench.userData.actions.signin',
					title: localize('sign in global', "Sign in to Sync Preferences(1)"),
					menu: {
						group: '5_sync',
						id: MenuId.GlobalActivity,
						when,
						order: 2
					}
				});
			}
			async run(): Promise<any> {
				try {
704
					await that.userDataSyncWorkbenchService.pickAccount();
705 706 707 708 709 710 711 712 713 714 715 716 717 718 719
				} catch (e) {
					that.notificationService.error(e);
				}
			}
		}));
		this._register(MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
			group: '1_sync',
			command: {
				id,
				title: localize('sign in accounts', "Sign in to Sync Preferences"),
			},
			when
		}));
	}

720
	private registerShowSettingsConflictsAction(): void {
721
		const resolveSettingsConflictsWhenContext = ContextKeyExpr.regex(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*settings.*/i);
722
		CommandsRegistry.registerCommand(resolveSettingsConflictsCommand.id, () => this.handleSyncResourceConflicts(SyncResource.Settings));
S
Sandeep Somavarapu 已提交
723
		MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
724 725
			group: '5_sync',
			command: {
726
				id: resolveSettingsConflictsCommand.id,
727
				title: localize('resolveConflicts_global', "Preferences Sync: Show Settings Conflicts (1)"),
728
			},
729
			when: resolveSettingsConflictsWhenContext,
730 731 732 733 734 735
			order: 2
		});
		MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
			group: '5_sync',
			command: {
				id: resolveSettingsConflictsCommand.id,
736
				title: localize('resolveConflicts_global', "Preferences Sync: Show Settings Conflicts (1)"),
737 738 739
			},
			when: resolveSettingsConflictsWhenContext,
			order: 2
S
Sandeep Somavarapu 已提交
740 741
		});
		MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
742
			command: resolveSettingsConflictsCommand,
743 744
			when: resolveSettingsConflictsWhenContext,
		});
745
	}
746

747
	private registerShowKeybindingsConflictsAction(): void {
748
		const resolveKeybindingsConflictsWhenContext = ContextKeyExpr.regex(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*keybindings.*/i);
749
		CommandsRegistry.registerCommand(resolveKeybindingsConflictsCommand.id, () => this.handleSyncResourceConflicts(SyncResource.Keybindings));
750 751 752
		MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
			group: '5_sync',
			command: {
753
				id: resolveKeybindingsConflictsCommand.id,
754
				title: localize('resolveKeybindingsConflicts_global', "Preferences Sync: Show Keybindings Conflicts (1)"),
755 756
			},
			when: resolveKeybindingsConflictsWhenContext,
757 758 759 760 761 762
			order: 2
		});
		MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
			group: '5_sync',
			command: {
				id: resolveKeybindingsConflictsCommand.id,
763
				title: localize('resolveKeybindingsConflicts_global', "Preferences Sync: Show Keybindings Conflicts (1)"),
764 765 766
			},
			when: resolveKeybindingsConflictsWhenContext,
			order: 2
767 768
		});
		MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
769
			command: resolveKeybindingsConflictsCommand,
770
			when: resolveKeybindingsConflictsWhenContext,
S
Sandeep Somavarapu 已提交
771
		});
772
	}
773

774 775 776 777 778 779 780 781 782 783
	private _snippetsConflictsActionsDisposable: DisposableStore = new DisposableStore();
	private registerShowSnippetsConflictsAction(): void {
		this._snippetsConflictsActionsDisposable.clear();
		const resolveSnippetsConflictsWhenContext = ContextKeyExpr.regex(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*snippets.*/i);
		const conflicts: Conflict[] | undefined = this.userDataSyncService.conflicts.filter(({ syncResource }) => syncResource === SyncResource.Snippets)[0]?.conflicts;
		this._snippetsConflictsActionsDisposable.add(CommandsRegistry.registerCommand(resolveSnippetsConflictsCommand.id, () => this.handleSyncResourceConflicts(SyncResource.Snippets)));
		this._snippetsConflictsActionsDisposable.add(MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
			group: '5_sync',
			command: {
				id: resolveSnippetsConflictsCommand.id,
784
				title: localize('resolveSnippetsConflicts_global', "Preferences Sync: Show User Snippets Conflicts ({0})", conflicts?.length || 1),
785 786 787 788 789 790 791 792
			},
			when: resolveSnippetsConflictsWhenContext,
			order: 2
		}));
		this._snippetsConflictsActionsDisposable.add(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
			group: '5_sync',
			command: {
				id: resolveSnippetsConflictsCommand.id,
793
				title: localize('resolveSnippetsConflicts_global', "Preferences Sync: Show User Snippets Conflicts ({0})", conflicts?.length || 1),
794 795 796 797 798 799 800 801
			},
			when: resolveSnippetsConflictsWhenContext,
			order: 2
		}));
		this._snippetsConflictsActionsDisposable.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
			command: resolveSnippetsConflictsCommand,
			when: resolveSnippetsConflictsWhenContext,
		}));
802 803
	}

S
#93960  
Sandeep Somavarapu 已提交
804
	private registerManageSyncAction(): void {
805
		const that = this;
S
Sandeep Somavarapu 已提交
806
		const when = ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized));
807
		this._register(registerAction2(class SyncStatusAction extends Action2 {
808 809
			constructor() {
				super({
810
					id: 'workbench.userDataSync.actions.manage',
S
Sandeep Somavarapu 已提交
811
					title: localize('sync is on', "Preferences Sync is On"),
812 813 814 815 816 817
					menu: [
						{
							id: MenuId.GlobalActivity,
							group: '5_sync',
							when,
							order: 3
818 819 820 821 822 823
						},
						{
							id: MenuId.MenubarPreferencesMenu,
							group: '5_sync',
							when,
							order: 3,
824 825 826 827 828
						},
						{
							id: MenuId.AccountsContext,
							group: '1_sync',
							when,
829 830 831 832 833 834 835 836
						}
					],
				});
			}
			run(accessor: ServicesAccessor): any {
				return new Promise((c, e) => {
					const quickInputService = accessor.get(IQuickInputService);
					const commandService = accessor.get(ICommandService);
S
Sandeep Somavarapu 已提交
837
					const disposables = new DisposableStore();
838
					const quickPick = quickInputService.createQuickPick();
S
Sandeep Somavarapu 已提交
839
					disposables.add(quickPick);
840
					const items: Array<IQuickPickItem | IQuickPickSeparator> = [];
841 842 843
					if (that.userDataSyncService.conflicts.length) {
						for (const { syncResource } of that.userDataSyncService.conflicts) {
							switch (syncResource) {
S
Sandeep Somavarapu 已提交
844
								case SyncResource.Settings:
845 846
									items.push({ id: resolveSettingsConflictsCommand.id, label: resolveSettingsConflictsCommand.title });
									break;
S
Sandeep Somavarapu 已提交
847
								case SyncResource.Keybindings:
848 849
									items.push({ id: resolveKeybindingsConflictsCommand.id, label: resolveKeybindingsConflictsCommand.title });
									break;
850 851 852
								case SyncResource.Snippets:
									items.push({ id: resolveSnippetsConflictsCommand.id, label: resolveSnippetsConflictsCommand.title });
									break;
853 854 855 856 857 858
							}
						}
						items.push({ type: 'separator' });
					}
					items.push({ id: configureSyncCommand.id, label: configureSyncCommand.title });
					items.push({ type: 'separator' });
S
Sandeep Somavarapu 已提交
859
					items.push({ id: syncNowCommand.id, label: syncNowCommand.title, description: syncNowCommand.description(that.userDataSyncService) });
S
Sandeep Somavarapu 已提交
860
					if (that.userDataSyncEnablementService.canToggleEnablement()) {
861
						const account = that.userDataSyncWorkbenchService.current;
862
						items.push({ id: turnOffSyncCommand.id, label: turnOffSyncCommand.title, description: account ? `${account.accountName} (${that.authenticationService.getDisplayName(account.authenticationProviderId)})` : undefined });
S
Sandeep Somavarapu 已提交
863
					}
864 865 866
					quickPick.items = items;
					disposables.add(quickPick.onDidAccept(() => {
						if (quickPick.selectedItems[0] && quickPick.selectedItems[0].id) {
867
							commandService.executeCommand(quickPick.selectedItems[0].id);
868 869 870 871 872 873 874 875 876 877
						}
						quickPick.hide();
					}));
					disposables.add(quickPick.onDidHide(() => {
						disposables.dispose();
						c();
					}));
					quickPick.show();
				});
			}
878
		}));
879 880
	}

S
#93960  
Sandeep Somavarapu 已提交
881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897
	private registerEnableSyncViewsAction(): void {
		const that = this;
		const when = ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized));
		this._register(registerAction2(class SyncStatusAction extends Action2 {
			constructor() {
				super({
					id: ENABLE_SYNC_VIEWS_COMMAND_ID,
					title: ENABLE_SYNC_VIEWS_COMMAND_ID,
					precondition: when
				});
			}
			run(accessor: ServicesAccessor): any {
				that.viewsEnablementContext.set(true);
			}
		}));
	}

S
Sandeep Somavarapu 已提交
898 899 900 901 902 903 904
	private registerSyncNowAction(): void {
		const that = this;
		this._register(registerAction2(class SyncNowAction extends Action2 {
			constructor() {
				super({
					id: syncNowCommand.id,
					title: syncNowCommand.title,
905 906 907 908
					menu: {
						id: MenuId.CommandPalette,
						when: ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized))
					}
S
Sandeep Somavarapu 已提交
909 910
				});
			}
911 912
			run(accessor: ServicesAccessor): Promise<any> {
				accessor.get(ITelemetryService).publicLog2(`sync/actions/${syncNowCommand.id}`);
S
Sandeep Somavarapu 已提交
913 914 915 916 917
				return that.userDataSyncService.sync();
			}
		}));
	}

918 919
	private registerTurnOffSyncAction(): void {
		const that = this;
920
		this._register(registerAction2(class StopSyncAction extends Action2 {
921 922
			constructor() {
				super({
923 924
					id: turnOffSyncCommand.id,
					title: turnOffSyncCommand.title,
925 926 927 928 929 930 931 932 933 934 935 936 937 938 939
					menu: {
						id: MenuId.CommandPalette,
						when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT),
					},
				});
			}
			async run(): Promise<any> {
				try {
					await that.turnOff();
				} catch (e) {
					if (!isPromiseCanceledError(e)) {
						that.notificationService.error(localize('turn off failed', "Error while turning off sync: {0}", toErrorMessage(e)));
					}
				}
			}
940
		}));
941 942
	}

943 944
	private registerConfigureSyncAction(): void {
		const that = this;
S
#93960  
Sandeep Somavarapu 已提交
945 946
		const when = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT);
		this._register(registerAction2(class ConfigureSyncAction extends Action2 {
947 948 949 950 951 952
			constructor() {
				super({
					id: configureSyncCommand.id,
					title: configureSyncCommand.title,
					menu: {
						id: MenuId.CommandPalette,
S
#93960  
Sandeep Somavarapu 已提交
953 954
						when
					}
955 956 957
				});
			}
			run(): any { return that.configureSyncOptions(); }
958
		}));
959
	}
S
Sandeep Somavarapu 已提交
960

S
#93960  
Sandeep Somavarapu 已提交
961
	private registerShowLogAction(): void {
962
		const that = this;
963
		this._register(registerAction2(class ShowSyncActivityAction extends Action2 {
964 965
			constructor() {
				super({
S
#93960  
Sandeep Somavarapu 已提交
966 967
					id: SHOW_SYNC_LOG_COMMAND_ID,
					title: localize('show sync log title', "Preferences Sync: Show Log"),
968 969 970 971 972 973 974
					menu: {
						id: MenuId.CommandPalette,
						when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)),
					},
				});
			}
			run(): any { return that.showSyncActivity(); }
975
		}));
976
	}
S
Sandeep Somavarapu 已提交
977

978
	private registerShowSettingsAction(): void {
979
		this._register(registerAction2(class ShowSyncSettingsAction extends Action2 {
980 981 982 983 984 985 986 987 988 989 990
			constructor() {
				super({
					id: showSyncSettingsCommand.id,
					title: showSyncSettingsCommand.title,
					menu: {
						id: MenuId.CommandPalette,
						when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)),
					},
				});
			}
			run(accessor: ServicesAccessor): any {
R
Rob Lourens 已提交
991
				accessor.get(IPreferencesService).openGlobalSettings(false, { query: '@tag:sync' });
992
			}
993
		}));
994
	}
995

S
#93960  
Sandeep Somavarapu 已提交
996 997 998 999 1000 1001 1002 1003
	private registerViews(): void {
		const container = this.registerViewContainer();
		this.registerDataViews(container);
	}

	private registerViewContainer(): ViewContainer {
		const viewContainerId = 'workbench.view.sync';
		return Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry).registerViewContainer(
1004
			{
S
#93960  
Sandeep Somavarapu 已提交
1005
				id: viewContainerId,
1006 1007 1008
				name: localize('sync preferences', "Preferences Sync"),
				ctorDescriptor: new SyncDescriptor(
					UserDataSyncViewPaneContainer,
S
#93960  
Sandeep Somavarapu 已提交
1009
					[viewContainerId]
1010 1011 1012 1013 1014 1015
				),
				icon: Codicon.sync.classNames,
				hideIfEmpty: true,
			}, ViewContainerLocation.Sidebar);
	}

S
#93960  
Sandeep Somavarapu 已提交
1016 1017 1018 1019
	private registerDataViews(container: ViewContainer): void {
		this._register(this.instantiationService.createInstance(UserDataSyncDataViews, container));
	}

1020
}
S
Sandeep Somavarapu 已提交
1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031

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 {
1032 1033
		if (uri.scheme === USER_DATA_SYNC_SCHEME) {
			return this.userDataSyncService.resolveContent(uri).then(content => this.modelService.createModel(content || '', this.modeService.create('jsonc'), uri));
S
Sandeep Somavarapu 已提交
1034 1035 1036 1037 1038
		}
		return null;
	}
}

1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052
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,
		@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
1053
		@INotificationService private readonly notificationService: INotificationService,
1054 1055
		@IDialogService private readonly dialogService: IDialogService,
		@IConfigurationService private readonly configurationService: IConfigurationService,
S
Sandeep Somavarapu 已提交
1056
		@ITelemetryService private readonly telemetryService: ITelemetryService
1057 1058 1059 1060 1061 1062 1063 1064
	) {
		super();

		this.update();
		this.registerListeners();
	}

	private registerListeners(): void {
1065 1066
		this._register(this.editor.onDidChangeModel(() => this.update()));
		this._register(this.userDataSyncService.onDidChangeConflicts(() => this.update()));
1067
		this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('diffEditor.renderSideBySide'))(() => this.update()));
1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084
	}

	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
		}

1085 1086 1087 1088 1089 1090
		const syncResourceConflicts = this.getSyncResourceConflicts(model.uri);
		if (!syncResourceConflicts) {
			return false;
		}

		if (syncResourceConflicts.conflicts.some(({ local }) => isEqual(local, model.uri))) {
1091 1092 1093
			return true;
		}

1094
		if (syncResourceConflicts.conflicts.some(({ remote }) => isEqual(remote, model.uri))) {
1095 1096 1097 1098 1099 1100
			return this.configurationService.getValue<boolean>('diffEditor.renderSideBySide');
		}

		return false;
	}

1101 1102
	private createAcceptChangesWidgetRenderer(): void {
		if (!this.acceptChangesButton) {
1103 1104 1105
			const resource = this.editor.getModel()!.uri;
			const syncResourceConflicts = this.getSyncResourceConflicts(resource)!;
			const isRemote = syncResourceConflicts.conflicts.some(({ remote }) => isEqual(remote, resource));
S
Sandeep Somavarapu 已提交
1106 1107
			const acceptRemoteLabel = localize('accept remote', "Accept Remote");
			const acceptLocalLabel = localize('accept local', "Accept Local");
1108
			this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, isRemote ? acceptRemoteLabel : acceptLocalLabel, null);
1109 1110 1111
			this._register(this.acceptChangesButton.onClick(async () => {
				const model = this.editor.getModel();
				if (model) {
1112 1113
					this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: syncResourceConflicts.syncResource, action: isRemote ? 'acceptRemote' : 'acceptLocal' });
					const syncAreaLabel = getSyncAreaLabel(syncResourceConflicts.syncResource);
S
Sandeep Somavarapu 已提交
1114 1115
					const result = await this.dialogService.confirm({
						type: 'info',
1116
						title: isRemote
1117 1118
							? localize('Sync accept remote', "Preferences Sync: {0}", acceptRemoteLabel)
							: localize('Sync accept local', "Preferences Sync: {0}", acceptLocalLabel),
1119
						message: isRemote
1120 1121
							? localize('confirm replace and overwrite local', "Would you like to accept remote {0} and replace local {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase())
							: localize('confirm replace and overwrite remote', "Would you like to accept local {0} and replace remote {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()),
1122
						primaryButton: isRemote ? acceptRemoteLabel : acceptLocalLabel
S
Sandeep Somavarapu 已提交
1123 1124 1125
					});
					if (result.confirmed) {
						try {
1126
							await this.userDataSyncService.acceptConflict(model.uri, model.getValue());
S
Sandeep Somavarapu 已提交
1127
						} catch (e) {
1128
							if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.LocalPreconditionFailed) {
1129
								const syncResourceCoflicts = this.userDataSyncService.conflicts.filter(({ syncResource }) => syncResource === syncResourceConflicts.syncResource)[0];
1130
								if (syncResourceCoflicts && syncResourceCoflicts.conflicts.some(conflict => isEqual(conflict.local, model.uri) || isEqual(conflict.remote, model.uri))) {
1131 1132
									this.notificationService.warn(localize('update conflicts', "Could not resolve conflicts as there is new local version available. Please try again."));
								}
S
Sandeep Somavarapu 已提交
1133 1134 1135
							} else {
								this.notificationService.error(e);
							}
1136
						}
S
Sandeep Somavarapu 已提交
1137
					}
1138 1139 1140 1141 1142 1143 1144
				}
			}));

			this.acceptChangesButton.render();
		}
	}

1145 1146 1147 1148
	private getSyncResourceConflicts(resource: URI): SyncResourceConflicts | undefined {
		return this.userDataSyncService.conflicts.filter(({ conflicts }) => conflicts.some(({ local, remote }) => isEqual(local, resource) || isEqual(remote, resource)))[0];
	}

1149 1150 1151 1152 1153 1154 1155 1156 1157 1158
	private disposeAcceptChangesWidgetRenderer(): void {
		dispose(this.acceptChangesButton);
		this.acceptChangesButton = undefined;
	}

	dispose(): void {
		this.disposeAcceptChangesWidgetRenderer();
		super.dispose();
	}
}