diff --git a/src/vs/workbench/contrib/userData/browser/media/sync-push-dark.svg b/src/vs/workbench/contrib/userData/browser/media/sync-push-dark.svg
new file mode 100644
index 0000000000000..72695bb2e5b13
--- /dev/null
+++ b/src/vs/workbench/contrib/userData/browser/media/sync-push-dark.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/vs/workbench/contrib/userData/browser/media/sync-push-light.svg b/src/vs/workbench/contrib/userData/browser/media/sync-push-light.svg
new file mode 100644
index 0000000000000..82cbddecbc01c
--- /dev/null
+++ b/src/vs/workbench/contrib/userData/browser/media/sync-push-light.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/vs/workbench/contrib/userData/browser/userData.contribution.ts b/src/vs/workbench/contrib/userData/browser/userData.contribution.ts
index c0c80c1f4ea27..8bd7f3cf0212b 100644
--- a/src/vs/workbench/contrib/userData/browser/userData.contribution.ts
+++ b/src/vs/workbench/contrib/userData/browser/userData.contribution.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
-import { IUserDataSyncService, SyncStatus } from 'vs/workbench/services/userData/common/userData';
+import { IUserDataSyncService, SyncStatus, USER_DATA_PREVIEW_SCHEME } from 'vs/workbench/services/userData/common/userData';
import { localize } from 'vs/nls';
import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
@@ -18,9 +18,12 @@ import { FalseContext } from 'vs/platform/contextkey/common/contextkeys';
import { IActivityService, IBadge, NumberBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity';
import { timeout } from 'vs/base/common/async';
-import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
-import { AcceptChangesController } from 'vs/workbench/contrib/userData/browser/userDataPreviewEditorContribution';
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';
const CONTEXT_SYNC_STATE = new RawContextKey('syncStatus', SyncStatus.Uninitialized);
@@ -71,6 +74,8 @@ class AutoSyncUserData extends Disposable implements IWorkbenchContribution {
}
+const SYNC_PUSH_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userData/browser/media/sync-push-light.svg`));
+const SYNC_PUSH_DARK_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userData/browser/media/sync-push-dark.svg`));
class SyncContribution extends Disposable implements IWorkbenchContribution {
private readonly syncEnablementContext: IContextKey;
@@ -82,7 +87,9 @@ class SyncContribution extends Disposable implements IWorkbenchContribution {
@IContextKeyService contextKeyService: IContextKeyService,
@IActivityService private readonly activityService: IActivityService,
@INotificationService private readonly notificationService: INotificationService,
- @IConfigurationService private readonly configurationService: IConfigurationService
+ @IConfigurationService private readonly configurationService: IConfigurationService,
+ @IEditorService private readonly editorService: IEditorService,
+ @ITextFileService private readonly textFileService: ITextFileService,
) {
super();
this.syncEnablementContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService);
@@ -139,6 +146,30 @@ class SyncContribution extends Disposable implements IWorkbenchContribution {
this.configurationService.updateValue('userConfiguration.autoSync', false);
return this.userDataSyncService.stopSync();
}
+
+ private async continueSync(): Promise {
+ // Get the preview editor
+ const editorInput = this.editorService.editors.filter(input => {
+ const resource = input.getResource();
+ return resource && resource.scheme === USER_DATA_PREVIEW_SCHEME;
+ })[0];
+ // Save the preview
+ if (editorInput && editorInput.isDirty()) {
+ await this.textFileService.save(editorInput.getResource()!);
+ }
+ try {
+ // Continue Sync
+ await this.userDataSyncService.continueSync();
+ } catch (error) {
+ this.notificationService.error(error);
+ return;
+ }
+ // Close the preview editor
+ if (editorInput) {
+ editorInput.dispose();
+ }
+ }
+
private registerActions(): void {
const startSyncMenuItem: IMenuItem = {
@@ -187,6 +218,29 @@ class SyncContribution extends Disposable implements IWorkbenchContribution {
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, resolveConflictsMenuItem);
MenuRegistry.appendMenuItem(MenuId.CommandPalette, resolveConflictsMenuItem);
+ const continueSyncCommandId = 'workbench.userData.actions.continueSync';
+ CommandsRegistry.registerCommand(continueSyncCommandId, () => this.continueSync());
+ MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
+ command: {
+ id: continueSyncCommandId,
+ title: localize('continue sync', "Sync: Continue")
+ },
+ when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts)),
+ });
+ MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
+ command: {
+ id: continueSyncCommandId,
+ title: localize('continue sync', "Sync: Continue"),
+ iconLocation: {
+ 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.Scheme.isEqualTo(USER_DATA_PREVIEW_SCHEME)),
+ });
+
CommandsRegistry.registerCommand('sync.synchronising', () => { });
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '5_sync',
@@ -213,5 +267,3 @@ class SyncContribution extends Disposable implements IWorkbenchContribution {
const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(SyncContribution, LifecyclePhase.Starting);
workbenchRegistry.registerWorkbenchContribution(AutoSyncUserData, LifecyclePhase.Eventually);
-
-registerEditorContribution(AcceptChangesController);
diff --git a/src/vs/workbench/contrib/userData/browser/userDataPreviewEditorContribution.ts b/src/vs/workbench/contrib/userData/browser/userDataPreviewEditorContribution.ts
deleted file mode 100644
index 5ef756c569181..0000000000000
--- a/src/vs/workbench/contrib/userData/browser/userDataPreviewEditorContribution.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
-import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import * as editorCommon from 'vs/editor/common/editorCommon';
-import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
-import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets';
-import { localize } from 'vs/nls';
-import { IUserDataSyncService, SETTINGS_PREVIEW_RESOURCE } from 'vs/workbench/services/userData/common/userData';
-import { isEqual } from 'vs/base/common/resources';
-import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
-import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
-import { INotificationService } from 'vs/platform/notification/common/notification';
-import { EditorOption } from 'vs/editor/common/config/editorOptions';
-
-export class AcceptChangesController extends Disposable implements editorCommon.IEditorContribution {
-
- private static readonly ID = 'editor.contrib.sync.acceptChanges';
-
- static get(editor: ICodeEditor): AcceptChangesController {
- return editor.getContribution(AcceptChangesController.ID);
- }
-
- private readonly acceptChangesWidgetRenderer: MutableDisposable;
-
- constructor(
- private editor: ICodeEditor,
- @IInstantiationService private readonly instantiationService: IInstantiationService
- ) {
- super();
-
- this.acceptChangesWidgetRenderer = this._register(new MutableDisposable());
- this._register(this.editor.onDidChangeModel(() => this.update()));
- this.update();
- }
-
- getId(): string {
- return AcceptChangesController.ID;
- }
-
- private update(): void {
- if (this.isInterestingEditorModel()) {
- if (!this.acceptChangesWidgetRenderer.value) {
- this.acceptChangesWidgetRenderer.value = this.instantiationService.createInstance(AcceptChangesWidgetRenderer, this.editor);
- }
- } else {
- this.acceptChangesWidgetRenderer.clear();
- }
- }
-
- private isInterestingEditorModel(): boolean {
- const model = this.editor.getModel();
- if (!model) {
- return false;
- }
- return isEqual(model.uri, SETTINGS_PREVIEW_RESOURCE, false);
- }
-}
-
-export class AcceptChangesWidgetRenderer extends Disposable {
-
- constructor(
- private readonly editor: ICodeEditor,
- @IInstantiationService instantiationService: IInstantiationService,
- @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
- @IEditorService private readonly editorService: IEditorService,
- @ITextFileService private readonly textFileService: ITextFileService,
- @INotificationService private readonly notificationService: INotificationService
- ) {
- super();
-
- const floatingClickWidget = this._register(instantiationService.createInstance(FloatingClickWidget, editor, localize('Accept', "Accept & Sync"), null));
- this._register(floatingClickWidget.onClick(() => this.acceptChanges()));
- floatingClickWidget.render();
- }
-
- private async acceptChanges(): Promise {
- // Do not accept if editor is readonly
- if (this.editor.getOption(EditorOption.readOnly)) {
- return;
- }
-
- const model = this.editor.getModel();
- if (model) {
- // Disable updating
- this.editor.updateOptions({ readOnly: true });
- // Save the preview
- await this.textFileService.save(model.uri);
-
- try {
- // Apply Preview
- await this.userDataSyncService.apply(model.uri);
- } catch (error) {
- this.notificationService.error(error);
- // Enable updating
- this.editor.updateOptions({ readOnly: false });
- return;
- }
-
- // Close all preview editors
- const editorInputs = this.editorService.editors.filter(input => isEqual(input.getResource(), model.uri));
- for (const input of editorInputs) {
- input.dispose();
- }
- }
-
- }
-}
diff --git a/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts b/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts
index 940794852e208..95495464e6dcd 100644
--- a/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts
+++ b/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts
@@ -29,7 +29,7 @@ export class TextResourcePropertiesService implements ITextResourcePropertiesSer
remoteAgentService.getEnvironment().then(remoteEnv => this.remoteEnvironment = remoteEnv);
}
- getEOL(resource: URI, language?: string): string {
+ getEOL(resource?: URI, language?: string): string {
const filesConfiguration = this.configurationService.getValue<{ eol: string }>('files', { overrideIdentifier: language, resource });
if (filesConfiguration && filesConfiguration.eol && filesConfiguration.eol !== 'auto') {
return filesConfiguration.eol;
@@ -38,12 +38,12 @@ export class TextResourcePropertiesService implements ITextResourcePropertiesSer
return os === OperatingSystem.Linux || os === OperatingSystem.Macintosh ? '\n' : '\r\n';
}
- private getOS(resource: URI): OperatingSystem {
+ private getOS(resource?: URI): OperatingSystem {
let os = OS;
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
if (remoteAuthority) {
- if (resource.scheme !== Schemas.file) {
+ if (resource && resource.scheme !== Schemas.file) {
const osCacheKey = `resource.authority.os.${remoteAuthority}`;
os = this.remoteEnvironment ? this.remoteEnvironment.os : /* Get it from cache */ this.storageService.getNumber(osCacheKey, StorageScope.WORKSPACE, OS);
this.storageService.store(osCacheKey, os, StorageScope.WORKSPACE);
diff --git a/src/vs/workbench/services/userData/common/settingsSync.ts b/src/vs/workbench/services/userData/common/settingsSync.ts
index 7cce436e7ef3f..7ab74c4e7d1c5 100644
--- a/src/vs/workbench/services/userData/common/settingsSync.ts
+++ b/src/vs/workbench/services/userData/common/settingsSync.ts
@@ -11,7 +11,6 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
import { IRemoteUserDataService, IUserData, RemoteUserDataError, RemoteUserDataErrorCode, ISynchroniser, SyncStatus, SETTINGS_PREVIEW_RESOURCE } from 'vs/workbench/services/userData/common/userData';
import { VSBuffer } from 'vs/base/common/buffer';
import { parse, findNodeAtLocation, parseTree, ParseError } from 'vs/base/common/json';
-import { URI } from 'vs/base/common/uri';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
@@ -25,7 +24,6 @@ import { Emitter, Event } from 'vs/base/common/event';
import { ILogService } from 'vs/platform/log/common/log';
import { Position } from 'vs/editor/common/core/position';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
-import { isEqual } from 'vs/base/common/resources';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
interface ISyncPreviewResult {
@@ -83,7 +81,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
this.setStatus(SyncStatus.HasConflicts);
return false;
}
- await this.apply(SETTINGS_PREVIEW_RESOURCE);
+ await this.apply();
return true;
} catch (e) {
this.syncPreviewResultPromise = null;
@@ -129,35 +127,40 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
return true;
}
- async apply(previewResource: URI): Promise {
- if (!isEqual(previewResource, SETTINGS_PREVIEW_RESOURCE, false)) {
+ async continueSync(): Promise {
+ if (this.status !== SyncStatus.HasConflicts) {
return false;
}
- if (this.syncPreviewResultPromise) {
- const result = await this.syncPreviewResultPromise;
- let remoteUserData = result.remoteUserData;
- const settingsPreivew = await this.fileService.readFile(SETTINGS_PREVIEW_RESOURCE);
- const content = settingsPreivew.value.toString();
-
- const parseErrors: ParseError[] = [];
- parse(content, parseErrors);
- if (parseErrors.length > 0) {
- return Promise.reject(localize('errorInvalidSettings', "Unable to sync settings. Please fix errors/warnings in it and try again."));
- }
- if (result.hasRemoteChanged) {
- const ref = await this.writeToRemote(content, remoteUserData ? remoteUserData.ref : null);
- remoteUserData = { ref, content };
- }
- if (result.hasLocalChanged) {
- await this.writeToLocal(content, result.fileContent);
- }
- if (remoteUserData) {
- this.updateLastSyncValue(remoteUserData);
- }
+ await this.apply();
+ return true;
+ }
+
+ private async apply(): Promise {
+ if (!this.syncPreviewResultPromise) {
+ return;
+ }
+ const result = await this.syncPreviewResultPromise;
+ let remoteUserData = result.remoteUserData;
+ const settingsPreivew = await this.fileService.readFile(SETTINGS_PREVIEW_RESOURCE);
+ const content = settingsPreivew.value.toString();
+
+ const parseErrors: ParseError[] = [];
+ parse(content, parseErrors);
+ if (parseErrors.length > 0) {
+ return Promise.reject(localize('errorInvalidSettings', "Unable to sync settings. Please resolve conflicts without any errors/warnings and try again."));
+ }
+ if (result.hasRemoteChanged) {
+ const ref = await this.writeToRemote(content, remoteUserData ? remoteUserData.ref : null);
+ remoteUserData = { ref, content };
+ }
+ if (result.hasLocalChanged) {
+ await this.writeToLocal(content, result.fileContent);
+ }
+ if (remoteUserData) {
+ this.updateLastSyncValue(remoteUserData);
}
this.syncPreviewResultPromise = null;
this.setStatus(SyncStatus.Idle);
- return true;
}
private getPreview(): Promise {
@@ -225,8 +228,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
// Remote has moved forward
if (remoteUserData.ref !== lastSyncData.ref) {
this.logService.trace('Settings Sync: Remote contents have changed. Merge and Sync.');
- hasRemoteChanged = true;
- hasLocalChanged = lastSyncData.content !== localContent;
+ hasLocalChanged = true;
+ hasRemoteChanged = lastSyncData.content !== localContent;
const mergeResult = await this.mergeContents(localContent, remoteContent, lastSyncData.content);
return { settingsPreview: mergeResult.settingsPreview, hasLocalChanged, hasRemoteChanged, hasConflicts: mergeResult.hasConflicts };
}
@@ -278,8 +281,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
const base = lastSyncedContent ? parse(lastSyncedContent) : null;
const settingsPreviewModel = this.modelService.createModel(localContent, this.modeService.create('jsonc'));
- const baseToLocal = base ? this.compare(base, local) : { added: new Set(), removed: new Set(), updated: new Set() };
- const baseToRemote = base ? this.compare(base, remote) : { added: new Set(), removed: new Set(), updated: new Set() };
+ const baseToLocal = base ? this.compare(base, local) : { added: Object.keys(local).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() };
+ const baseToRemote = base ? this.compare(base, remote) : { added: Object.keys(remote).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() };
const localToRemote = this.compare(local, remote);
const conflicts: Set = new Set();
diff --git a/src/vs/workbench/services/userData/common/userData.ts b/src/vs/workbench/services/userData/common/userData.ts
index 470d2b4386210..b7969cf03729d 100644
--- a/src/vs/workbench/services/userData/common/userData.ts
+++ b/src/vs/workbench/services/userData/common/userData.ts
@@ -101,9 +101,9 @@ export interface ISynchroniser {
readonly status: SyncStatus;
readonly onDidChangeStatus: Event;
sync(): Promise;
+ continueSync(): Promise;
stopSync(): Promise;
handleConflicts(): boolean;
- apply(previewResource: URI): Promise;
}
export const IUserDataSyncService = createDecorator('IUserDataSyncService');
diff --git a/src/vs/workbench/services/userData/common/userDataSyncService.ts b/src/vs/workbench/services/userData/common/userDataSyncService.ts
index 8fb5a98fa3ef4..e034d2a45f9d4 100644
--- a/src/vs/workbench/services/userData/common/userDataSyncService.ts
+++ b/src/vs/workbench/services/userData/common/userDataSyncService.ts
@@ -9,7 +9,6 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { SettingsSynchroniser } from 'vs/workbench/services/userData/common/settingsSync';
import { Emitter, Event } from 'vs/base/common/event';
-import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
import { InMemoryFileSystemProvider } from 'vs/workbench/services/userData/common/inMemoryUserDataProvider';
@@ -59,12 +58,12 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
}
}
- async apply(previewResource: URI): Promise {
+ async continueSync(): Promise {
if (!this.remoteUserDataService.isEnabled()) {
throw new Error('Not enabled');
}
for (const synchroniser of this.synchronisers) {
- if (await synchroniser.apply(previewResource)) {
+ if (await synchroniser.continueSync()) {
return true;
}
}