mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from vscode 1b314ab317fbff7d799b21754326b7d849889ceb
This commit is contained in:
@@ -0,0 +1,452 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IViewsRegistry, Extensions, ITreeViewDescriptor, ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState, TreeViewItemHandleArg, ViewContainer, ITreeView } from 'vs/workbench/common/views';
|
||||
import { localize } from 'vs/nls';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { TreeViewPane } from 'vs/workbench/browser/parts/views/treeView';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IUserDataSyncService, Change } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { FileThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { IUserDataSyncWorkbenchService, getSyncAreaLabel, CONTEXT_ENABLE_MANUAL_SYNC_VIEW, IUserDataSyncPreview, IUserDataSyncResourceGroup, MANUAL_SYNC_VIEW_ID } from 'vs/workbench/services/userDataSync/common/userDataSync';
|
||||
import { TreeView } from 'vs/workbench/contrib/views/browser/treeView';
|
||||
import { isEqual, basename } from 'vs/base/common/resources';
|
||||
import { IDecorationsProvider, IDecorationData, IDecorationsService } from 'vs/workbench/services/decorations/browser/decorations';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
|
||||
const viewName = localize('manual sync', "Manual Sync");
|
||||
|
||||
export class UserDataManualSyncView extends Disposable {
|
||||
|
||||
private readonly treeView: ITreeView;
|
||||
private userDataSyncPreview: IUserDataSyncPreview;
|
||||
|
||||
constructor(
|
||||
container: ViewContainer,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IProgressService private readonly progressService: IProgressService,
|
||||
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
|
||||
@IUserDataSyncWorkbenchService userDataSyncWorkbenchService: IUserDataSyncWorkbenchService,
|
||||
@IDecorationsService decorationsService: IDecorationsService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.userDataSyncPreview = userDataSyncWorkbenchService.userDataSyncPreview;
|
||||
this.treeView = this.createTreeView();
|
||||
this.registerManualSyncView(container);
|
||||
this.registerActions();
|
||||
|
||||
decorationsService.registerDecorationsProvider(this._register(new UserDataSyncResourcesDecorationProvider(this.userDataSyncPreview)));
|
||||
}
|
||||
|
||||
private createTreeView(): ITreeView {
|
||||
const treeView = this.instantiationService.createInstance(TreeView, MANUAL_SYNC_VIEW_ID, viewName);
|
||||
|
||||
this._register(Event.any(
|
||||
this.userDataSyncPreview.onDidChangeChanges,
|
||||
this.userDataSyncPreview.onDidChangeConflicts
|
||||
)(() => treeView.refresh()));
|
||||
|
||||
const disposable = treeView.onDidChangeVisibility(visible => {
|
||||
if (visible && !treeView.dataProvider) {
|
||||
disposable.dispose();
|
||||
treeView.dataProvider = new ManualSyncViewDataProvider(this.userDataSyncPreview);
|
||||
}
|
||||
});
|
||||
|
||||
return treeView;
|
||||
}
|
||||
|
||||
private registerManualSyncView(container: ViewContainer): void {
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
|
||||
viewsRegistry.registerViews([<ITreeViewDescriptor>{
|
||||
id: MANUAL_SYNC_VIEW_ID,
|
||||
name: viewName,
|
||||
ctorDescriptor: new SyncDescriptor(TreeViewPane),
|
||||
when: CONTEXT_ENABLE_MANUAL_SYNC_VIEW,
|
||||
canToggleVisibility: false,
|
||||
canMoveView: false,
|
||||
treeView: this.treeView,
|
||||
collapsed: false,
|
||||
order: 100,
|
||||
}], container);
|
||||
}
|
||||
|
||||
private registerActions(): void {
|
||||
const localActionOrder = 1;
|
||||
const remoteActionOrder = 1;
|
||||
const mergeActionOrder = 1;
|
||||
const that = this;
|
||||
|
||||
/* accept all local */
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.acceptLocalAll`,
|
||||
title: localize('workbench.actions.sync.acceptLocalAll', "Accept Local"),
|
||||
icon: Codicon.cloudUpload,
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID)),
|
||||
group: 'navigation',
|
||||
order: localActionOrder,
|
||||
},
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor): Promise<void> {
|
||||
return that.push();
|
||||
}
|
||||
});
|
||||
|
||||
/* accept all remote */
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.acceptRemoteAll`,
|
||||
title: localize('workbench.actions.sync.acceptRemoteAll', "Accept Remote"),
|
||||
icon: Codicon.cloudDownload,
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID)),
|
||||
group: 'navigation',
|
||||
order: remoteActionOrder,
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
return that.pull();
|
||||
}
|
||||
});
|
||||
|
||||
/* merge all */
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.mergeAll`,
|
||||
title: localize('workbench.actions.sync.mergeAll', "Merge"),
|
||||
icon: Codicon.sync,
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID)),
|
||||
group: 'navigation',
|
||||
order: mergeActionOrder,
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
return that.merge();
|
||||
}
|
||||
});
|
||||
|
||||
/* accept local change */
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.acceptLocal`,
|
||||
title: localize('workbench.actions.sync.acceptLocal', "Accept Local"),
|
||||
icon: Codicon.cloudUpload,
|
||||
menu: {
|
||||
id: MenuId.ViewItemContext,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.regex('viewItem', /sync-resource-modified-.*/i)),
|
||||
group: 'inline',
|
||||
order: localActionOrder,
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
return that.acceptLocal(ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle));
|
||||
}
|
||||
});
|
||||
|
||||
/* accept remote change */
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.acceptRemote`,
|
||||
title: localize('workbench.actions.sync.acceptRemote', "Accept Remote"),
|
||||
icon: Codicon.cloudDownload,
|
||||
menu: {
|
||||
id: MenuId.ViewItemContext,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.regex('viewItem', /sync-resource-modified-.*/i)),
|
||||
group: 'inline',
|
||||
order: remoteActionOrder,
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
return that.acceptRemote(ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle));
|
||||
}
|
||||
});
|
||||
|
||||
/* merge */
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.merge`,
|
||||
title: localize('workbench.actions.sync.merge', "Merge"),
|
||||
icon: Codicon.sync,
|
||||
menu: {
|
||||
id: MenuId.ViewItemContext,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.equals('viewItem', 'sync-resource-modified-change')),
|
||||
group: 'inline',
|
||||
order: mergeActionOrder,
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
return that.mergeResource(ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle));
|
||||
}
|
||||
});
|
||||
|
||||
/* delete */
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.deleteLocal`,
|
||||
title: localize('workbench.actions.sync.deleteLocal', "Delete"),
|
||||
icon: Codicon.trash,
|
||||
menu: {
|
||||
id: MenuId.ViewItemContext,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.regex('viewItem', /sync-resource-(add|delete)-.*/i)),
|
||||
group: 'inline',
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
return that.deleteResource(ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle));
|
||||
}
|
||||
});
|
||||
|
||||
/* add */
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.addLocal`,
|
||||
title: localize('workbench.actions.sync.addLocal', "Add"),
|
||||
icon: Codicon.add,
|
||||
menu: {
|
||||
id: MenuId.ViewItemContext,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.regex('viewItem', /sync-resource-(add|delete)-.*/i)),
|
||||
group: 'inline',
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
return that.addResource(ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle));
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.showChanges`,
|
||||
title: localize({ key: 'workbench.actions.sync.showChanges', comment: ['This is an action title to show the changes between local and remote version of resources'] }, "Open Changes"),
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
const previewResource: IUserDataSyncResourceGroup = ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle);
|
||||
return that.showChanges(previewResource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async push(): Promise<void> {
|
||||
return this.withProgress(() => this.userDataSyncPreview.push());
|
||||
}
|
||||
|
||||
private async pull(): Promise<void> {
|
||||
return this.withProgress(() => this.userDataSyncPreview.pull());
|
||||
}
|
||||
|
||||
private async merge(): Promise<void> {
|
||||
return this.withProgress(() => this.userDataSyncPreview.merge());
|
||||
}
|
||||
|
||||
private async acceptLocal(previewResource: IUserDataSyncResourceGroup): Promise<void> {
|
||||
const isConflict = this.userDataSyncPreview.conflicts.some(({ local }) => isEqual(local, previewResource.local));
|
||||
const localResource = isConflict ? previewResource.preview : previewResource.local;
|
||||
return this.withProgress(async () => {
|
||||
const content = await this.userDataSyncService.resolveContent(localResource);
|
||||
await this.userDataSyncPreview.accept(previewResource.syncResource, localResource, content || '');
|
||||
});
|
||||
}
|
||||
|
||||
private async acceptRemote(previewResource: IUserDataSyncResourceGroup): Promise<void> {
|
||||
return this.withProgress(async () => {
|
||||
const content = await this.userDataSyncService.resolveContent(previewResource.remote);
|
||||
await this.userDataSyncPreview.accept(previewResource.syncResource, previewResource.remote, content || '');
|
||||
});
|
||||
}
|
||||
|
||||
private async mergeResource(previewResource: IUserDataSyncResourceGroup): Promise<void> {
|
||||
return this.withProgress(() => this.userDataSyncPreview.merge(previewResource.preview));
|
||||
}
|
||||
|
||||
private async deleteResource(previewResource: IUserDataSyncResourceGroup): Promise<void> {
|
||||
const resource = previewResource.remoteChange === Change.Deleted || previewResource.localChange === Change.Added ? previewResource.local : previewResource.remote;
|
||||
return this.withProgress(async () => {
|
||||
const content = await this.userDataSyncService.resolveContent(resource);
|
||||
await this.userDataSyncPreview.accept(previewResource.syncResource, resource, content || '');
|
||||
});
|
||||
}
|
||||
|
||||
private async addResource(previewResource: IUserDataSyncResourceGroup): Promise<void> {
|
||||
const resource = previewResource.remoteChange === Change.Added || previewResource.localChange === Change.Deleted ? previewResource.local : previewResource.remote;
|
||||
return this.withProgress(async () => {
|
||||
const content = await this.userDataSyncService.resolveContent(resource);
|
||||
await this.userDataSyncPreview.accept(previewResource.syncResource, resource, content || '');
|
||||
});
|
||||
}
|
||||
|
||||
private async showChanges(previewResource: IUserDataSyncResourceGroup): Promise<void> {
|
||||
const isConflict = this.userDataSyncPreview.conflicts.some(({ local }) => isEqual(local, previewResource.local));
|
||||
if (previewResource.localChange === Change.Added || previewResource.remoteChange === Change.Deleted) {
|
||||
await this.editorService.openEditor({ resource: URI.revive(previewResource.remote), label: localize({ key: 'resourceLabel', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(previewResource.remote)) });
|
||||
} else {
|
||||
const leftResource = URI.revive(previewResource.remote);
|
||||
const rightResource = isConflict ? URI.revive(previewResource.preview) : URI.revive(previewResource.local);
|
||||
const leftResourceName = localize({ key: 'leftResourceName', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(leftResource));
|
||||
const rightResourceName = localize({ key: 'rightResourceName', comment: ['local as in file in disk'] }, "{0} (Local)", basename(rightResource));
|
||||
await this.editorService.openEditor({
|
||||
leftResource,
|
||||
rightResource,
|
||||
label: localize('sideBySideLabels', "{0} ↔ {1}", leftResourceName, rightResourceName),
|
||||
options: {
|
||||
preserveFocus: true,
|
||||
revealIfVisible: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private withProgress(task: () => Promise<void>): Promise<void> {
|
||||
return this.progressService.withProgress({ location: MANUAL_SYNC_VIEW_ID, delay: 500 }, task);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ManualSyncViewDataProvider implements ITreeViewDataProvider {
|
||||
|
||||
constructor(
|
||||
private readonly userDataSyncPreview: IUserDataSyncPreview
|
||||
) {
|
||||
}
|
||||
|
||||
async getChildren(element?: ITreeItem): Promise<ITreeItem[]> {
|
||||
if (element) {
|
||||
if (element.handle === 'changes') {
|
||||
return this.getChanges();
|
||||
} else {
|
||||
return this.getConflicts();
|
||||
}
|
||||
}
|
||||
return this.getRoots();
|
||||
}
|
||||
|
||||
private getRoots(): ITreeItem[] {
|
||||
const roots: ITreeItem[] = [];
|
||||
if (this.userDataSyncPreview.changes.length) {
|
||||
roots.push({
|
||||
handle: 'changes',
|
||||
collapsibleState: TreeItemCollapsibleState.Expanded,
|
||||
label: { label: localize('changes', "Changes") },
|
||||
themeIcon: Codicon.folder,
|
||||
contextValue: 'changes'
|
||||
});
|
||||
}
|
||||
if (this.userDataSyncPreview.conflicts.length) {
|
||||
roots.push({
|
||||
handle: 'conflicts',
|
||||
collapsibleState: TreeItemCollapsibleState.Expanded,
|
||||
label: { label: localize('conflicts', "Conflicts") },
|
||||
themeIcon: Codicon.folder,
|
||||
contextValue: 'conflicts',
|
||||
});
|
||||
}
|
||||
return roots;
|
||||
}
|
||||
|
||||
private getChanges(): ITreeItem[] {
|
||||
return this.userDataSyncPreview.changes.map(change => {
|
||||
return {
|
||||
handle: JSON.stringify(change),
|
||||
resourceUri: change.remote,
|
||||
themeIcon: FileThemeIcon,
|
||||
description: getSyncAreaLabel(change.syncResource),
|
||||
contextValue: `sync-resource-${change.localChange === Change.Added ? 'add-local' : change.localChange === Change.Deleted ? 'delete-local' : change.remoteChange === Change.Added ? 'add-remote' : change.remoteChange === Change.Deleted ? 'delete-remote' : 'modified'}-change`,
|
||||
collapsibleState: TreeItemCollapsibleState.None,
|
||||
command: { id: `workbench.actions.sync.showChanges`, title: '', arguments: [<TreeViewItemHandleArg>{ $treeViewId: '', $treeItemHandle: JSON.stringify(change) }] },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private getConflicts(): ITreeItem[] {
|
||||
return this.userDataSyncPreview.conflicts.map(conflict => {
|
||||
return {
|
||||
handle: JSON.stringify(conflict),
|
||||
resourceUri: conflict.remote,
|
||||
themeIcon: FileThemeIcon,
|
||||
description: getSyncAreaLabel(conflict.syncResource),
|
||||
contextValue: `sync-resource-modified-conflict`,
|
||||
collapsibleState: TreeItemCollapsibleState.None,
|
||||
command: { id: `workbench.actions.sync.showChanges`, title: '', arguments: [<TreeViewItemHandleArg>{ $treeViewId: '', $treeItemHandle: JSON.stringify(conflict) }] },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
static toUserDataSyncResourceGroup(handle: string): IUserDataSyncResourceGroup {
|
||||
const parsed: IUserDataSyncResourceGroup = JSON.parse(handle);
|
||||
return {
|
||||
syncResource: parsed.syncResource,
|
||||
local: URI.revive(parsed.local),
|
||||
preview: URI.revive(parsed.preview),
|
||||
remote: URI.revive(parsed.remote),
|
||||
localChange: parsed.localChange,
|
||||
remoteChange: parsed.remoteChange
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class UserDataSyncResourcesDecorationProvider extends Disposable implements IDecorationsProvider {
|
||||
|
||||
readonly label: string = localize('label', "UserDataSyncResources");
|
||||
|
||||
private readonly _onDidChange = this._register(new Emitter<URI[]>());
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
constructor(private readonly userDataSyncPreview: IUserDataSyncPreview) {
|
||||
super();
|
||||
}
|
||||
|
||||
provideDecorations(resource: URI): IDecorationData | undefined {
|
||||
const changeResource = this.userDataSyncPreview.changes.find(c => isEqual(c.remote, resource)) || this.userDataSyncPreview.conflicts.find(c => isEqual(c.remote, resource));
|
||||
if (changeResource) {
|
||||
if (changeResource.localChange === Change.Modified || changeResource.remoteChange === Change.Modified) {
|
||||
return {
|
||||
letter: 'M',
|
||||
};
|
||||
}
|
||||
if (changeResource.localChange === Change.Added
|
||||
|| changeResource.localChange === Change.Deleted
|
||||
|| changeResource.remoteChange === Change.Added
|
||||
|| changeResource.remoteChange === Change.Deleted) {
|
||||
return {
|
||||
letter: 'A',
|
||||
};
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,11 @@ class UserDataSyncReportIssueContribution extends Disposable implements IWorkben
|
||||
switch (error.code) {
|
||||
case UserDataSyncErrorCode.LocalTooManyRequests:
|
||||
case UserDataSyncErrorCode.TooManyRequests:
|
||||
const operationId = error.operationId ? localize('operationId', "Operation Id: {0}", error.operationId) : undefined;
|
||||
const message = localize('too many requests', "Turned off syncing preferences on this device because it is making too many requests.");
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: localize('too many requests', "Turned off syncing preferences on this device because it is making too many requests."),
|
||||
message: operationId ? `${message} ${operationId}` : message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import {
|
||||
IUserDataAutoSyncService, IUserDataSyncService, registerConfiguration,
|
||||
SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncResourceEnablementService,
|
||||
SyncResourceConflicts, Conflict, getSyncResourceFromLocalPreview
|
||||
getSyncResourceFromLocalPreview, IResourcePreview
|
||||
} from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
@@ -53,7 +53,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { ViewContainerLocation, IViewContainersRegistry, Extensions, ViewContainer } from 'vs/workbench/common/views';
|
||||
import { UserDataSyncViewPaneContainer, UserDataSyncDataViews } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncViews';
|
||||
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, SHOW_SYNCED_DATA_COMMAND_ID } from 'vs/workbench/services/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncWorkbenchService, getSyncAreaLabel, AccountStatus, CONTEXT_SYNC_STATE, CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE, CONFIGURE_SYNC_COMMAND_ID, SHOW_SYNC_LOG_COMMAND_ID, SYNC_VIEW_CONTAINER_ID } from 'vs/workbench/services/userDataSync/common/userDataSync';
|
||||
|
||||
const CONTEXT_CONFLICTS_SOURCES = new RawContextKey<string>('conflictsSources', '');
|
||||
|
||||
@@ -75,15 +75,16 @@ const syncNowCommand = {
|
||||
title: localize('sync now', "Preferences Sync: Sync Now"),
|
||||
description(userDataSyncService: IUserDataSyncService): string | undefined {
|
||||
if (userDataSyncService.status === SyncStatus.Syncing) {
|
||||
return localize('sync is on with syncing', "syncing");
|
||||
return localize('syncing', "syncing");
|
||||
}
|
||||
if (userDataSyncService.lastSyncTime) {
|
||||
return localize('sync is on with time', "synced {0}", fromNow(userDataSyncService.lastSyncTime, true));
|
||||
return localize('synced with time', "synced {0}", fromNow(userDataSyncService.lastSyncTime, true));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
const showSyncSettingsCommand = { id: 'workbench.userDataSync.actions.settings', title: localize('sync settings', "Preferences Sync: Show Settings"), };
|
||||
const showSyncedDataCommand = { id: 'workbench.userDataSync.actions.showSyncedData', title: localize('show synced data', "Preferences Sync: Show Synced Data"), };
|
||||
|
||||
const CONTEXT_TURNING_ON_STATE = new RawContextKey<false>('userDataSyncTurningOn', false);
|
||||
|
||||
@@ -91,7 +92,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
|
||||
private readonly turningOnSyncContext: IContextKey<boolean>;
|
||||
private readonly conflictsSources: IContextKey<string>;
|
||||
private readonly viewsEnablementContext: IContextKey<boolean>;
|
||||
|
||||
private readonly globalActivityBadgeDisposable = this._register(new MutableDisposable());
|
||||
private readonly accountBadgeDisposable = this._register(new MutableDisposable());
|
||||
@@ -123,7 +123,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
|
||||
this.turningOnSyncContext = CONTEXT_TURNING_ON_STATE.bindTo(contextKeyService);
|
||||
this.conflictsSources = CONTEXT_CONFLICTS_SOURCES.bindTo(contextKeyService);
|
||||
this.viewsEnablementContext = CONTEXT_ENABLE_VIEWS.bindTo(contextKeyService);
|
||||
|
||||
if (this.userDataSyncWorkbenchService.authenticationProviders.length) {
|
||||
registerConfiguration();
|
||||
@@ -143,8 +142,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
this._register(userDataSyncService.onDidChangeConflicts(() => this.onDidChangeConflicts(this.userDataSyncService.conflicts)));
|
||||
this._register(userDataSyncService.onSyncErrors(errors => this.onSynchronizerErrors(errors)));
|
||||
this._register(userDataAutoSyncService.onError(error => this.onAutoSyncError(error)));
|
||||
this._register(userDataAutoSyncService.onTurnOnSync(() => this.turningOnSync = true));
|
||||
this._register(userDataAutoSyncService.onDidTurnOnSync(() => this.turningOnSync = false));
|
||||
|
||||
this.registerActions();
|
||||
this.registerViews();
|
||||
@@ -155,10 +152,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
|
||||
private readonly conflictsDisposables = new Map<SyncResource, IDisposable>();
|
||||
private onDidChangeConflicts(conflicts: SyncResourceConflicts[]) {
|
||||
private onDidChangeConflicts(conflicts: [SyncResource, IResourcePreview[]][]) {
|
||||
this.updateGlobalActivityBadge();
|
||||
if (conflicts.length) {
|
||||
const conflictsSources: SyncResource[] = conflicts.map(conflict => conflict.syncResource);
|
||||
const conflictsSources: SyncResource[] = conflicts.map(([syncResource]) => syncResource);
|
||||
this.conflictsSources.set(conflictsSources.join(','));
|
||||
if (conflictsSources.indexOf(SyncResource.Snippets) !== -1) {
|
||||
this.registerShowSnippetsConflictsAction();
|
||||
@@ -172,13 +169,13 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
});
|
||||
|
||||
for (const { syncResource, conflicts } of this.userDataSyncService.conflicts) {
|
||||
for (const [syncResource, conflicts] of this.userDataSyncService.conflicts) {
|
||||
const conflictsEditorInputs = this.getConflictsEditorInputs(syncResource);
|
||||
|
||||
// close stale conflicts editor previews
|
||||
if (conflictsEditorInputs.length) {
|
||||
conflictsEditorInputs.forEach(input => {
|
||||
if (!conflicts.some(({ local }) => isEqual(local, input.primary.resource))) {
|
||||
if (!conflicts.some(({ previewResource }) => isEqual(previewResource, input.primary.resource))) {
|
||||
input.dispose();
|
||||
}
|
||||
});
|
||||
@@ -207,7 +204,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
label: localize('show conflicts', "Show Conflicts"),
|
||||
run: () => {
|
||||
this.telemetryService.publicLog2<{ source: string, action?: string }, SyncConflictsClassification>('sync/showConflicts', { source: syncResource });
|
||||
this.handleConflicts({ syncResource, conflicts });
|
||||
this.handleConflicts([syncResource, conflicts]);
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -238,12 +235,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
}
|
||||
|
||||
private async acceptRemote(syncResource: SyncResource, conflicts: Conflict[]) {
|
||||
private async acceptRemote(syncResource: SyncResource, conflicts: IResourcePreview[]) {
|
||||
try {
|
||||
for (const conflict of conflicts) {
|
||||
const modelRef = await this.textModelResolverService.createModelReference(conflict.remote);
|
||||
const modelRef = await this.textModelResolverService.createModelReference(conflict.remoteResource);
|
||||
try {
|
||||
await this.userDataSyncService.acceptConflict(conflict.remote, modelRef.object.textEditorModel.getValue());
|
||||
await this.userDataSyncService.acceptPreviewContent(syncResource, conflict.remoteResource, modelRef.object.textEditorModel.getValue());
|
||||
} finally {
|
||||
modelRef.dispose();
|
||||
}
|
||||
@@ -253,12 +250,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
}
|
||||
|
||||
private async acceptLocal(syncResource: SyncResource, conflicts: Conflict[]): Promise<void> {
|
||||
private async acceptLocal(syncResource: SyncResource, conflicts: IResourcePreview[]): Promise<void> {
|
||||
try {
|
||||
for (const conflict of conflicts) {
|
||||
const modelRef = await this.textModelResolverService.createModelReference(conflict.local);
|
||||
const modelRef = await this.textModelResolverService.createModelReference(conflict.previewResource);
|
||||
try {
|
||||
await this.userDataSyncService.acceptConflict(conflict.local, modelRef.object.textEditorModel.getValue());
|
||||
await this.userDataSyncService.acceptPreviewContent(syncResource, conflict.previewResource, modelRef.object.textEditorModel.getValue());
|
||||
} finally {
|
||||
modelRef.dispose();
|
||||
}
|
||||
@@ -270,11 +267,19 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
|
||||
private onAutoSyncError(error: UserDataSyncError): void {
|
||||
switch (error.code) {
|
||||
case UserDataSyncErrorCode.TurnedOff:
|
||||
case UserDataSyncErrorCode.SessionExpired:
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Info,
|
||||
message: localize('turned off', "Preferences sync was turned off from another device."),
|
||||
message: localize('session expired', "Preferences sync was turned off because current session is expired, please sign in again to turn on sync."),
|
||||
actions: {
|
||||
primary: [new Action('turn on sync', localize('turn on sync', "Turn on Preferences Sync..."), undefined, true, () => this.turnOn())]
|
||||
}
|
||||
});
|
||||
break;
|
||||
case UserDataSyncErrorCode.TurnedOff:
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Info,
|
||||
message: localize('turned off', "Preferences sync was turned off from another device, please sign in again to turn on sync."),
|
||||
actions: {
|
||||
primary: [new Action('turn on sync', localize('turn on sync', "Turn on Preferences Sync..."), undefined, true, () => this.turnOn())]
|
||||
}
|
||||
@@ -284,25 +289,39 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
if (error.resource === SyncResource.Keybindings || error.resource === SyncResource.Settings) {
|
||||
this.disableSync(error.resource);
|
||||
const sourceArea = getSyncAreaLabel(error.resource);
|
||||
this.handleTooLargeError(error.resource, 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'));
|
||||
this.handleTooLargeError(error.resource, 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'), error);
|
||||
}
|
||||
break;
|
||||
case UserDataSyncErrorCode.Incompatible:
|
||||
case UserDataSyncErrorCode.IncompatibleLocalContent:
|
||||
case UserDataSyncErrorCode.Gone:
|
||||
case UserDataSyncErrorCode.UpgradeRequired:
|
||||
this.userDataSyncWorkbenchService.turnoff(false);
|
||||
const message = localize('error upgrade required', "Preferences sync is disabled because the current version ({0}, {1}) is not compatible with the sync service. Please update before turning on sync.", this.productService.version, this.productService.commit);
|
||||
const operationId = error.operationId ? localize('operationId', "Operation Id: {0}", error.operationId) : undefined;
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: localize('error upgrade required', "Preferences sync is disabled because the current version ({0}, {1}) is not compatible with the sync service. Please update before turning on sync.", this.productService.version, this.productService.commit),
|
||||
message: operationId ? `${message} ${operationId}` : message,
|
||||
});
|
||||
break;
|
||||
case UserDataSyncErrorCode.IncompatibleRemoteContent:
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: localize('error reset required', "Preferences sync is disabled because your data in the cloud is older than that of in the client. Please reset your data in the cloud before turning on sync."),
|
||||
actions: {
|
||||
primary: [
|
||||
new Action('reset', localize('reset', "Reset Synced Data"), undefined, true, () => this.userDataSyncWorkbenchService.resetSyncedData()),
|
||||
new Action('show synced data', localize('show synced data action', "Show Synced Data"), undefined, true, () => this.userDataSyncWorkbenchService.showSyncActivity())
|
||||
]
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private handleTooLargeError(resource: SyncResource, message: string): void {
|
||||
private handleTooLargeError(resource: SyncResource, message: string, error: UserDataSyncError): void {
|
||||
const operationId = error.operationId ? localize('operationId', "Operation Id: {0}", error.operationId) : undefined;
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message,
|
||||
message: operationId ? `${message} ${operationId}` : message,
|
||||
actions: {
|
||||
primary: [new Action('open sync file', localize('open file', "Open {0} File", getSyncAreaLabel(resource)), undefined, true,
|
||||
() => resource === SyncResource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))]
|
||||
@@ -368,7 +387,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
let priority: number | undefined = undefined;
|
||||
|
||||
if (this.userDataSyncService.conflicts.length) {
|
||||
badge = new NumberBadge(this.userDataSyncService.conflicts.reduce((result, syncResourceConflict) => { return result + syncResourceConflict.conflicts.length; }, 0), () => localize('has conflicts', "Preferences Sync: Conflicts Detected"));
|
||||
badge = new NumberBadge(this.userDataSyncService.conflicts.reduce((result, [, conflicts]) => { return result + conflicts.length; }, 0), () => localize('has conflicts', "Preferences Sync: Conflicts Detected"));
|
||||
} else if (this.turningOnSync) {
|
||||
badge = new ProgressBadge(() => localize('turning on syncing', "Turning on Preferences Sync..."));
|
||||
clazz = 'progress-badge';
|
||||
@@ -404,6 +423,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
|
||||
private async turnOn(): Promise<void> {
|
||||
this.turningOnSync = true;
|
||||
try {
|
||||
if (!this.storageService.getBoolean('sync.donotAskPreviewConfirmation', StorageScope.GLOBAL, false)) {
|
||||
if (!await this.askForConfirmation()) {
|
||||
@@ -424,21 +444,37 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
switch (e.code) {
|
||||
case UserDataSyncErrorCode.TooLarge:
|
||||
if (e.resource === SyncResource.Keybindings || e.resource === SyncResource.Settings) {
|
||||
this.handleTooLargeError(e.resource, localize('too large while starting sync', "Preferences sync cannot be turned on because size of the {0} file to sync is larger than {1}. Please open the file and reduce the size and turn on sync", getSyncAreaLabel(e.resource).toLowerCase(), '100kb'));
|
||||
this.handleTooLargeError(e.resource, localize('too large while starting sync', "Preferences sync cannot be turned on because size of the {0} file to sync is larger than {1}. Please open the file and reduce the size and turn on sync", getSyncAreaLabel(e.resource).toLowerCase(), '100kb'), e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case UserDataSyncErrorCode.Incompatible:
|
||||
case UserDataSyncErrorCode.IncompatibleLocalContent:
|
||||
case UserDataSyncErrorCode.Gone:
|
||||
case UserDataSyncErrorCode.UpgradeRequired:
|
||||
const message = localize('error upgrade required while starting sync', "Preferences sync cannot be turned on because the current version ({0}, {1}) is not compatible with the sync service. Please update before turning on sync.", this.productService.version, this.productService.commit);
|
||||
const operationId = e.operationId ? localize('operationId', "Operation Id: {0}", e.operationId) : undefined;
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: localize('error upgrade required while starting sync', "Preferences sync cannot be turned on because the current version ({0}, {1}) is not compatible with the sync service. Please update before turning on sync.", this.productService.version, this.productService.commit),
|
||||
message: operationId ? `${message} ${operationId}` : message,
|
||||
});
|
||||
return;
|
||||
case UserDataSyncErrorCode.IncompatibleRemoteContent:
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: localize('error reset required while starting sync', "Preferences sync cannot be turned on because your data in the cloud is older than that of in the client. Please reset your data in the cloud before turning on sync."),
|
||||
actions: {
|
||||
primary: [
|
||||
new Action('reset', localize('reset', "Reset Synced Data"), undefined, true, () => this.userDataSyncWorkbenchService.resetSyncedData()),
|
||||
new Action('show synced data', localize('show synced data action', "Show Synced Data"), undefined, true, () => this.userDataSyncWorkbenchService.showSyncActivity())
|
||||
]
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.notificationService.error(localize('turn on failed', "Error while starting Sync: {0}", toErrorMessage(e)));
|
||||
} finally {
|
||||
this.turningOnSync = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,8 +511,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
} else {
|
||||
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");
|
||||
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} `);
|
||||
? this.authenticationService.getLabel(this.userDataSyncWorkbenchService.authenticationProviders[0].id)
|
||||
: this.userDataSyncWorkbenchService.authenticationProviders.map(({ id }) => this.authenticationService.getLabel(id)).join(` ${orTerm} `);
|
||||
quickPick.description = localize('sign in and turn on sync detail', "Sign in with your {0} account to synchronize your data across devices.", displayName);
|
||||
quickPick.customLabel = localize('sign in and turn on sync', "Sign in & Turn on");
|
||||
}
|
||||
@@ -603,13 +639,13 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
|
||||
private async handleSyncResourceConflicts(resource: SyncResource): Promise<void> {
|
||||
const syncResourceCoflicts = this.userDataSyncService.conflicts.filter(({ syncResource }) => syncResource === resource)[0];
|
||||
const syncResourceCoflicts = this.userDataSyncService.conflicts.filter(([syncResource]) => syncResource === resource)[0];
|
||||
if (syncResourceCoflicts) {
|
||||
this.handleConflicts(syncResourceCoflicts);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleConflicts({ syncResource, conflicts }: SyncResourceConflicts): Promise<void> {
|
||||
private async handleConflicts([syncResource, conflicts]: [SyncResource, IResourcePreview[]]): Promise<void> {
|
||||
for (const conflict of conflicts) {
|
||||
let label: string | undefined = undefined;
|
||||
if (syncResource === SyncResource.Settings) {
|
||||
@@ -617,11 +653,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
} else if (syncResource === SyncResource.Keybindings) {
|
||||
label = localize('keybindings conflicts preview', "Keybindings Conflicts (Remote ↔ Local)");
|
||||
} else if (syncResource === SyncResource.Snippets) {
|
||||
label = localize('snippets conflicts preview', "User Snippet Conflicts (Remote ↔ Local) - {0}", basename(conflict.local));
|
||||
label = localize('snippets conflicts preview', "User Snippet Conflicts (Remote ↔ Local) - {0}", basename(conflict.previewResource));
|
||||
}
|
||||
await this.editorService.openEditor({
|
||||
leftResource: conflict.remote,
|
||||
rightResource: conflict.local,
|
||||
leftResource: conflict.remoteResource,
|
||||
rightResource: conflict.previewResource,
|
||||
label,
|
||||
options: {
|
||||
preserveFocus: false,
|
||||
@@ -806,7 +842,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
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;
|
||||
const conflicts: IResourcePreview[] | undefined = this.userDataSyncService.conflicts.filter(([syncResource]) => syncResource === SyncResource.Snippets)[0]?.[1];
|
||||
this._snippetsConflictsActionsDisposable.add(CommandsRegistry.registerCommand(resolveSnippetsConflictsCommand.id, () => this.handleSyncResourceConflicts(SyncResource.Snippets)));
|
||||
this._snippetsConflictsActionsDisposable.add(MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||
group: '5_sync',
|
||||
@@ -870,7 +906,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
disposables.add(quickPick);
|
||||
const items: Array<IQuickPickItem | IQuickPickSeparator> = [];
|
||||
if (that.userDataSyncService.conflicts.length) {
|
||||
for (const { syncResource } of that.userDataSyncService.conflicts) {
|
||||
for (const [syncResource] of that.userDataSyncService.conflicts) {
|
||||
switch (syncResource) {
|
||||
case SyncResource.Settings:
|
||||
items.push({ id: resolveSettingsConflictsCommand.id, label: resolveSettingsConflictsCommand.title });
|
||||
@@ -887,12 +923,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
items.push({ id: configureSyncCommand.id, label: configureSyncCommand.title });
|
||||
items.push({ id: showSyncSettingsCommand.id, label: showSyncSettingsCommand.title });
|
||||
items.push({ id: SHOW_SYNCED_DATA_COMMAND_ID, label: localize('show synced data', "Preferences Sync: Show Synced Data") });
|
||||
items.push({ id: showSyncedDataCommand.id, label: showSyncedDataCommand.title });
|
||||
items.push({ type: 'separator' });
|
||||
items.push({ id: syncNowCommand.id, label: syncNowCommand.title, description: syncNowCommand.description(that.userDataSyncService) });
|
||||
if (that.userDataAutoSyncService.canToggleEnablement()) {
|
||||
const account = that.userDataSyncWorkbenchService.current;
|
||||
items.push({ id: turnOffSyncCommand.id, label: turnOffSyncCommand.title, description: account ? `${account.accountName} (${that.authenticationService.getDisplayName(account.authenticationProviderId)})` : undefined });
|
||||
items.push({ id: turnOffSyncCommand.id, label: turnOffSyncCommand.title, description: account ? `${account.accountName} (${that.authenticationService.getLabel(account.authenticationProviderId)})` : undefined });
|
||||
}
|
||||
quickPick.items = items;
|
||||
disposables.add(quickPick.onDidAccept(() => {
|
||||
@@ -917,13 +953,18 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
this._register(registerAction2(class SyncStatusAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: ENABLE_SYNC_VIEWS_COMMAND_ID,
|
||||
title: ENABLE_SYNC_VIEWS_COMMAND_ID,
|
||||
precondition: when
|
||||
id: showSyncedDataCommand.id,
|
||||
title: { value: localize('workbench.action.showSyncRemoteBackup', "Show Synced Data"), original: `Show Synced Data` },
|
||||
category: { value: localize('sync preferences', "Preferences Sync"), original: `Preferences Sync` },
|
||||
precondition: when,
|
||||
menu: {
|
||||
id: MenuId.CommandPalette,
|
||||
when
|
||||
}
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor): any {
|
||||
that.viewsEnablementContext.set(true);
|
||||
run(accessor: ServicesAccessor): Promise<void> {
|
||||
return that.userDataSyncWorkbenchService.showSyncActivity();
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -1031,14 +1072,13 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
|
||||
private registerViewContainer(): ViewContainer {
|
||||
const viewContainerId = 'workbench.view.sync';
|
||||
return Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry).registerViewContainer(
|
||||
{
|
||||
id: viewContainerId,
|
||||
id: SYNC_VIEW_CONTAINER_ID,
|
||||
name: localize('sync preferences', "Preferences Sync"),
|
||||
ctorDescriptor: new SyncDescriptor(
|
||||
UserDataSyncViewPaneContainer,
|
||||
[viewContainerId]
|
||||
[SYNC_VIEW_CONTAINER_ID]
|
||||
),
|
||||
icon: Codicon.sync.classNames,
|
||||
hideIfEmpty: true,
|
||||
@@ -1119,11 +1159,11 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio
|
||||
return false;
|
||||
}
|
||||
|
||||
if (syncResourceConflicts.conflicts.some(({ local }) => isEqual(local, model.uri))) {
|
||||
if (syncResourceConflicts[1].some(({ previewResource }) => isEqual(previewResource, model.uri))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (syncResourceConflicts.conflicts.some(({ remote }) => isEqual(remote, model.uri))) {
|
||||
if (syncResourceConflicts[1].some(({ remoteResource }) => isEqual(remoteResource, model.uri))) {
|
||||
return this.configurationService.getValue<boolean>('diffEditor.renderSideBySide');
|
||||
}
|
||||
|
||||
@@ -1133,16 +1173,16 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio
|
||||
private createAcceptChangesWidgetRenderer(): void {
|
||||
if (!this.acceptChangesButton) {
|
||||
const resource = this.editor.getModel()!.uri;
|
||||
const syncResourceConflicts = this.getSyncResourceConflicts(resource)!;
|
||||
const isRemote = syncResourceConflicts.conflicts.some(({ remote }) => isEqual(remote, resource));
|
||||
const [syncResource, conflicts] = this.getSyncResourceConflicts(resource)!;
|
||||
const isRemote = conflicts.some(({ remoteResource }) => isEqual(remoteResource, resource));
|
||||
const acceptRemoteLabel = localize('accept remote', "Accept Remote");
|
||||
const acceptLocalLabel = localize('accept local', "Accept Local");
|
||||
this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, isRemote ? acceptRemoteLabel : acceptLocalLabel, null);
|
||||
this._register(this.acceptChangesButton.onClick(async () => {
|
||||
const model = this.editor.getModel();
|
||||
if (model) {
|
||||
this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: syncResourceConflicts.syncResource, action: isRemote ? 'acceptRemote' : 'acceptLocal' });
|
||||
const syncAreaLabel = getSyncAreaLabel(syncResourceConflicts.syncResource);
|
||||
this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: syncResource, action: isRemote ? 'acceptRemote' : 'acceptLocal' });
|
||||
const syncAreaLabel = getSyncAreaLabel(syncResource);
|
||||
const result = await this.dialogService.confirm({
|
||||
type: 'info',
|
||||
title: isRemote
|
||||
@@ -1155,11 +1195,11 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio
|
||||
});
|
||||
if (result.confirmed) {
|
||||
try {
|
||||
await this.userDataSyncService.acceptConflict(model.uri, model.getValue());
|
||||
await this.userDataSyncService.acceptPreviewContent(syncResource, model.uri, model.getValue());
|
||||
} catch (e) {
|
||||
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.LocalPreconditionFailed) {
|
||||
const syncResourceCoflicts = this.userDataSyncService.conflicts.filter(({ syncResource }) => syncResource === syncResourceConflicts.syncResource)[0];
|
||||
if (syncResourceCoflicts && syncResourceCoflicts.conflicts.some(conflict => isEqual(conflict.local, model.uri) || isEqual(conflict.remote, model.uri))) {
|
||||
const syncResourceCoflicts = this.userDataSyncService.conflicts.filter(syncResourceCoflicts => syncResourceCoflicts[0] === syncResource)[0];
|
||||
if (syncResourceCoflicts && conflicts.some(conflict => isEqual(conflict.previewResource, model.uri) || isEqual(conflict.remoteResource, model.uri))) {
|
||||
this.notificationService.warn(localize('update conflicts', "Could not resolve conflicts as there is new local version available. Please try again."));
|
||||
}
|
||||
} else {
|
||||
@@ -1174,8 +1214,8 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio
|
||||
}
|
||||
}
|
||||
|
||||
private getSyncResourceConflicts(resource: URI): SyncResourceConflicts | undefined {
|
||||
return this.userDataSyncService.conflicts.filter(({ conflicts }) => conflicts.some(({ local, remote }) => isEqual(local, resource) || isEqual(remote, resource)))[0];
|
||||
private getSyncResourceConflicts(resource: URI): [SyncResource, IResourcePreview[]] | undefined {
|
||||
return this.userDataSyncService.conflicts.filter(([, conflicts]) => conflicts.some(({ previewResource, remoteResource }) => isEqual(previewResource, resource) || isEqual(remoteResource, resource)))[0];
|
||||
}
|
||||
|
||||
private disposeAcceptChangesWidgetRenderer(): void {
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IViewsRegistry, Extensions, ITreeViewDescriptor, ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState, IViewsService, TreeViewItemHandleArg, ViewContainer, IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IViewsRegistry, Extensions, ITreeViewDescriptor, ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState, TreeViewItemHandleArg, ViewContainer, IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { localize } from 'vs/nls';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { TreeViewPane } from 'vs/workbench/browser/parts/views/treeView';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ALL_SYNC_RESOURCES, SyncResource, IUserDataSyncService, ISyncResourceHandle as IResourceHandle, SyncStatus, IUserDataSyncResourceEnablementService, IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { ALL_SYNC_RESOURCES, SyncResource, IUserDataSyncService, ISyncResourceHandle as IResourceHandle, SyncStatus, IUserDataSyncResourceEnablementService, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -30,18 +30,18 @@ import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { IUserDataSyncWorkbenchService, CONTEXT_SYNC_STATE, getSyncAreaLabel, CONTEXT_ACCOUNT_STATE, AccountStatus, CONTEXT_ENABLE_VIEWS, SHOW_SYNC_LOG_COMMAND_ID, CONFIGURE_SYNC_COMMAND_ID, ENABLE_SYNC_VIEWS_COMMAND_ID, SHOW_SYNCED_DATA_COMMAND_ID } from 'vs/workbench/services/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncWorkbenchService, CONTEXT_SYNC_STATE, getSyncAreaLabel, CONTEXT_ACCOUNT_STATE, AccountStatus, CONTEXT_ENABLE_ACTIVITY_VIEWS, SHOW_SYNC_LOG_COMMAND_ID, CONFIGURE_SYNC_COMMAND_ID } from 'vs/workbench/services/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncMachinesService, IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { TreeView } from 'vs/workbench/contrib/views/browser/treeView';
|
||||
import { flatten } from 'vs/base/common/arrays';
|
||||
import { UserDataManualSyncView } from 'vs/workbench/contrib/userDataSync/browser/userDataManualSyncView';
|
||||
|
||||
export class UserDataSyncViewPaneContainer extends ViewPaneContainer {
|
||||
|
||||
constructor(
|
||||
containerId: string,
|
||||
@IDialogService private readonly dialogService: IDialogService,
|
||||
@IUserDataSyncWorkbenchService private readonly userDataSyncWorkbenchService: IUserDataSyncWorkbenchService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@@ -67,22 +67,10 @@ export class UserDataSyncViewPaneContainer extends ViewPaneContainer {
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
return [
|
||||
new Action('workbench.actions.syncData.reset', localize('workbench.actions.syncData.reset', "Reset Synced Data"), undefined, true, () => this.reset()),
|
||||
new Action('workbench.actions.syncData.reset', localize('workbench.actions.syncData.reset', "Reset Synced Data"), undefined, true, () => this.userDataSyncWorkbenchService.resetSyncedData()),
|
||||
];
|
||||
}
|
||||
|
||||
private async reset(): Promise<void> {
|
||||
const result = await this.dialogService.confirm({
|
||||
message: localize('reset', "This will clear your synced data from the cloud and stop sync on all your devices."),
|
||||
title: localize('reset title', "Reset Synced Data"),
|
||||
type: 'info',
|
||||
primaryButton: localize('reset button', "Reset"),
|
||||
});
|
||||
if (result.confirmed) {
|
||||
await this.userDataSyncWorkbenchService.turnoff(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class UserDataSyncDataViews extends Disposable {
|
||||
@@ -98,77 +86,14 @@ export class UserDataSyncDataViews extends Disposable {
|
||||
}
|
||||
|
||||
private registerViews(container: ViewContainer): void {
|
||||
this.registerSyncedDataView(container);
|
||||
this.registerMachinesView(container);
|
||||
this._register(this.instantiationService.createInstance(UserDataManualSyncView, container));
|
||||
|
||||
this.registerActivityView(container, true);
|
||||
this.registerMachinesView(container);
|
||||
|
||||
this.registerActivityView(container, false);
|
||||
}
|
||||
|
||||
private registerSyncedDataView(container: ViewContainer): void {
|
||||
const id = `workbench.views.syncedDataView`;
|
||||
const name = localize('remote title', "Synced Data");
|
||||
const treeView = this.instantiationService.createInstance(TreeView, id, name);
|
||||
treeView.showRefreshAction = true;
|
||||
const disposable = treeView.onDidChangeVisibility(visible => {
|
||||
if (visible && !treeView.dataProvider) {
|
||||
disposable.dispose();
|
||||
treeView.dataProvider = this.instantiationService.createInstance(SyncedDataViewDataProvider);
|
||||
}
|
||||
});
|
||||
this._register(Event.any(this.userDataSyncResourceEnablementService.onDidChangeResourceEnablement, this.userDataAutoSyncService.onDidChangeEnablement)(() => treeView.refresh()));
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
|
||||
viewsRegistry.registerViews([<ITreeViewDescriptor>{
|
||||
id,
|
||||
name,
|
||||
ctorDescriptor: new SyncDescriptor(TreeViewPane),
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_ENABLE_VIEWS),
|
||||
canToggleVisibility: true,
|
||||
canMoveView: false,
|
||||
treeView,
|
||||
collapsed: false,
|
||||
order: 100,
|
||||
}], container);
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: SHOW_SYNCED_DATA_COMMAND_ID,
|
||||
title: { value: localize('workbench.action.showSyncRemoteBackup', "Show Synced Data"), original: `Show Synced Data` },
|
||||
category: { value: localize('sync preferences', "Preferences Sync"), original: `Preferences Sync` },
|
||||
menu: {
|
||||
id: MenuId.CommandPalette,
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available)),
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const viewDescriptorService = accessor.get(IViewDescriptorService);
|
||||
const viewsService = accessor.get(IViewsService);
|
||||
const commandService = accessor.get(ICommandService);
|
||||
|
||||
await commandService.executeCommand(ENABLE_SYNC_VIEWS_COMMAND_ID);
|
||||
|
||||
const viewContainer = viewDescriptorService.getViewContainerByViewId(id);
|
||||
if (viewContainer) {
|
||||
const model = viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
if (model.activeViewDescriptors.some(viewDescriptor => viewDescriptor.id === id)) {
|
||||
viewsService.openView(id, true);
|
||||
} else {
|
||||
const disposable = model.onDidChangeActiveViewDescriptors(e => {
|
||||
if (e.added.some(viewDescriptor => viewDescriptor.id === id)) {
|
||||
disposable.dispose();
|
||||
viewsService.openView(id, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.registerDataViewActions(id);
|
||||
}
|
||||
|
||||
private registerMachinesView(container: ViewContainer): void {
|
||||
const id = `workbench.views.sync.machines`;
|
||||
const name = localize('synced machines', "Synced Machines");
|
||||
@@ -187,12 +112,12 @@ export class UserDataSyncDataViews extends Disposable {
|
||||
id,
|
||||
name,
|
||||
ctorDescriptor: new SyncDescriptor(TreeViewPane),
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_ENABLE_VIEWS),
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_ENABLE_ACTIVITY_VIEWS),
|
||||
canToggleVisibility: true,
|
||||
canMoveView: false,
|
||||
treeView,
|
||||
collapsed: false,
|
||||
order: 200,
|
||||
order: 300,
|
||||
}], container);
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
@@ -255,13 +180,13 @@ export class UserDataSyncDataViews extends Disposable {
|
||||
id,
|
||||
name,
|
||||
ctorDescriptor: new SyncDescriptor(TreeViewPane),
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_ENABLE_VIEWS),
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_ENABLE_ACTIVITY_VIEWS),
|
||||
canToggleVisibility: true,
|
||||
canMoveView: false,
|
||||
treeView,
|
||||
collapsed: false,
|
||||
order: 300,
|
||||
hideByDefault: true,
|
||||
order: remote ? 200 : 400,
|
||||
hideByDefault: !remote,
|
||||
}], container);
|
||||
|
||||
this.registerDataViewActions(id);
|
||||
@@ -357,6 +282,7 @@ abstract class UserDataSyncActivityViewDataProvider implements ITreeViewDataProv
|
||||
constructor(
|
||||
@IUserDataSyncService protected readonly userDataSyncService: IUserDataSyncService,
|
||||
@IUserDataAutoSyncService protected readonly userDataAutoSyncService: IUserDataAutoSyncService,
|
||||
@IUserDataSyncWorkbenchService private readonly userDataSyncWorkbenchService: IUserDataSyncWorkbenchService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
) { }
|
||||
|
||||
@@ -370,7 +296,22 @@ abstract class UserDataSyncActivityViewDataProvider implements ITreeViewDataProv
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
this.notificationService.error(error);
|
||||
if (!(error instanceof UserDataSyncError)) {
|
||||
error = UserDataSyncError.toUserDataSyncError(error);
|
||||
}
|
||||
if (error instanceof UserDataSyncError && error.code === UserDataSyncErrorCode.IncompatibleRemoteContent) {
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: error.message,
|
||||
actions: {
|
||||
primary: [
|
||||
new Action('reset', localize('reset', "Reset Synced Data"), undefined, true, () => this.userDataSyncWorkbenchService.resetSyncedData()),
|
||||
]
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.notificationService.error(error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -402,7 +343,7 @@ abstract class UserDataSyncActivityViewDataProvider implements ITreeViewDataProv
|
||||
handle,
|
||||
collapsibleState: TreeItemCollapsibleState.None,
|
||||
resourceUri: resource,
|
||||
command: { id: `workbench.actions.sync.commpareWithLocal`, title: '', arguments: [<TreeViewItemHandleArg>{ $treeViewId: '', $treeItemHandle: handle }] },
|
||||
command: { id: `workbench.actions.sync.compareWithLocal`, title: '', arguments: [<TreeViewItemHandleArg>{ $treeViewId: '', $treeItemHandle: handle }] },
|
||||
contextValue: `sync-associatedResource-${(<SyncResourceHandleTreeItem>element).syncResourceHandle.syncResource}`
|
||||
};
|
||||
});
|
||||
@@ -436,9 +377,10 @@ class RemoteUserDataSyncActivityViewDataProvider extends UserDataSyncActivityVie
|
||||
@IUserDataSyncService userDataSyncService: IUserDataSyncService,
|
||||
@IUserDataAutoSyncService userDataAutoSyncService: IUserDataAutoSyncService,
|
||||
@IUserDataSyncMachinesService private readonly userDataSyncMachinesService: IUserDataSyncMachinesService,
|
||||
@IUserDataSyncWorkbenchService userDataSyncWorkbenchService: IUserDataSyncWorkbenchService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
) {
|
||||
super(userDataSyncService, userDataAutoSyncService, notificationService);
|
||||
super(userDataSyncService, userDataAutoSyncService, userDataSyncWorkbenchService, notificationService);
|
||||
}
|
||||
|
||||
async getChildren(element?: ITreeItem): Promise<ITreeItem[]> {
|
||||
@@ -471,14 +413,6 @@ class RemoteUserDataSyncActivityViewDataProvider extends UserDataSyncActivityVie
|
||||
}
|
||||
}
|
||||
|
||||
class SyncedDataViewDataProvider extends RemoteUserDataSyncActivityViewDataProvider {
|
||||
|
||||
protected async getResourceHandles(syncResource: SyncResource): Promise<IResourceHandle[]> {
|
||||
const resourceHandles = await this.userDataSyncService.getRemoteSyncResourceHandles(syncResource);
|
||||
return resourceHandles.length ? [resourceHandles[0]] : [];
|
||||
}
|
||||
}
|
||||
|
||||
class UserDataSyncMachinesViewDataProvider implements ITreeViewDataProvider {
|
||||
|
||||
private machinesPromise: Promise<IUserDataSyncMachine[]> | undefined;
|
||||
|
||||
Reference in New Issue
Block a user