mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 02:48:30 -05:00
Merge from vscode 0a7364f00514c46c9caceece15e1f82f82e3712f
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .pane > .pane-body > .manual-sync-buttons-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 13px 20px 0 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.monaco-workbench .pane > .pane-body > .manual-sync-buttons-container .monaco-button {
|
||||
margin-block-start: 13px;
|
||||
margin-inline-start: 0px;
|
||||
margin-inline-end: 0px;
|
||||
max-width: 260px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
@@ -3,174 +3,160 @@
|
||||
* 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 'vs/css!./media/userDataSyncViews';
|
||||
import { ITreeItem, TreeItemCollapsibleState, TreeViewItemHandleArg, 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 { IUserDataSyncService, Change } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncService, Change, MergeState, SyncResource } 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 { ContextKeyExpr, ContextKeyEqualsExpr, IContextKeyService } 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 { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, dispose } 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 { IUserDataSyncWorkbenchService, getSyncAreaLabel, IUserDataSyncPreview, IUserDataSyncResource, MANUAL_SYNC_VIEW_ID } from 'vs/workbench/services/userDataSync/common/userDataSync';
|
||||
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';
|
||||
import { listWarningForeground, listDeemphasizedForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets';
|
||||
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
|
||||
const viewName = localize('manual sync', "Manual Sync");
|
||||
export class UserDataManualSyncViewPane extends TreeViewPane {
|
||||
|
||||
export class UserDataManualSyncView extends Disposable {
|
||||
|
||||
private readonly treeView: ITreeView;
|
||||
private userDataSyncPreview: IUserDataSyncPreview;
|
||||
|
||||
private buttonsContainer!: HTMLElement;
|
||||
private syncButton!: Button;
|
||||
private cancelButton!: Button;
|
||||
|
||||
private readonly treeItems = new Map<string, ITreeItem>();
|
||||
|
||||
constructor(
|
||||
container: ViewContainer,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
options: IViewletViewOptions,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IDialogService private readonly dialogService: IDialogService,
|
||||
@IProgressService private readonly progressService: IProgressService,
|
||||
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
|
||||
@IUserDataSyncWorkbenchService userDataSyncWorkbenchService: IUserDataSyncWorkbenchService,
|
||||
@IDecorationsService decorationsService: IDecorationsService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
) {
|
||||
super();
|
||||
|
||||
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||
this.userDataSyncPreview = userDataSyncWorkbenchService.userDataSyncPreview;
|
||||
this.treeView = this.createTreeView();
|
||||
this.registerManualSyncView(container);
|
||||
|
||||
this._register(this.userDataSyncPreview.onDidChangeResources(() => this.updateSyncButtonEnablement()));
|
||||
this._register(this.userDataSyncPreview.onDidChangeResources(() => this.treeView.refresh()));
|
||||
this._register(this.userDataSyncPreview.onDidChangeResources(() => this.closeConflictEditors()));
|
||||
this._register(decorationsService.registerDecorationsProvider(this._register(new UserDataSyncResourcesDecorationProvider(this.userDataSyncPreview))));
|
||||
|
||||
this.registerActions();
|
||||
|
||||
decorationsService.registerDecorationsProvider(this._register(new UserDataSyncResourcesDecorationProvider(this.userDataSyncPreview)));
|
||||
}
|
||||
|
||||
private createTreeView(): ITreeView {
|
||||
const treeView = this.instantiationService.createInstance(TreeView, MANUAL_SYNC_VIEW_ID, viewName);
|
||||
protected renderTreeView(container: HTMLElement): void {
|
||||
super.renderTreeView(DOM.append(container, DOM.$('')));
|
||||
this.createButtons(container);
|
||||
|
||||
this._register(Event.any(
|
||||
this.userDataSyncPreview.onDidChangeChanges,
|
||||
this.userDataSyncPreview.onDidChangeConflicts
|
||||
)(() => treeView.refresh()));
|
||||
const that = this;
|
||||
this.treeView.message = localize('explanation', "Please go through each entry and accept the change to enable sync.");
|
||||
this.treeView.dataProvider = { getChildren() { return that.getTreeItems(); } };
|
||||
}
|
||||
|
||||
const disposable = treeView.onDidChangeVisibility(visible => {
|
||||
if (visible && !treeView.dataProvider) {
|
||||
disposable.dispose();
|
||||
treeView.dataProvider = new ManualSyncViewDataProvider(this.userDataSyncPreview);
|
||||
private createButtons(container: HTMLElement): void {
|
||||
this.buttonsContainer = DOM.append(container, DOM.$('.manual-sync-buttons-container'));
|
||||
|
||||
this.syncButton = this._register(new Button(this.buttonsContainer));
|
||||
this.syncButton.label = localize('turn on sync', "Turn on Preferences Sync");
|
||||
this.updateSyncButtonEnablement();
|
||||
this._register(attachButtonStyler(this.syncButton, this.themeService));
|
||||
this._register(this.syncButton.onDidClick(() => this.apply()));
|
||||
|
||||
this.cancelButton = this._register(new Button(this.buttonsContainer, { secondary: true }));
|
||||
this.cancelButton.label = localize('cancel', "Cancel");
|
||||
this._register(attachButtonStyler(this.cancelButton, this.themeService));
|
||||
this._register(this.cancelButton.onDidClick(() => this.cancel()));
|
||||
}
|
||||
|
||||
protected layoutTreeView(height: number, width: number): void {
|
||||
const buttonContainerHeight = 78;
|
||||
this.buttonsContainer.style.height = `${buttonContainerHeight}px`;
|
||||
this.buttonsContainer.style.width = `${width}px`;
|
||||
|
||||
const numberOfChanges = this.userDataSyncPreview.resources.filter(r => r.syncResource !== SyncResource.GlobalState && (r.localChange !== Change.None || r.remoteChange !== Change.None)).length;
|
||||
const messageHeight = 44;
|
||||
super.layoutTreeView(Math.min(height - buttonContainerHeight, ((22 * numberOfChanges) + messageHeight)), width);
|
||||
}
|
||||
|
||||
private updateSyncButtonEnablement(): void {
|
||||
this.syncButton.enabled = this.userDataSyncPreview.resources.every(c => c.syncResource === SyncResource.GlobalState || c.mergeState === MergeState.Accepted);
|
||||
}
|
||||
|
||||
private async getTreeItems(): Promise<ITreeItem[]> {
|
||||
this.treeItems.clear();
|
||||
const roots: ITreeItem[] = [];
|
||||
for (const resource of this.userDataSyncPreview.resources) {
|
||||
if (resource.syncResource !== SyncResource.GlobalState && (resource.localChange !== Change.None || resource.remoteChange !== Change.None)) {
|
||||
const handle = JSON.stringify(resource);
|
||||
const treeItem = {
|
||||
handle,
|
||||
resourceUri: resource.remote,
|
||||
label: { label: basename(resource.remote), strikethrough: resource.mergeState === MergeState.Accepted && (resource.localChange === Change.Deleted || resource.remoteChange === Change.Deleted) },
|
||||
description: getSyncAreaLabel(resource.syncResource),
|
||||
collapsibleState: TreeItemCollapsibleState.None,
|
||||
command: { id: `workbench.actions.sync.showChanges`, title: '', arguments: [<TreeViewItemHandleArg>{ $treeViewId: '', $treeItemHandle: handle }] },
|
||||
contextValue: `sync-resource-${resource.mergeState}`
|
||||
};
|
||||
this.treeItems.set(handle, treeItem);
|
||||
roots.push(treeItem);
|
||||
}
|
||||
});
|
||||
|
||||
return treeView;
|
||||
}
|
||||
return roots;
|
||||
}
|
||||
|
||||
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 toUserDataSyncResourceGroup(handle: string): IUserDataSyncResource {
|
||||
const parsed: IUserDataSyncResource = JSON.parse(handle);
|
||||
return {
|
||||
syncResource: parsed.syncResource,
|
||||
local: URI.revive(parsed.local),
|
||||
remote: URI.revive(parsed.remote),
|
||||
merged: URI.revive(parsed.merged),
|
||||
accepted: URI.revive(parsed.accepted),
|
||||
localChange: parsed.localChange,
|
||||
remoteChange: parsed.remoteChange,
|
||||
mergeState: parsed.mergeState,
|
||||
};
|
||||
}
|
||||
|
||||
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 {
|
||||
this._register(registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.acceptRemote`,
|
||||
@@ -178,76 +164,78 @@ export class UserDataManualSyncView extends Disposable {
|
||||
icon: Codicon.cloudDownload,
|
||||
menu: {
|
||||
id: MenuId.ViewItemContext,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.regex('viewItem', /sync-resource-modified-.*/i)),
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.equals('viewItem', 'sync-resource-preview')),
|
||||
group: 'inline',
|
||||
order: remoteActionOrder,
|
||||
order: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
return that.acceptRemote(ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle));
|
||||
return that.acceptRemote(that.toUserDataSyncResourceGroup(handle.$treeItemHandle));
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/* accept local change */
|
||||
this._register(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.equals('viewItem', 'sync-resource-preview')),
|
||||
group: 'inline',
|
||||
order: 2,
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
return that.acceptLocal(that.toUserDataSyncResourceGroup(handle.$treeItemHandle));
|
||||
}
|
||||
}));
|
||||
|
||||
/* merge */
|
||||
registerAction2(class extends Action2 {
|
||||
this._register(registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.merge`,
|
||||
title: localize('workbench.actions.sync.merge', "Merge"),
|
||||
icon: Codicon.sync,
|
||||
icon: Codicon.merge,
|
||||
menu: {
|
||||
id: MenuId.ViewItemContext,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.equals('viewItem', 'sync-resource-modified-change')),
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.equals('viewItem', 'sync-resource-preview')),
|
||||
group: 'inline',
|
||||
order: mergeActionOrder,
|
||||
order: 3,
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
return that.mergeResource(ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle));
|
||||
return that.mergeResource(that.toUserDataSyncResourceGroup(handle.$treeItemHandle));
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/* delete */
|
||||
registerAction2(class extends Action2 {
|
||||
/* discard */
|
||||
this._register(registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.deleteLocal`,
|
||||
title: localize('workbench.actions.sync.deleteLocal', "Delete"),
|
||||
icon: Codicon.trash,
|
||||
id: `workbench.actions.sync.undo`,
|
||||
title: localize('workbench.actions.sync.discard', "Discard"),
|
||||
icon: Codicon.discard,
|
||||
menu: {
|
||||
id: MenuId.ViewItemContext,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.regex('viewItem', /sync-resource-(add|delete)-.*/i)),
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.or(ContextKeyExpr.equals('viewItem', 'sync-resource-accepted'), ContextKeyExpr.equals('viewItem', 'sync-resource-conflict'))),
|
||||
group: 'inline',
|
||||
order: 3,
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
return that.deleteResource(ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle));
|
||||
return that.discardResource(that.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 {
|
||||
this._register(registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.showChanges`,
|
||||
@@ -255,69 +243,76 @@ export class UserDataManualSyncView extends Disposable {
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
const previewResource: IUserDataSyncResourceGroup = ManualSyncViewDataProvider.toUserDataSyncResourceGroup(handle.$treeItemHandle);
|
||||
return that.showChanges(previewResource);
|
||||
const previewResource: IUserDataSyncResource = that.toUserDataSyncResourceGroup(handle.$treeItemHandle);
|
||||
return that.open(previewResource);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private async acceptLocal(userDataSyncResource: IUserDataSyncResource): Promise<void> {
|
||||
await this.withProgress(async () => {
|
||||
const content = await this.userDataSyncService.resolveContent(userDataSyncResource.local);
|
||||
await this.userDataSyncPreview.accept(userDataSyncResource.syncResource, userDataSyncResource.local, content);
|
||||
});
|
||||
await this.reopen(userDataSyncResource);
|
||||
}
|
||||
|
||||
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(userDataSyncResource: IUserDataSyncResource): Promise<void> {
|
||||
await this.withProgress(async () => {
|
||||
const content = await this.userDataSyncService.resolveContent(userDataSyncResource.remote);
|
||||
await this.userDataSyncPreview.accept(userDataSyncResource.syncResource, userDataSyncResource.remote, content);
|
||||
});
|
||||
await this.reopen(userDataSyncResource);
|
||||
}
|
||||
|
||||
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: IUserDataSyncResource): Promise<void> {
|
||||
await this.withProgress(() => this.userDataSyncPreview.merge(previewResource.merged));
|
||||
previewResource = this.userDataSyncPreview.resources.find(({ local }) => isEqual(local, previewResource.local))!;
|
||||
await this.reopen(previewResource);
|
||||
if (previewResource.mergeState === MergeState.Conflict) {
|
||||
await this.dialogService.show(Severity.Warning, localize('conflicts detected', "Conflicts Detected."), [], {
|
||||
detail: localize('resolve', "Unable to merge due to conflicts. Please resolve them to continue.")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async mergeResource(previewResource: IUserDataSyncResourceGroup): Promise<void> {
|
||||
return this.withProgress(() => this.userDataSyncPreview.merge(previewResource.preview));
|
||||
private async discardResource(previewResource: IUserDataSyncResource): Promise<void> {
|
||||
this.close(previewResource);
|
||||
return this.withProgress(() => this.userDataSyncPreview.discard(previewResource.merged));
|
||||
}
|
||||
|
||||
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 apply(): Promise<void> {
|
||||
this.closeAll();
|
||||
this.syncButton.label = localize('turning on', "Turning on...");
|
||||
this.syncButton.enabled = false;
|
||||
this.cancelButton.enabled = false;
|
||||
try {
|
||||
await this.withProgress(async () => this.userDataSyncPreview.apply());
|
||||
} catch (error) {
|
||||
this.syncButton.enabled = false;
|
||||
this.cancelButton.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
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 cancel(): Promise<void> {
|
||||
for (const resource of this.userDataSyncPreview.resources) {
|
||||
this.close(resource);
|
||||
}
|
||||
await this.userDataSyncPreview.cancel();
|
||||
}
|
||||
|
||||
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)) });
|
||||
private async open(previewResource: IUserDataSyncResource): Promise<void> {
|
||||
if (previewResource.mergeState === MergeState.Accepted) {
|
||||
if (previewResource.localChange !== Change.Deleted && previewResource.remoteChange !== Change.Deleted) {
|
||||
// Do not open deleted preview
|
||||
await this.editorService.openEditor({ resource: previewResource.accepted, label: localize('preview', "{0} (Preview)", basename(previewResource.accepted)) });
|
||||
}
|
||||
} else {
|
||||
const leftResource = URI.revive(previewResource.remote);
|
||||
const rightResource = isConflict ? URI.revive(previewResource.preview) : URI.revive(previewResource.local);
|
||||
const leftResource = previewResource.remote;
|
||||
const rightResource = previewResource.mergeState === MergeState.Conflict ? previewResource.merged : 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));
|
||||
const rightResourceName = previewResource.mergeState === MergeState.Conflict ? localize('merges', "{0} (Merges)", basename(rightResource))
|
||||
: localize({ key: 'rightResourceName', comment: ['local as in file in disk'] }, "{0} (Local)", basename(rightResource));
|
||||
await this.editorService.openEditor({
|
||||
leftResource,
|
||||
rightResource,
|
||||
@@ -330,91 +325,55 @@ export class UserDataManualSyncView extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private withProgress(task: () => Promise<void>): Promise<void> {
|
||||
return this.progressService.withProgress({ location: MANUAL_SYNC_VIEW_ID, delay: 500 }, task);
|
||||
private async reopen(previewResource: IUserDataSyncResource): Promise<void> {
|
||||
this.close(previewResource);
|
||||
const resource = this.userDataSyncPreview.resources.find(({ local }) => isEqual(local, previewResource.local));
|
||||
if (resource) {
|
||||
// select the resource
|
||||
await this.treeView.refresh();
|
||||
this.treeView.setSelection([this.treeItems.get(JSON.stringify(resource))!]);
|
||||
|
||||
await this.open(resource);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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();
|
||||
private close(previewResource: IUserDataSyncResource): void {
|
||||
for (const input of this.editorService.editors) {
|
||||
if (input instanceof DiffEditorInput) {
|
||||
// Close all diff editors
|
||||
if (isEqual(previewResource.remote, input.secondary.resource)) {
|
||||
input.dispose();
|
||||
}
|
||||
}
|
||||
// Close all preview editors
|
||||
else if (isEqual(previewResource.accepted, input.resource)) {
|
||||
input.dispose();
|
||||
}
|
||||
}
|
||||
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'
|
||||
});
|
||||
private closeConflictEditors() {
|
||||
for (const previewResource of this.userDataSyncPreview.resources) {
|
||||
if (previewResource.mergeState !== MergeState.Conflict) {
|
||||
for (const input of this.editorService.editors) {
|
||||
if (input instanceof DiffEditorInput) {
|
||||
if (isEqual(previewResource.remote, input.secondary.resource) && isEqual(previewResource.merged, input.primary.resource)) {
|
||||
input.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.userDataSyncPreview.conflicts.length) {
|
||||
roots.push({
|
||||
handle: 'conflicts',
|
||||
collapsibleState: TreeItemCollapsibleState.Expanded,
|
||||
label: { label: localize('conflicts', "Conflicts") },
|
||||
themeIcon: Codicon.folder,
|
||||
contextValue: 'conflicts',
|
||||
});
|
||||
}
|
||||
|
||||
private closeAll() {
|
||||
for (const previewResource of this.userDataSyncPreview.resources) {
|
||||
this.close(previewResource);
|
||||
}
|
||||
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
|
||||
};
|
||||
private withProgress(task: () => Promise<void>): Promise<void> {
|
||||
return this.progressService.withProgress({ location: MANUAL_SYNC_VIEW_ID, delay: 500 }, task);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -428,25 +387,118 @@ class UserDataSyncResourcesDecorationProvider extends Disposable implements IDec
|
||||
|
||||
constructor(private readonly userDataSyncPreview: IUserDataSyncPreview) {
|
||||
super();
|
||||
this._register(userDataSyncPreview.onDidChangeResources(c => this._onDidChange.fire(c.map(({ remote }) => remote))));
|
||||
}
|
||||
|
||||
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',
|
||||
};
|
||||
const userDataSyncResource = this.userDataSyncPreview.resources.find(c => isEqual(c.remote, resource));
|
||||
if (userDataSyncResource) {
|
||||
switch (userDataSyncResource.mergeState) {
|
||||
case MergeState.Conflict:
|
||||
return { letter: '⚠', color: listWarningForeground, tooltip: localize('conflict', "Conflicts Detected") };
|
||||
case MergeState.Accepted:
|
||||
return { letter: '✓', color: listDeemphasizedForeground, tooltip: localize('accepted', "Accepted") };
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
type AcceptChangesClassification = {
|
||||
source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
action: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
|
||||
class AcceptChangesContribution extends Disposable implements IEditorContribution {
|
||||
|
||||
static get(editor: ICodeEditor): AcceptChangesContribution {
|
||||
return editor.getContribution<AcceptChangesContribution>(AcceptChangesContribution.ID);
|
||||
}
|
||||
|
||||
public static readonly ID = 'editor.contrib.acceptChangesButton';
|
||||
|
||||
private acceptChangesButton: FloatingClickWidget | undefined;
|
||||
|
||||
constructor(
|
||||
private editor: ICodeEditor,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IUserDataSyncWorkbenchService private readonly userDataSyncWorkbenchService: IUserDataSyncWorkbenchService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.update();
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.editor.onDidChangeModel(() => this.update()));
|
||||
this._register(this.userDataSyncService.onDidChangeConflicts(() => this.update()));
|
||||
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('diffEditor.renderSideBySide'))(() => this.update()));
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
if (!this.shouldShowButton(this.editor)) {
|
||||
this.disposeAcceptChangesWidgetRenderer();
|
||||
return;
|
||||
}
|
||||
|
||||
this.createAcceptChangesWidgetRenderer();
|
||||
}
|
||||
|
||||
private shouldShowButton(editor: ICodeEditor): boolean {
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
return false; // we need a model
|
||||
}
|
||||
|
||||
const userDataSyncResource = this.getUserDataSyncResource(model.uri);
|
||||
if (!userDataSyncResource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private createAcceptChangesWidgetRenderer(): void {
|
||||
if (!this.acceptChangesButton) {
|
||||
const resource = this.editor.getModel()!.uri;
|
||||
const userDataSyncResource = this.getUserDataSyncResource(resource)!;
|
||||
|
||||
const isRemoteResource = isEqual(userDataSyncResource.remote, resource);
|
||||
const isLocalResource = isEqual(userDataSyncResource.local, resource);
|
||||
const label = isRemoteResource ? localize('accept remote', "Accept Remote")
|
||||
: isLocalResource ? localize('accept local', "Accept Local")
|
||||
: localize('accept merges', "Accept Merges");
|
||||
|
||||
this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, label, null);
|
||||
this._register(this.acceptChangesButton.onClick(async () => {
|
||||
const model = this.editor.getModel();
|
||||
if (model) {
|
||||
this.telemetryService.publicLog2<{ source: string, action: string }, AcceptChangesClassification>('sync/acceptChanges', { source: userDataSyncResource.syncResource, action: isRemoteResource ? 'acceptRemote' : isLocalResource ? 'acceptLocal' : 'acceptMerges' });
|
||||
await this.userDataSyncWorkbenchService.userDataSyncPreview.accept(userDataSyncResource.syncResource, model.uri, model.getValue());
|
||||
}
|
||||
}));
|
||||
|
||||
this.acceptChangesButton.render();
|
||||
}
|
||||
}
|
||||
|
||||
private getUserDataSyncResource(resource: URI): IUserDataSyncResource | undefined {
|
||||
return this.userDataSyncWorkbenchService.userDataSyncPreview.resources.find(r => isEqual(resource, r.local) || isEqual(resource, r.remote) || isEqual(resource, r.merged));
|
||||
}
|
||||
|
||||
private disposeAcceptChangesWidgetRenderer(): void {
|
||||
dispose(this.acceptChangesButton);
|
||||
this.acceptChangesButton = undefined;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposeAcceptChangesWidgetRenderer();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorContribution(AcceptChangesContribution.ID, AcceptChangesContribution);
|
||||
|
||||
@@ -140,6 +140,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
this.updateGlobalActivityBadge();
|
||||
}));
|
||||
this._register(userDataSyncService.onDidChangeConflicts(() => this.onDidChangeConflicts(this.userDataSyncService.conflicts)));
|
||||
this._register(userDataAutoSyncService.onDidChangeEnablement(() => this.onDidChangeConflicts(this.userDataSyncService.conflicts)));
|
||||
this._register(userDataSyncService.onSyncErrors(errors => this.onSynchronizerErrors(errors)));
|
||||
this._register(userDataAutoSyncService.onError(error => this.onAutoSyncError(error)));
|
||||
|
||||
@@ -153,6 +154,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
|
||||
private readonly conflictsDisposables = new Map<SyncResource, IDisposable>();
|
||||
private onDidChangeConflicts(conflicts: [SyncResource, IResourcePreview[]][]) {
|
||||
if (!this.userDataAutoSyncService.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
this.updateGlobalActivityBadge();
|
||||
if (conflicts.length) {
|
||||
const conflictsSources: SyncResource[] = conflicts.map(([syncResource]) => syncResource);
|
||||
@@ -194,14 +198,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
},
|
||||
{
|
||||
label: localize('accept local', "Accept Local"),
|
||||
label: localize('accept merges', "Accept Merges"),
|
||||
run: () => {
|
||||
this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: syncResource, action: 'acceptLocal' });
|
||||
this.acceptLocal(syncResource, conflicts);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: localize('show conflicts', "Show Conflicts"),
|
||||
label: localize('show merges', "Show Merges"),
|
||||
run: () => {
|
||||
this.telemetryService.publicLog2<{ source: string, action?: string }, SyncConflictsClassification>('sync/showConflicts', { source: syncResource });
|
||||
this.handleConflicts([syncResource, conflicts]);
|
||||
@@ -240,7 +244,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
for (const conflict of conflicts) {
|
||||
const modelRef = await this.textModelResolverService.createModelReference(conflict.remoteResource);
|
||||
try {
|
||||
await this.userDataSyncService.acceptPreviewContent(syncResource, conflict.remoteResource, modelRef.object.textEditorModel.getValue());
|
||||
await this.userDataSyncService.accept(syncResource, conflict.remoteResource, modelRef.object.textEditorModel.getValue(), this.userDataAutoSyncService.isEnabled());
|
||||
} finally {
|
||||
modelRef.dispose();
|
||||
}
|
||||
@@ -255,7 +259,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
for (const conflict of conflicts) {
|
||||
const modelRef = await this.textModelResolverService.createModelReference(conflict.previewResource);
|
||||
try {
|
||||
await this.userDataSyncService.acceptPreviewContent(syncResource, conflict.previewResource, modelRef.object.textEditorModel.getValue());
|
||||
await this.userDataSyncService.accept(syncResource, conflict.previewResource, modelRef.object.textEditorModel.getValue(), this.userDataAutoSyncService.isEnabled());
|
||||
} finally {
|
||||
modelRef.dispose();
|
||||
}
|
||||
@@ -305,10 +309,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
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."),
|
||||
message: localize('error reset required', "Preferences sync is disabled because your data in the cloud is older than that of the client. Please clear 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('reset', localize('reset', "Clear Data in Cloud..."), undefined, true, () => this.userDataSyncWorkbenchService.resetSyncedData()),
|
||||
new Action('show synced data', localize('show synced data action', "Show Synced Data"), undefined, true, () => this.userDataSyncWorkbenchService.showSyncActivity())
|
||||
]
|
||||
}
|
||||
@@ -386,7 +390,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
let clazz: string | undefined;
|
||||
let priority: number | undefined = undefined;
|
||||
|
||||
if (this.userDataSyncService.conflicts.length) {
|
||||
if (this.userDataSyncService.conflicts.length && this.userDataAutoSyncService.isEnabled()) {
|
||||
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..."));
|
||||
@@ -461,10 +465,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
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."),
|
||||
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 the client. Please clear 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('reset', localize('reset', "Clear Data in Cloud..."), undefined, true, () => this.userDataSyncWorkbenchService.resetSyncedData()),
|
||||
new Action('show synced data', localize('show synced data action', "Show Synced Data"), undefined, true, () => this.userDataSyncWorkbenchService.showSyncActivity())
|
||||
]
|
||||
}
|
||||
@@ -483,8 +487,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
Severity.Info,
|
||||
localize('sync preview message', "Synchronizing your preferences is a preview feature, please read the documentation before turning it on."),
|
||||
[
|
||||
localize('open doc', "Open Documentation"),
|
||||
localize('turn on', "Turn On"),
|
||||
localize('open doc', "Open Documentation"),
|
||||
localize('cancel', "Cancel"),
|
||||
],
|
||||
{
|
||||
@@ -492,7 +496,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
);
|
||||
switch (result.choice) {
|
||||
case 0: this.openerService.open(URI.parse('https://aka.ms/vscode-settings-sync-help')); return false;
|
||||
case 1: this.openerService.open(URI.parse('https://aka.ms/vscode-settings-sync-help')); return false;
|
||||
case 2: return false;
|
||||
}
|
||||
return true;
|
||||
@@ -647,18 +651,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
|
||||
private async handleConflicts([syncResource, conflicts]: [SyncResource, IResourcePreview[]]): Promise<void> {
|
||||
for (const conflict of conflicts) {
|
||||
let label: string | undefined = undefined;
|
||||
if (syncResource === SyncResource.Settings) {
|
||||
label = localize('settings conflicts preview', "Settings Conflicts (Remote ↔ Local)");
|
||||
} else if (syncResource === SyncResource.Keybindings) {
|
||||
label = localize('keybindings conflicts preview', "Keybindings Conflicts (Remote ↔ Local)");
|
||||
} else if (syncResource === SyncResource.Snippets) {
|
||||
label = localize('snippets conflicts preview', "User Snippet Conflicts (Remote ↔ Local) - {0}", basename(conflict.previewResource));
|
||||
}
|
||||
const leftResourceName = localize({ key: 'leftResourceName', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(conflict.remoteResource));
|
||||
const rightResourceName = localize('merges', "{0} (Merges)", basename(conflict.previewResource));
|
||||
await this.editorService.openEditor({
|
||||
leftResource: conflict.remoteResource,
|
||||
rightResource: conflict.previewResource,
|
||||
label,
|
||||
label: localize('sideBySideLabels', "{0} ↔ {1}", leftResourceName, rightResourceName),
|
||||
options: {
|
||||
preserveFocus: false,
|
||||
pinned: true,
|
||||
@@ -1125,7 +1123,8 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IDialogService private readonly dialogService: IDialogService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -1154,6 +1153,10 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio
|
||||
return false; // we need a model
|
||||
}
|
||||
|
||||
if (!this.userDataAutoSyncService.isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const syncResourceConflicts = this.getSyncResourceConflicts(model.uri);
|
||||
if (!syncResourceConflicts) {
|
||||
return false;
|
||||
@@ -1176,8 +1179,8 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio
|
||||
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);
|
||||
const acceptMergesLabel = localize('accept merges', "Accept Merges");
|
||||
this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, isRemote ? acceptRemoteLabel : acceptMergesLabel, null);
|
||||
this._register(this.acceptChangesButton.onClick(async () => {
|
||||
const model = this.editor.getModel();
|
||||
if (model) {
|
||||
@@ -1187,15 +1190,15 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio
|
||||
type: 'info',
|
||||
title: isRemote
|
||||
? localize('Sync accept remote', "Preferences Sync: {0}", acceptRemoteLabel)
|
||||
: localize('Sync accept local', "Preferences Sync: {0}", acceptLocalLabel),
|
||||
: localize('Sync accept merges', "Preferences Sync: {0}", acceptMergesLabel),
|
||||
message: isRemote
|
||||
? localize('confirm replace and overwrite local', "Would you like to accept remote {0} and replace local {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase())
|
||||
: localize('confirm replace and overwrite remote', "Would you like to accept local {0} and replace remote {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()),
|
||||
primaryButton: isRemote ? acceptRemoteLabel : acceptLocalLabel
|
||||
: localize('confirm replace and overwrite remote', "Would you like to accept merges and replace remote {0}?", syncAreaLabel.toLowerCase()),
|
||||
primaryButton: isRemote ? acceptRemoteLabel : acceptMergesLabel
|
||||
});
|
||||
if (result.confirmed) {
|
||||
try {
|
||||
await this.userDataSyncService.acceptPreviewContent(syncResource, model.uri, model.getValue());
|
||||
await this.userDataSyncService.accept(syncResource, model.uri, model.getValue(), true);
|
||||
} catch (e) {
|
||||
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.LocalPreconditionFailed) {
|
||||
const syncResourceCoflicts = this.userDataSyncService.conflicts.filter(syncResourceCoflicts => syncResourceCoflicts[0] === syncResource)[0];
|
||||
|
||||
@@ -30,13 +30,13 @@ 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_ACTIVITY_VIEWS, SHOW_SYNC_LOG_COMMAND_ID, CONFIGURE_SYNC_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, MANUAL_SYNC_VIEW_ID, CONTEXT_ENABLE_MANUAL_SYNC_VIEW } 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, 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';
|
||||
import { UserDataManualSyncViewPane } from 'vs/workbench/contrib/userDataSync/browser/userDataManualSyncView';
|
||||
|
||||
export class UserDataSyncViewPaneContainer extends ViewPaneContainer {
|
||||
|
||||
@@ -55,7 +55,7 @@ export class UserDataSyncViewPaneContainer extends ViewPaneContainer {
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
) {
|
||||
super(containerId, { mergeViewWithContainerWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService);
|
||||
super(containerId, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService);
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
@@ -67,7 +67,7 @@ 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.userDataSyncWorkbenchService.resetSyncedData()),
|
||||
new Action('workbench.actions.syncData.reset', localize('workbench.actions.syncData.reset', "Clear Data in Cloud..."), undefined, true, () => this.userDataSyncWorkbenchService.resetSyncedData()),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ export class UserDataSyncDataViews extends Disposable {
|
||||
}
|
||||
|
||||
private registerViews(container: ViewContainer): void {
|
||||
this._register(this.instantiationService.createInstance(UserDataManualSyncView, container));
|
||||
this.registerManualSyncView(container);
|
||||
|
||||
this.registerActivityView(container, true);
|
||||
this.registerMachinesView(container);
|
||||
@@ -94,6 +94,22 @@ export class UserDataSyncDataViews extends Disposable {
|
||||
this.registerActivityView(container, false);
|
||||
}
|
||||
|
||||
private registerManualSyncView(container: ViewContainer): void {
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
|
||||
const viewName = localize('manual sync', "Manual Sync");
|
||||
viewsRegistry.registerViews([<ITreeViewDescriptor>{
|
||||
id: MANUAL_SYNC_VIEW_ID,
|
||||
name: viewName,
|
||||
ctorDescriptor: new SyncDescriptor(UserDataManualSyncViewPane),
|
||||
when: CONTEXT_ENABLE_MANUAL_SYNC_VIEW,
|
||||
canToggleVisibility: false,
|
||||
canMoveView: false,
|
||||
treeView: this.instantiationService.createInstance(TreeView, MANUAL_SYNC_VIEW_ID, viewName),
|
||||
collapsed: false,
|
||||
order: 100,
|
||||
}], container);
|
||||
}
|
||||
|
||||
private registerMachinesView(container: ViewContainer): void {
|
||||
const id = `workbench.views.sync.machines`;
|
||||
const name = localize('synced machines', "Synced Machines");
|
||||
|
||||
Reference in New Issue
Block a user