Skip to content

Commit b59d0cf

Browse files
committed
improvements to settings sync
1 parent 0ebe7ec commit b59d0cf

8 files changed

Lines changed: 104 additions & 155 deletions

File tree

Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading

src/vs/workbench/contrib/userData/browser/userData.contribution.ts

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
7-
import { IUserDataSyncService, SyncStatus } from 'vs/workbench/services/userData/common/userData';
7+
import { IUserDataSyncService, SyncStatus, USER_DATA_PREVIEW_SCHEME } from 'vs/workbench/services/userData/common/userData';
88
import { localize } from 'vs/nls';
99
import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
1010
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
@@ -18,9 +18,12 @@ import { FalseContext } from 'vs/platform/contextkey/common/contextkeys';
1818
import { IActivityService, IBadge, NumberBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
1919
import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity';
2020
import { timeout } from 'vs/base/common/async';
21-
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
22-
import { AcceptChangesController } from 'vs/workbench/contrib/userData/browser/userDataPreviewEditorContribution';
2321
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
22+
import { URI } from 'vs/base/common/uri';
23+
import { registerAndGetAmdImageURL } from 'vs/base/common/amd';
24+
import { ResourceContextKey } from 'vs/workbench/common/resources';
25+
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
26+
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
2427

2528
const CONTEXT_SYNC_STATE = new RawContextKey<string>('syncStatus', SyncStatus.Uninitialized);
2629

@@ -71,6 +74,8 @@ class AutoSyncUserData extends Disposable implements IWorkbenchContribution {
7174

7275
}
7376

77+
const SYNC_PUSH_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userData/browser/media/sync-push-light.svg`));
78+
const SYNC_PUSH_DARK_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userData/browser/media/sync-push-dark.svg`));
7479
class SyncContribution extends Disposable implements IWorkbenchContribution {
7580

7681
private readonly syncEnablementContext: IContextKey<string>;
@@ -82,7 +87,9 @@ class SyncContribution extends Disposable implements IWorkbenchContribution {
8287
@IContextKeyService contextKeyService: IContextKeyService,
8388
@IActivityService private readonly activityService: IActivityService,
8489
@INotificationService private readonly notificationService: INotificationService,
85-
@IConfigurationService private readonly configurationService: IConfigurationService
90+
@IConfigurationService private readonly configurationService: IConfigurationService,
91+
@IEditorService private readonly editorService: IEditorService,
92+
@ITextFileService private readonly textFileService: ITextFileService,
8693
) {
8794
super();
8895
this.syncEnablementContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService);
@@ -139,6 +146,30 @@ class SyncContribution extends Disposable implements IWorkbenchContribution {
139146
this.configurationService.updateValue('userConfiguration.autoSync', false);
140147
return this.userDataSyncService.stopSync();
141148
}
149+
150+
private async continueSync(): Promise<void> {
151+
// Get the preview editor
152+
const editorInput = this.editorService.editors.filter(input => {
153+
const resource = input.getResource();
154+
return resource && resource.scheme === USER_DATA_PREVIEW_SCHEME;
155+
})[0];
156+
// Save the preview
157+
if (editorInput && editorInput.isDirty()) {
158+
await this.textFileService.save(editorInput.getResource()!);
159+
}
160+
try {
161+
// Continue Sync
162+
await this.userDataSyncService.continueSync();
163+
} catch (error) {
164+
this.notificationService.error(error);
165+
return;
166+
}
167+
// Close the preview editor
168+
if (editorInput) {
169+
editorInput.dispose();
170+
}
171+
}
172+
142173
private registerActions(): void {
143174

144175
const startSyncMenuItem: IMenuItem = {
@@ -187,6 +218,29 @@ class SyncContribution extends Disposable implements IWorkbenchContribution {
187218
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, resolveConflictsMenuItem);
188219
MenuRegistry.appendMenuItem(MenuId.CommandPalette, resolveConflictsMenuItem);
189220

221+
const continueSyncCommandId = 'workbench.userData.actions.continueSync';
222+
CommandsRegistry.registerCommand(continueSyncCommandId, () => this.continueSync());
223+
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
224+
command: {
225+
id: continueSyncCommandId,
226+
title: localize('continue sync', "Sync: Continue")
227+
},
228+
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts)),
229+
});
230+
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
231+
command: {
232+
id: continueSyncCommandId,
233+
title: localize('continue sync', "Sync: Continue"),
234+
iconLocation: {
235+
light: SYNC_PUSH_LIGHT_ICON_URI,
236+
dark: SYNC_PUSH_DARK_ICON_URI
237+
}
238+
},
239+
group: 'navigation',
240+
order: 1,
241+
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), ResourceContextKey.Scheme.isEqualTo(USER_DATA_PREVIEW_SCHEME)),
242+
});
243+
190244
CommandsRegistry.registerCommand('sync.synchronising', () => { });
191245
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
192246
group: '5_sync',
@@ -213,5 +267,3 @@ class SyncContribution extends Disposable implements IWorkbenchContribution {
213267
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
214268
workbenchRegistry.registerWorkbenchContribution(SyncContribution, LifecyclePhase.Starting);
215269
workbenchRegistry.registerWorkbenchContribution(AutoSyncUserData, LifecyclePhase.Eventually);
216-
217-
registerEditorContribution(AcceptChangesController);

src/vs/workbench/contrib/userData/browser/userDataPreviewEditorContribution.ts

Lines changed: 0 additions & 111 deletions
This file was deleted.

src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export class TextResourcePropertiesService implements ITextResourcePropertiesSer
2929
remoteAgentService.getEnvironment().then(remoteEnv => this.remoteEnvironment = remoteEnv);
3030
}
3131

32-
getEOL(resource: URI, language?: string): string {
32+
getEOL(resource?: URI, language?: string): string {
3333
const filesConfiguration = this.configurationService.getValue<{ eol: string }>('files', { overrideIdentifier: language, resource });
3434
if (filesConfiguration && filesConfiguration.eol && filesConfiguration.eol !== 'auto') {
3535
return filesConfiguration.eol;
@@ -38,12 +38,12 @@ export class TextResourcePropertiesService implements ITextResourcePropertiesSer
3838
return os === OperatingSystem.Linux || os === OperatingSystem.Macintosh ? '\n' : '\r\n';
3939
}
4040

41-
private getOS(resource: URI): OperatingSystem {
41+
private getOS(resource?: URI): OperatingSystem {
4242
let os = OS;
4343

4444
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
4545
if (remoteAuthority) {
46-
if (resource.scheme !== Schemas.file) {
46+
if (resource && resource.scheme !== Schemas.file) {
4747
const osCacheKey = `resource.authority.os.${remoteAuthority}`;
4848
os = this.remoteEnvironment ? this.remoteEnvironment.os : /* Get it from cache */ this.storageService.getNumber(osCacheKey, StorageScope.WORKSPACE, OS);
4949
this.storageService.store(osCacheKey, os, StorageScope.WORKSPACE);

src/vs/workbench/services/userData/common/settingsSync.ts

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
1111
import { IRemoteUserDataService, IUserData, RemoteUserDataError, RemoteUserDataErrorCode, ISynchroniser, SyncStatus, SETTINGS_PREVIEW_RESOURCE } from 'vs/workbench/services/userData/common/userData';
1212
import { VSBuffer } from 'vs/base/common/buffer';
1313
import { parse, findNodeAtLocation, parseTree, ParseError } from 'vs/base/common/json';
14-
import { URI } from 'vs/base/common/uri';
1514
import { ITextModel } from 'vs/editor/common/model';
1615
import { IModelService } from 'vs/editor/common/services/modelService';
1716
import { IModeService } from 'vs/editor/common/services/modeService';
@@ -25,7 +24,6 @@ import { Emitter, Event } from 'vs/base/common/event';
2524
import { ILogService } from 'vs/platform/log/common/log';
2625
import { Position } from 'vs/editor/common/core/position';
2726
import { IHistoryService } from 'vs/workbench/services/history/common/history';
28-
import { isEqual } from 'vs/base/common/resources';
2927
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
3028

3129
interface ISyncPreviewResult {
@@ -83,7 +81,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
8381
this.setStatus(SyncStatus.HasConflicts);
8482
return false;
8583
}
86-
await this.apply(SETTINGS_PREVIEW_RESOURCE);
84+
await this.apply();
8785
return true;
8886
} catch (e) {
8987
this.syncPreviewResultPromise = null;
@@ -129,35 +127,40 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
129127
return true;
130128
}
131129

132-
async apply(previewResource: URI): Promise<boolean> {
133-
if (!isEqual(previewResource, SETTINGS_PREVIEW_RESOURCE, false)) {
130+
async continueSync(): Promise<boolean> {
131+
if (this.status !== SyncStatus.HasConflicts) {
134132
return false;
135133
}
136-
if (this.syncPreviewResultPromise) {
137-
const result = await this.syncPreviewResultPromise;
138-
let remoteUserData = result.remoteUserData;
139-
const settingsPreivew = await this.fileService.readFile(SETTINGS_PREVIEW_RESOURCE);
140-
const content = settingsPreivew.value.toString();
141-
142-
const parseErrors: ParseError[] = [];
143-
parse(content, parseErrors);
144-
if (parseErrors.length > 0) {
145-
return Promise.reject(localize('errorInvalidSettings', "Unable to sync settings. Please fix errors/warnings in it and try again."));
146-
}
147-
if (result.hasRemoteChanged) {
148-
const ref = await this.writeToRemote(content, remoteUserData ? remoteUserData.ref : null);
149-
remoteUserData = { ref, content };
150-
}
151-
if (result.hasLocalChanged) {
152-
await this.writeToLocal(content, result.fileContent);
153-
}
154-
if (remoteUserData) {
155-
this.updateLastSyncValue(remoteUserData);
156-
}
134+
await this.apply();
135+
return true;
136+
}
137+
138+
private async apply(): Promise<void> {
139+
if (!this.syncPreviewResultPromise) {
140+
return;
141+
}
142+
const result = await this.syncPreviewResultPromise;
143+
let remoteUserData = result.remoteUserData;
144+
const settingsPreivew = await this.fileService.readFile(SETTINGS_PREVIEW_RESOURCE);
145+
const content = settingsPreivew.value.toString();
146+
147+
const parseErrors: ParseError[] = [];
148+
parse(content, parseErrors);
149+
if (parseErrors.length > 0) {
150+
return Promise.reject(localize('errorInvalidSettings', "Unable to sync settings. Please resolve conflicts without any errors/warnings and try again."));
151+
}
152+
if (result.hasRemoteChanged) {
153+
const ref = await this.writeToRemote(content, remoteUserData ? remoteUserData.ref : null);
154+
remoteUserData = { ref, content };
155+
}
156+
if (result.hasLocalChanged) {
157+
await this.writeToLocal(content, result.fileContent);
158+
}
159+
if (remoteUserData) {
160+
this.updateLastSyncValue(remoteUserData);
157161
}
158162
this.syncPreviewResultPromise = null;
159163
this.setStatus(SyncStatus.Idle);
160-
return true;
161164
}
162165

163166
private getPreview(): Promise<ISyncPreviewResult> {
@@ -225,8 +228,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
225228
// Remote has moved forward
226229
if (remoteUserData.ref !== lastSyncData.ref) {
227230
this.logService.trace('Settings Sync: Remote contents have changed. Merge and Sync.');
228-
hasRemoteChanged = true;
229-
hasLocalChanged = lastSyncData.content !== localContent;
231+
hasLocalChanged = true;
232+
hasRemoteChanged = lastSyncData.content !== localContent;
230233
const mergeResult = await this.mergeContents(localContent, remoteContent, lastSyncData.content);
231234
return { settingsPreview: mergeResult.settingsPreview, hasLocalChanged, hasRemoteChanged, hasConflicts: mergeResult.hasConflicts };
232235
}
@@ -278,8 +281,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
278281
const base = lastSyncedContent ? parse(lastSyncedContent) : null;
279282
const settingsPreviewModel = this.modelService.createModel(localContent, this.modeService.create('jsonc'));
280283

281-
const baseToLocal = base ? this.compare(base, local) : { added: new Set<string>(), removed: new Set<string>(), updated: new Set<string>() };
282-
const baseToRemote = base ? this.compare(base, remote) : { added: new Set<string>(), removed: new Set<string>(), updated: new Set<string>() };
284+
const baseToLocal = base ? this.compare(base, local) : { added: Object.keys(local).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
285+
const baseToRemote = base ? this.compare(base, remote) : { added: Object.keys(remote).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
283286
const localToRemote = this.compare(local, remote);
284287

285288
const conflicts: Set<string> = new Set<string>();

src/vs/workbench/services/userData/common/userData.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ export interface ISynchroniser {
101101
readonly status: SyncStatus;
102102
readonly onDidChangeStatus: Event<SyncStatus>;
103103
sync(): Promise<boolean>;
104+
continueSync(): Promise<boolean>;
104105
stopSync(): Promise<void>;
105106
handleConflicts(): boolean;
106-
apply(previewResource: URI): Promise<boolean>;
107107
}
108108

109109
export const IUserDataSyncService = createDecorator<IUserDataSyncService>('IUserDataSyncService');

0 commit comments

Comments
 (0)