mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from vscode 33a65245075e4d18908652865a79cf5489c30f40 (#9279)
* Merge from vscode 33a65245075e4d18908652865a79cf5489c30f40 * remove github
This commit is contained in:
@@ -11,8 +11,9 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo
|
||||
import { ICommandService, CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
|
||||
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { UriDto } from 'vs/base/common/types';
|
||||
|
||||
export interface ILocalizedString {
|
||||
value: string;
|
||||
@@ -28,9 +29,7 @@ export interface ICommandAction {
|
||||
toggled?: ContextKeyExpr;
|
||||
}
|
||||
|
||||
type Serialized<T> = { [K in keyof T]: T[K] extends URI ? UriComponents : Serialized<T[K]> };
|
||||
|
||||
export type ISerializableCommandAction = Serialized<ICommandAction>;
|
||||
export type ISerializableCommandAction = UriDto<ICommandAction>;
|
||||
|
||||
export interface IMenuItem {
|
||||
command: ICommandAction;
|
||||
|
||||
16
src/vs/platform/dialogs/test/common/testDialogService.ts
Normal file
16
src/vs/platform/dialogs/test/common/testDialogService.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions, IShowResult } from 'vs/platform/dialogs/common/dialogs';
|
||||
|
||||
export class TestDialogService implements IDialogService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
confirm(_confirmation: IConfirmation): Promise<IConfirmationResult> { return Promise.resolve({ confirmed: false }); }
|
||||
show(_severity: Severity, _message: string, _buttons: string[], _options?: IDialogOptions): Promise<IShowResult> { return Promise.resolve({ choice: 0 }); }
|
||||
about(): Promise<void> { return Promise.resolve(); }
|
||||
}
|
||||
@@ -157,7 +157,7 @@ export class InstantiationService implements IInstantiationService {
|
||||
graph.lookupOrInsertNode(item);
|
||||
|
||||
// a weak but working heuristic for cycle checks
|
||||
if (cycleCount++ > 150) {
|
||||
if (cycleCount++ > 200) {
|
||||
throw new CyclicDependencyError(graph);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,8 +86,6 @@ export interface IProgressRunner {
|
||||
done(): void;
|
||||
}
|
||||
|
||||
export const emptyProgress: IProgress<IProgressStep> = { report: () => { } };
|
||||
|
||||
export const emptyProgressRunner: IProgressRunner = Object.freeze({
|
||||
total() { },
|
||||
worked() { },
|
||||
@@ -100,6 +98,8 @@ export interface IProgress<T> {
|
||||
|
||||
export class Progress<T> implements IProgress<T> {
|
||||
|
||||
static readonly None: IProgress<any> = Object.freeze({ report() { } });
|
||||
|
||||
private _value?: T;
|
||||
get value(): T | undefined { return this._value; }
|
||||
|
||||
|
||||
@@ -8,42 +8,26 @@ import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export const IUndoRedoService = createDecorator<IUndoRedoService>('undoRedoService');
|
||||
|
||||
export interface IUndoRedoContext {
|
||||
replaceCurrentElement(others: IUndoRedoElement[]): void;
|
||||
export const enum UndoRedoElementType {
|
||||
Resource,
|
||||
Workspace
|
||||
}
|
||||
|
||||
export interface IUndoRedoElement {
|
||||
/**
|
||||
* None, one or multiple resources that this undo/redo element impacts.
|
||||
*/
|
||||
readonly resources: readonly URI[];
|
||||
|
||||
/**
|
||||
* The label of the undo/redo element.
|
||||
*/
|
||||
export interface IResourceUndoRedoElement {
|
||||
readonly type: UndoRedoElementType.Resource;
|
||||
readonly resource: URI;
|
||||
readonly label: string;
|
||||
undo(): Promise<void> | void;
|
||||
redo(): Promise<void> | void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo.
|
||||
* Will always be called before `redo`.
|
||||
* Can be called multiple times.
|
||||
* e.g. `undo` -> `redo` -> `undo` -> `redo`
|
||||
*/
|
||||
undo(ctx: IUndoRedoContext): void;
|
||||
|
||||
/**
|
||||
* Redo.
|
||||
* Will always be called after `undo`.
|
||||
* Can be called multiple times.
|
||||
* e.g. `undo` -> `redo` -> `undo` -> `redo`
|
||||
*/
|
||||
redo(ctx: IUndoRedoContext): void;
|
||||
|
||||
/**
|
||||
* Invalidate the edits concerning `resource`.
|
||||
* i.e. the undo/redo stack for that particular resource has been destroyed.
|
||||
*/
|
||||
invalidate(resource: URI): void;
|
||||
export interface IWorkspaceUndoRedoElement {
|
||||
readonly type: UndoRedoElementType.Workspace;
|
||||
readonly resources: readonly URI[];
|
||||
readonly label: string;
|
||||
undo(): Promise<void> | void;
|
||||
redo(): Promise<void> | void;
|
||||
split(): IResourceUndoRedoElement[];
|
||||
}
|
||||
|
||||
export interface IUndoRedoService {
|
||||
@@ -53,12 +37,12 @@ export interface IUndoRedoService {
|
||||
* Add a new element to the `undo` stack.
|
||||
* This will destroy the `redo` stack.
|
||||
*/
|
||||
pushElement(element: IUndoRedoElement): void;
|
||||
pushElement(element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement): void;
|
||||
|
||||
/**
|
||||
* Get the last pushed element. If the last pushed element has been undone, returns null.
|
||||
*/
|
||||
getLastElement(resource: URI): IUndoRedoElement | null;
|
||||
getLastElement(resource: URI): IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null;
|
||||
|
||||
/**
|
||||
* Remove elements that target `resource`.
|
||||
@@ -66,8 +50,8 @@ export interface IUndoRedoService {
|
||||
removeElements(resource: URI): void;
|
||||
|
||||
canUndo(resource: URI): boolean;
|
||||
undo(resource: URI): void;
|
||||
undo(resource: URI): Promise<void> | void;
|
||||
|
||||
redo(resource: URI): void;
|
||||
canRedo(resource: URI): boolean;
|
||||
redo(resource: URI): Promise<void> | void;
|
||||
}
|
||||
|
||||
@@ -3,31 +3,88 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IUndoRedoService, IUndoRedoElement } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IUndoRedoService, IResourceUndoRedoElement, IWorkspaceUndoRedoElement, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { getComparisonKey as uriGetComparisonKey } from 'vs/base/common/resources';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
class StackElement {
|
||||
public readonly actual: IUndoRedoElement;
|
||||
class ResourceStackElement {
|
||||
public readonly type = UndoRedoElementType.Resource;
|
||||
public readonly actual: IResourceUndoRedoElement;
|
||||
public readonly label: string;
|
||||
public readonly resources: readonly URI[];
|
||||
|
||||
public readonly resource: URI;
|
||||
public readonly strResource: string;
|
||||
public readonly resources: URI[];
|
||||
public readonly strResources: string[];
|
||||
|
||||
constructor(actual: IUndoRedoElement) {
|
||||
constructor(actual: IResourceUndoRedoElement) {
|
||||
this.actual = actual;
|
||||
this.label = actual.label;
|
||||
this.resources = actual.resources;
|
||||
this.resource = actual.resource;
|
||||
this.strResource = uriGetComparisonKey(this.resource);
|
||||
this.resources = [this.resource];
|
||||
this.strResources = [this.strResource];
|
||||
}
|
||||
}
|
||||
|
||||
const enum RemovedResourceReason {
|
||||
ExternalRemoval = 0,
|
||||
NoParallelUniverses = 1
|
||||
}
|
||||
|
||||
class RemovedResources {
|
||||
public readonly set: Set<string> = new Set<string>();
|
||||
public readonly reason: [URI[], URI[]] = [[], []];
|
||||
|
||||
public createMessage(): string {
|
||||
let messages: string[] = [];
|
||||
if (this.reason[RemovedResourceReason.ExternalRemoval].length > 0) {
|
||||
const paths = this.reason[RemovedResourceReason.ExternalRemoval].map(uri => uri.scheme === Schemas.file ? uri.fsPath : uri.path);
|
||||
messages.push(nls.localize('externalRemoval', "The following files have been closed: {0}.", paths.join(', ')));
|
||||
}
|
||||
if (this.reason[RemovedResourceReason.NoParallelUniverses].length > 0) {
|
||||
const paths = this.reason[RemovedResourceReason.NoParallelUniverses].map(uri => uri.scheme === Schemas.file ? uri.fsPath : uri.path);
|
||||
messages.push(nls.localize('noParallelUniverses', "The following files have been modified in an incompatible way: {0}.", paths.join(', ')));
|
||||
}
|
||||
return messages.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
class WorkspaceStackElement {
|
||||
public readonly type = UndoRedoElementType.Workspace;
|
||||
public readonly actual: IWorkspaceUndoRedoElement;
|
||||
public readonly label: string;
|
||||
|
||||
public readonly resources: URI[];
|
||||
public readonly strResources: string[];
|
||||
public removedResources: RemovedResources | null;
|
||||
|
||||
constructor(actual: IWorkspaceUndoRedoElement) {
|
||||
this.actual = actual;
|
||||
this.label = actual.label;
|
||||
this.resources = actual.resources.slice(0);
|
||||
this.strResources = this.resources.map(resource => uriGetComparisonKey(resource));
|
||||
this.removedResources = null;
|
||||
}
|
||||
|
||||
public invalidate(resource: URI): void {
|
||||
if (this.resources.length > 1) {
|
||||
this.actual.invalidate(resource);
|
||||
public removeResource(resource: URI, strResource: string, reason: RemovedResourceReason): void {
|
||||
if (!this.removedResources) {
|
||||
this.removedResources = new RemovedResources();
|
||||
}
|
||||
if (!this.removedResources.set.has(strResource)) {
|
||||
this.removedResources.set.add(strResource);
|
||||
this.removedResources.reason[reason].push(resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
type StackElement = ResourceStackElement | WorkspaceStackElement;
|
||||
|
||||
class ResourceEditStack {
|
||||
public resource: URI;
|
||||
@@ -46,12 +103,15 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
|
||||
private readonly _editStacks: Map<string, ResourceEditStack>;
|
||||
|
||||
constructor() {
|
||||
constructor(
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
) {
|
||||
this._editStacks = new Map<string, ResourceEditStack>();
|
||||
}
|
||||
|
||||
public pushElement(_element: IUndoRedoElement): void {
|
||||
const element = new StackElement(_element);
|
||||
public pushElement(_element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement): void {
|
||||
const element: StackElement = (_element.type === UndoRedoElementType.Resource ? new ResourceStackElement(_element) : new WorkspaceStackElement(_element));
|
||||
for (let i = 0, len = element.resources.length; i < len; i++) {
|
||||
const resource = element.resources[i];
|
||||
const strResource = element.strResources[i];
|
||||
@@ -66,14 +126,16 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
|
||||
// remove the future
|
||||
for (const futureElement of editStack.future) {
|
||||
futureElement.invalidate(resource);
|
||||
if (futureElement.type === UndoRedoElementType.Workspace) {
|
||||
futureElement.removeResource(resource, strResource, RemovedResourceReason.NoParallelUniverses);
|
||||
}
|
||||
}
|
||||
editStack.future = [];
|
||||
editStack.past.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
public getLastElement(resource: URI): IUndoRedoElement | null {
|
||||
public getLastElement(resource: URI): IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null {
|
||||
const strResource = uriGetComparisonKey(resource);
|
||||
if (this._editStacks.has(strResource)) {
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
@@ -88,15 +150,75 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
return null;
|
||||
}
|
||||
|
||||
private _splitPastWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: Set<string> | null): void {
|
||||
const individualArr = toRemove.actual.split();
|
||||
const individualMap = new Map<string, ResourceStackElement>();
|
||||
for (const _element of individualArr) {
|
||||
const element = new ResourceStackElement(_element);
|
||||
individualMap.set(element.strResource, element);
|
||||
}
|
||||
|
||||
for (const strResource of toRemove.strResources) {
|
||||
if (ignoreResources && ignoreResources.has(strResource)) {
|
||||
continue;
|
||||
}
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
for (let j = editStack.past.length - 1; j >= 0; j--) {
|
||||
if (editStack.past[j] === toRemove) {
|
||||
if (individualMap.has(strResource)) {
|
||||
// gets replaced
|
||||
editStack.past[j] = individualMap.get(strResource)!;
|
||||
} else {
|
||||
// gets deleted
|
||||
editStack.past.splice(j, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _splitFutureWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: Set<string> | null): void {
|
||||
const individualArr = toRemove.actual.split();
|
||||
const individualMap = new Map<string, ResourceStackElement>();
|
||||
for (const _element of individualArr) {
|
||||
const element = new ResourceStackElement(_element);
|
||||
individualMap.set(element.strResource, element);
|
||||
}
|
||||
|
||||
for (const strResource of toRemove.strResources) {
|
||||
if (ignoreResources && ignoreResources.has(strResource)) {
|
||||
continue;
|
||||
}
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
for (let j = editStack.future.length - 1; j >= 0; j--) {
|
||||
if (editStack.future[j] === toRemove) {
|
||||
if (individualMap.has(strResource)) {
|
||||
// gets replaced
|
||||
editStack.future[j] = individualMap.get(strResource)!;
|
||||
} else {
|
||||
// gets deleted
|
||||
editStack.future.splice(j, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public removeElements(resource: URI): void {
|
||||
const strResource = uriGetComparisonKey(resource);
|
||||
if (this._editStacks.has(strResource)) {
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
for (const pastElement of editStack.past) {
|
||||
pastElement.invalidate(resource);
|
||||
for (const element of editStack.past) {
|
||||
if (element.type === UndoRedoElementType.Workspace) {
|
||||
element.removeResource(resource, strResource, RemovedResourceReason.ExternalRemoval);
|
||||
}
|
||||
}
|
||||
for (const futureElement of editStack.future) {
|
||||
futureElement.invalidate(resource);
|
||||
for (const element of editStack.future) {
|
||||
if (element.type === UndoRedoElementType.Workspace) {
|
||||
element.removeResource(resource, strResource, RemovedResourceReason.ExternalRemoval);
|
||||
}
|
||||
}
|
||||
this._editStacks.delete(strResource);
|
||||
}
|
||||
@@ -111,7 +233,85 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
return false;
|
||||
}
|
||||
|
||||
public undo(resource: URI): void {
|
||||
private _onError(err: Error, element: StackElement): void {
|
||||
onUnexpectedError(err);
|
||||
// An error occured while undoing or redoing => drop the undo/redo stack for all affected resources
|
||||
for (const resource of element.resources) {
|
||||
this.removeElements(resource);
|
||||
}
|
||||
this._notificationService.error(err);
|
||||
}
|
||||
|
||||
private _safeInvoke(element: StackElement, invoke: () => Promise<void> | void): Promise<void> | void {
|
||||
let result: Promise<void> | void;
|
||||
try {
|
||||
result = invoke();
|
||||
} catch (err) {
|
||||
return this._onError(err, element);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
return result.then(undefined, (err) => this._onError(err, element));
|
||||
}
|
||||
}
|
||||
|
||||
private _workspaceUndo(resource: URI, element: WorkspaceStackElement): Promise<void> | void {
|
||||
if (element.removedResources) {
|
||||
this._splitPastWorkspaceElement(element, element.removedResources.set);
|
||||
const message = nls.localize('cannotWorkspaceUndo', "Could not undo '{0}' across all files. {1}", element.label, element.removedResources.createMessage());
|
||||
this._notificationService.info(message);
|
||||
return;
|
||||
}
|
||||
|
||||
// this must be the last past element in all the impacted resources!
|
||||
let affectedEditStacks: ResourceEditStack[] = [];
|
||||
for (const strResource of element.strResources) {
|
||||
affectedEditStacks.push(this._editStacks.get(strResource)!);
|
||||
}
|
||||
|
||||
let cannotUndoDueToResources: URI[] = [];
|
||||
for (const editStack of affectedEditStacks) {
|
||||
if (editStack.past.length === 0 || editStack.past[editStack.past.length - 1] !== element) {
|
||||
cannotUndoDueToResources.push(editStack.resource);
|
||||
}
|
||||
}
|
||||
|
||||
if (cannotUndoDueToResources.length > 0) {
|
||||
this._splitPastWorkspaceElement(element, null);
|
||||
const paths = cannotUndoDueToResources.map(r => r.scheme === Schemas.file ? r.fsPath : r.path);
|
||||
const message = nls.localize('cannotWorkspaceUndoDueToChanges', "Could not undo '{0}' across all files because changes were made to {1}", element.label, paths.join(', '));
|
||||
this._notificationService.info(message);
|
||||
return;
|
||||
}
|
||||
|
||||
return this._dialogService.show(
|
||||
Severity.Info,
|
||||
nls.localize('confirmWorkspace', "Would you like to undo '{0}' across all files?", element.label),
|
||||
[
|
||||
nls.localize('ok', "Yes, change {0} files.", affectedEditStacks.length),
|
||||
nls.localize('nok', "No, change only this file.")
|
||||
]
|
||||
).then((result) => {
|
||||
if (result.choice === 0) {
|
||||
for (const editStack of affectedEditStacks) {
|
||||
editStack.past.pop();
|
||||
editStack.future.push(element);
|
||||
}
|
||||
return this._safeInvoke(element, () => element.actual.undo());
|
||||
} else {
|
||||
this._splitPastWorkspaceElement(element, null);
|
||||
return this.undo(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _resourceUndo(editStack: ResourceEditStack, element: ResourceStackElement): Promise<void> | void {
|
||||
editStack.past.pop();
|
||||
editStack.future.push(element);
|
||||
return this._safeInvoke(element, () => element.actual.undo());
|
||||
}
|
||||
|
||||
public undo(resource: URI): Promise<void> | void {
|
||||
const strResource = uriGetComparisonKey(resource);
|
||||
if (!this._editStacks.has(strResource)) {
|
||||
return;
|
||||
@@ -123,51 +323,10 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
}
|
||||
|
||||
const element = editStack.past[editStack.past.length - 1];
|
||||
|
||||
let replaceCurrentElement: IUndoRedoElement[] | null = null as IUndoRedoElement[] | null;
|
||||
try {
|
||||
element.actual.undo({
|
||||
replaceCurrentElement: (others: IUndoRedoElement[]): void => {
|
||||
replaceCurrentElement = others;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
editStack.past.pop();
|
||||
editStack.future.push(element);
|
||||
return;
|
||||
}
|
||||
|
||||
if (replaceCurrentElement === null) {
|
||||
// regular case
|
||||
editStack.past.pop();
|
||||
editStack.future.push(element);
|
||||
return;
|
||||
}
|
||||
|
||||
const replaceCurrentElementMap = new Map<string, StackElement>();
|
||||
for (const _replace of replaceCurrentElement) {
|
||||
const replace = new StackElement(_replace);
|
||||
for (const strResource of replace.strResources) {
|
||||
replaceCurrentElementMap.set(strResource, replace);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0, len = element.strResources.length; i < len; i++) {
|
||||
const strResource = element.strResources[i];
|
||||
if (this._editStacks.has(strResource)) {
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
for (let j = editStack.past.length - 1; j >= 0; j--) {
|
||||
if (editStack.past[j] === element) {
|
||||
if (replaceCurrentElementMap.has(strResource)) {
|
||||
editStack.past[j] = replaceCurrentElementMap.get(strResource)!;
|
||||
} else {
|
||||
editStack.past.splice(j, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (element.type === UndoRedoElementType.Workspace) {
|
||||
return this._workspaceUndo(resource, element);
|
||||
} else {
|
||||
return this._resourceUndo(editStack, element);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +339,49 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
return false;
|
||||
}
|
||||
|
||||
public redo(resource: URI): void {
|
||||
private _workspaceRedo(resource: URI, element: WorkspaceStackElement): Promise<void> | void {
|
||||
if (element.removedResources) {
|
||||
this._splitFutureWorkspaceElement(element, element.removedResources.set);
|
||||
const message = nls.localize('cannotWorkspaceRedo', "Could not redo '{0}' across all files. {1}", element.label, element.removedResources.createMessage());
|
||||
this._notificationService.info(message);
|
||||
return;
|
||||
}
|
||||
|
||||
// this must be the last future element in all the impacted resources!
|
||||
let affectedEditStacks: ResourceEditStack[] = [];
|
||||
for (const strResource of element.strResources) {
|
||||
affectedEditStacks.push(this._editStacks.get(strResource)!);
|
||||
}
|
||||
|
||||
let cannotRedoDueToResources: URI[] = [];
|
||||
for (const editStack of affectedEditStacks) {
|
||||
if (editStack.future.length === 0 || editStack.future[editStack.future.length - 1] !== element) {
|
||||
cannotRedoDueToResources.push(editStack.resource);
|
||||
}
|
||||
}
|
||||
|
||||
if (cannotRedoDueToResources.length > 0) {
|
||||
this._splitFutureWorkspaceElement(element, null);
|
||||
const paths = cannotRedoDueToResources.map(r => r.scheme === Schemas.file ? r.fsPath : r.path);
|
||||
const message = nls.localize('cannotWorkspaceRedoDueToChanges', "Could not redo '{0}' across all files because changes were made to {1}", element.label, paths.join(', '));
|
||||
this._notificationService.info(message);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const editStack of affectedEditStacks) {
|
||||
editStack.future.pop();
|
||||
editStack.past.push(element);
|
||||
}
|
||||
return this._safeInvoke(element, () => element.actual.redo());
|
||||
}
|
||||
|
||||
private _resourceRedo(editStack: ResourceEditStack, element: ResourceStackElement): Promise<void> | void {
|
||||
editStack.future.pop();
|
||||
editStack.past.push(element);
|
||||
return this._safeInvoke(element, () => element.actual.redo());
|
||||
}
|
||||
|
||||
public redo(resource: URI): Promise<void> | void {
|
||||
const strResource = uriGetComparisonKey(resource);
|
||||
if (!this._editStacks.has(strResource)) {
|
||||
return;
|
||||
@@ -192,51 +393,10 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
}
|
||||
|
||||
const element = editStack.future[editStack.future.length - 1];
|
||||
|
||||
let replaceCurrentElement: IUndoRedoElement[] | null = null as IUndoRedoElement[] | null;
|
||||
try {
|
||||
element.actual.redo({
|
||||
replaceCurrentElement: (others: IUndoRedoElement[]): void => {
|
||||
replaceCurrentElement = others;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
editStack.future.pop();
|
||||
editStack.past.push(element);
|
||||
return;
|
||||
}
|
||||
|
||||
if (replaceCurrentElement === null) {
|
||||
// regular case
|
||||
editStack.future.pop();
|
||||
editStack.past.push(element);
|
||||
return;
|
||||
}
|
||||
|
||||
const replaceCurrentElementMap = new Map<string, StackElement>();
|
||||
for (const _replace of replaceCurrentElement) {
|
||||
const replace = new StackElement(_replace);
|
||||
for (const strResource of replace.strResources) {
|
||||
replaceCurrentElementMap.set(strResource, replace);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0, len = element.strResources.length; i < len; i++) {
|
||||
const strResource = element.strResources[i];
|
||||
if (this._editStacks.has(strResource)) {
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
for (let j = editStack.future.length - 1; j >= 0; j--) {
|
||||
if (editStack.future[j] === element) {
|
||||
if (replaceCurrentElementMap.has(strResource)) {
|
||||
editStack.future[j] = replaceCurrentElementMap.get(strResource)!;
|
||||
} else {
|
||||
editStack.future.splice(j, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (element.type === UndoRedoElementType.Workspace) {
|
||||
return this._workspaceRedo(resource, element);
|
||||
} else {
|
||||
return this._resourceRedo(editStack, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,7 @@ import { ParseError, parse } from 'vs/base/common/json';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
const BACK_UP_MAX_AGE = 1000 * 60 * 60 * 24 * 30; /* 30 days */
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
type SyncSourceClassification = {
|
||||
source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
@@ -65,6 +64,7 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
@IUserDataSyncEnablementService protected readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService,
|
||||
@IConfigurationService protected readonly configurationService: IConfigurationService,
|
||||
) {
|
||||
super();
|
||||
this.syncFolder = joinPath(environmentService.userDataSyncHome, source);
|
||||
@@ -91,7 +91,7 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
|
||||
protected get enabled(): boolean { return this.userDataSyncEnablementService.isResourceEnabled(this.resourceKey); }
|
||||
|
||||
async sync(ref?: string): Promise<void> {
|
||||
async sync(ref?: string, donotUseLastSyncUserData?: boolean): Promise<void> {
|
||||
if (!this.enabled) {
|
||||
this.logService.info(`${this.source}: Skipped synchronizing ${this.source.toLowerCase()} as it is disabled.`);
|
||||
return;
|
||||
@@ -108,7 +108,7 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
this.logService.trace(`${this.source}: Started synchronizing ${this.source.toLowerCase()}...`);
|
||||
this.setStatus(SyncStatus.Syncing);
|
||||
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const lastSyncUserData = donotUseLastSyncUserData ? null : await this.getLastSyncUserData();
|
||||
const remoteUserData = ref && lastSyncUserData && lastSyncUserData.ref === ref ? lastSyncUserData : await this.getRemoteUserData(lastSyncUserData);
|
||||
|
||||
if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) {
|
||||
@@ -117,7 +117,19 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
throw new UserDataSyncError(localize('incompatible', "Cannot sync {0} as its version {1} is not compatible with cloud {2}", this.source, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.Incompatible, this.source);
|
||||
}
|
||||
|
||||
return this.doSync(remoteUserData, lastSyncUserData);
|
||||
try {
|
||||
await this.doSync(remoteUserData, lastSyncUserData);
|
||||
} catch (e) {
|
||||
if (e instanceof UserDataSyncError) {
|
||||
switch (e.code) {
|
||||
case UserDataSyncErrorCode.RemotePreconditionFailed:
|
||||
// Rejected as there is a new remote version. Syncing again,
|
||||
this.logService.info(`${this.source}: Failed to synchronize as there is a new remote version available. Synchronizing again...`);
|
||||
return this.sync(undefined, true);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async hasPreviouslySynced(): Promise<boolean> {
|
||||
@@ -191,15 +203,21 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
|
||||
protected async backupLocal(content: VSBuffer): Promise<void> {
|
||||
const resource = joinPath(this.syncFolder, toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, ''));
|
||||
await this.fileService.writeFile(resource, content);
|
||||
try {
|
||||
await this.fileService.writeFile(resource, content);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
this.cleanUpDelayer.trigger(() => this.cleanUpBackup());
|
||||
}
|
||||
|
||||
private async cleanUpBackup(): Promise<void> {
|
||||
const stat = await this.fileService.resolve(this.syncFolder);
|
||||
if (stat.children) {
|
||||
const toDelete = stat.children.filter(stat => {
|
||||
if (stat.isFile && /^\d{8}T\d{6}$/.test(stat.name)) {
|
||||
try {
|
||||
const stat = await this.fileService.resolve(this.syncFolder);
|
||||
if (stat.children) {
|
||||
const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}$/.test(stat.name)).sort();
|
||||
const backUpMaxAge = 1000 * 60 * 60 * 24 * (this.configurationService.getValue<number>('sync.localBackupDuration') || 30 /* Default 30 days */);
|
||||
let toDelete = all.filter(stat => {
|
||||
const ctime = stat.ctime || new Date(
|
||||
parseInt(stat.name.substring(0, 4)),
|
||||
parseInt(stat.name.substring(4, 6)) - 1,
|
||||
@@ -208,14 +226,19 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
parseInt(stat.name.substring(11, 13)),
|
||||
parseInt(stat.name.substring(13, 15))
|
||||
).getTime();
|
||||
return Date.now() - ctime > BACK_UP_MAX_AGE;
|
||||
return Date.now() - ctime > backUpMaxAge;
|
||||
});
|
||||
const remaining = all.length - toDelete.length;
|
||||
if (remaining < 10) {
|
||||
toDelete = toDelete.slice(10 - remaining);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
await Promise.all(toDelete.map(stat => {
|
||||
this.logService.info('Deleting from backup', stat.resource.path);
|
||||
this.fileService.del(stat.resource);
|
||||
}));
|
||||
await Promise.all(toDelete.map(stat => {
|
||||
this.logService.info('Deleting from backup', stat.resource.path);
|
||||
this.fileService.del(stat.resource);
|
||||
}));
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,8 +270,9 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
|
||||
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
) {
|
||||
super(source, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService);
|
||||
super(source, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService);
|
||||
this._register(this.fileService.watch(dirname(file)));
|
||||
this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e)));
|
||||
}
|
||||
@@ -346,8 +370,9 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
@IUserDataSyncUtilService protected readonly userDataSyncUtilService: IUserDataSyncUtilService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
) {
|
||||
super(file, source, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService);
|
||||
super(file, source, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService);
|
||||
}
|
||||
|
||||
protected hasErrors(content: string): boolean {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
@@ -46,11 +46,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
@IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
) {
|
||||
super(SyncSource.Extensions, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService);
|
||||
super(SyncSource.Extensions, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService);
|
||||
this._register(
|
||||
Event.debounce(
|
||||
Event.any<any>(
|
||||
@@ -154,11 +154,6 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
await this.apply(previewResult);
|
||||
} catch (e) {
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.RemotePreconditionFailed) {
|
||||
// Rejected as there is a new remote version. Syncing again,
|
||||
this.logService.info('Extensions: Failed to synchronize extensions as there is a new remote version available. Synchronizing again...');
|
||||
return this.sync();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
@@ -15,6 +15,7 @@ import { merge } from 'vs/platform/userDataSync/common/globalStateMerge';
|
||||
import { parse } from 'vs/base/common/json';
|
||||
import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
const argvProperties: string[] = ['locale'];
|
||||
|
||||
@@ -38,8 +39,9 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
) {
|
||||
super(SyncSource.GlobalState, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService);
|
||||
super(SyncSource.GlobalState, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService);
|
||||
this._register(this.fileService.watch(dirname(this.environmentService.argvResource)));
|
||||
this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire()));
|
||||
}
|
||||
@@ -129,11 +131,6 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
this.logService.trace('UI State: Finished synchronizing ui state.');
|
||||
} catch (e) {
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.RemotePreconditionFailed) {
|
||||
// Rejected as there is a new remote version. Syncing again,
|
||||
this.logService.info('UI State: Failed to synchronize ui state as there is a new remote version available. Synchronizing again...');
|
||||
return this.sync();
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
|
||||
@@ -36,14 +36,14 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
constructor(
|
||||
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
) {
|
||||
super(environmentService.keybindingsResource, SyncSource.Keybindings, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService);
|
||||
super(environmentService.keybindingsResource, SyncSource.Keybindings, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService);
|
||||
}
|
||||
|
||||
async pull(): Promise<void> {
|
||||
@@ -180,10 +180,6 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
if (e instanceof UserDataSyncError) {
|
||||
switch (e.code) {
|
||||
case UserDataSyncErrorCode.RemotePreconditionFailed:
|
||||
// Rejected as there is a new remote version. Syncing again,
|
||||
this.logService.info('Keybindings: Failed to synchronize keybindings as there is a new remote version available. Synchronizing again...');
|
||||
return this.sync();
|
||||
case UserDataSyncErrorCode.LocalPreconditionFailed:
|
||||
// Rejected as there is a new local version. Syncing again.
|
||||
this.logService.info('Keybindings: Failed to synchronize keybindings as there is a new local version available. Synchronizing again...');
|
||||
|
||||
@@ -10,8 +10,8 @@ import { values } from 'vs/base/common/map';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { FormattingOptions, Edit, getEOL } from 'vs/base/common/jsonFormatter';
|
||||
import * as contentUtil from 'vs/platform/userDataSync/common/content';
|
||||
import { IConflictSetting, CONFIGURATION_SYNC_STORE_KEY } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { firstIndex } from 'vs/base/common/arrays';
|
||||
import { IConflictSetting } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { firstIndex, distinct } from 'vs/base/common/arrays';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
|
||||
@@ -22,7 +22,7 @@ export interface IMergeResult {
|
||||
conflictsSettings: IConflictSetting[];
|
||||
}
|
||||
|
||||
export function getIgnoredSettings(configurationService: IConfigurationService, settingsContent?: string): string[] {
|
||||
export function getIgnoredSettings(defaultIgnoredSettings: string[], configurationService: IConfigurationService, settingsContent?: string): string[] {
|
||||
let value: string[] = [];
|
||||
if (settingsContent) {
|
||||
const setting = parse(settingsContent);
|
||||
@@ -42,7 +42,7 @@ export function getIgnoredSettings(configurationService: IConfigurationService,
|
||||
}
|
||||
}
|
||||
}
|
||||
return [CONFIGURATION_SYNC_STORE_KEY, ...added].filter(setting => removed.indexOf(setting) === -1);
|
||||
return distinct([...defaultIgnoredSettings, ...added,].filter(setting => removed.indexOf(setting) === -1));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import { edit } from 'vs/platform/userDataSync/common/content';
|
||||
import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
|
||||
interface ISettingsSyncContent {
|
||||
settings: string;
|
||||
@@ -51,11 +52,12 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
@IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
) {
|
||||
super(environmentService.settingsResource, SyncSource.Settings, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService);
|
||||
super(environmentService.settingsResource, SyncSource.Settings, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService);
|
||||
}
|
||||
|
||||
protected setStatus(status: SyncStatus): void {
|
||||
@@ -94,7 +96,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
// Update ignored settings from local file content
|
||||
const content = updateIgnoredSettings(remoteSettingsSyncContent.settings, fileContent ? fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils);
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
const content = updateIgnoredSettings(remoteSettingsSyncContent.settings, fileContent ? fileContent.value.toString() : '{}', ignoredSettings, formatUtils);
|
||||
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<IFileSyncPreviewResult>({
|
||||
fileContent,
|
||||
remoteUserData,
|
||||
@@ -136,7 +139,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
if (fileContent !== null) {
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
// Remove ignored settings
|
||||
const content = updateIgnoredSettings(fileContent.value.toString(), '{}', getIgnoredSettings(this.configurationService), formatUtils);
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
const content = updateIgnoredSettings(fileContent.value.toString(), '{}', ignoredSettings, formatUtils);
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
|
||||
@@ -192,7 +196,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
if (preview && content !== null) {
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
// remove ignored settings from the remote content for preview
|
||||
content = updateIgnoredSettings(content, '{}', getIgnoredSettings(this.configurationService), formatUtils);
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
content = updateIgnoredSettings(content, '{}', ignoredSettings, formatUtils);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
@@ -203,7 +208,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
this.cancel();
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
// Add ignored settings from local file content
|
||||
content = updateIgnoredSettings(content, preview.fileContent ? preview.fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils);
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
content = updateIgnoredSettings(content, preview.fileContent ? preview.fileContent.value.toString() : '{}', ignoredSettings, formatUtils);
|
||||
this.syncPreviewResultPromise = createCancelablePromise(async () => ({ ...preview, content }));
|
||||
await this.apply(true);
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
@@ -237,10 +243,6 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
if (e instanceof UserDataSyncError) {
|
||||
switch (e.code) {
|
||||
case UserDataSyncErrorCode.RemotePreconditionFailed:
|
||||
// Rejected as there is a new remote version. Syncing again,
|
||||
this.logService.info('Settings: Failed to synchronize settings as there is a new remote version available. Synchronizing again...');
|
||||
return this.sync();
|
||||
case UserDataSyncErrorCode.LocalPreconditionFailed:
|
||||
// Rejected as there is a new local version. Syncing again.
|
||||
this.logService.info('Settings: Failed to synchronize settings as there is a new local version available. Synchronizing again...');
|
||||
@@ -271,7 +273,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
// Update ignored settings from remote
|
||||
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
|
||||
content = updateIgnoredSettings(content, remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : '{}', getIgnoredSettings(this.configurationService, content), formatUtils);
|
||||
const ignoredSettings = await this.getIgnoredSettings(content);
|
||||
content = updateIgnoredSettings(content, remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : '{}', ignoredSettings, formatUtils);
|
||||
this.logService.trace('Settings: Updating remote settings...');
|
||||
remoteUserData = await this.updateRemoteUserData(JSON.stringify(<ISettingsSyncContent>{ settings: content }), forcePush ? null : remoteUserData.ref);
|
||||
this.logService.info('Settings: Updated remote settings');
|
||||
@@ -317,7 +320,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
const localContent: string = fileContent ? fileContent.value.toString() : '{}';
|
||||
this.validateContent(localContent);
|
||||
this.logService.trace('Settings: Merging remote settings with local settings...');
|
||||
const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formattingOptions);
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, ignoredSettings, resolvedConflicts, formattingOptions);
|
||||
content = result.localContent || result.remoteContent;
|
||||
hasLocalChanged = result.localContent !== null;
|
||||
hasRemoteChanged = result.remoteContent !== null;
|
||||
@@ -334,7 +338,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
|
||||
if (content && !token.isCancellationRequested) {
|
||||
// Remove the ignored settings from the preview.
|
||||
const previewContent = updateIgnoredSettings(content, '{}', getIgnoredSettings(this.configurationService), formattingOptions);
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
const previewContent = updateIgnoredSettings(content, '{}', ignoredSettings, formattingOptions);
|
||||
await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(previewContent));
|
||||
}
|
||||
|
||||
@@ -356,6 +361,21 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
return null;
|
||||
}
|
||||
|
||||
private _defaultIgnoredSettings: Promise<string[]> | undefined = undefined;
|
||||
protected async getIgnoredSettings(content?: string): Promise<string[]> {
|
||||
if (!this._defaultIgnoredSettings) {
|
||||
this._defaultIgnoredSettings = this.userDataSyncUtilService.resolveDefaultIgnoredSettings();
|
||||
const disposable = Event.any<any>(
|
||||
Event.filter(this.extensionManagementService.onDidInstallExtension, (e => !!e.gallery)),
|
||||
Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error)))(() => {
|
||||
disposable.dispose();
|
||||
this._defaultIgnoredSettings = undefined;
|
||||
});
|
||||
}
|
||||
const defaultIgnoredSettings = await this._defaultIgnoredSettings;
|
||||
return getIgnoredSettings(defaultIgnoredSettings, this.configurationService, content);
|
||||
}
|
||||
|
||||
private validateContent(content: string): void {
|
||||
if (this.hasErrors(content)) {
|
||||
throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source);
|
||||
|
||||
@@ -36,6 +36,12 @@ export interface ISyncConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
export function getDefaultIgnoredSettings(): string[] {
|
||||
const allSettings = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
|
||||
const machineSettings = Object.keys(allSettings).filter(setting => allSettings[setting].scope === ConfigurationScope.MACHINE || allSettings[setting].scope === ConfigurationScope.MACHINE_OVERRIDABLE);
|
||||
return [CONFIGURATION_SYNC_STORE_KEY, ...machineSettings];
|
||||
}
|
||||
|
||||
export function registerConfiguration(): IDisposable {
|
||||
const ignoredSettingsSchemaId = 'vscode://schemas/ignoredSettings';
|
||||
const ignoredExtensionsSchemaId = 'vscode://schemas/ignoredExtensions';
|
||||
@@ -105,11 +111,12 @@ export function registerConfiguration(): IDisposable {
|
||||
});
|
||||
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
|
||||
const registerIgnoredSettingsSchema = () => {
|
||||
const defaultIgnoreSettings = getDefaultIgnoredSettings().filter(s => s !== CONFIGURATION_SYNC_STORE_KEY);
|
||||
const ignoredSettingsSchema: IJSONSchema = {
|
||||
items: {
|
||||
type: 'string',
|
||||
enum: Object.keys(allSettings.properties)
|
||||
}
|
||||
enum: [...Object.keys(allSettings.properties).filter(setting => defaultIgnoreSettings.indexOf(setting) === -1), ...defaultIgnoreSettings.map(setting => `-${setting}`)]
|
||||
},
|
||||
};
|
||||
jsonRegistry.registerSchema(ignoredSettingsSchemaId, ignoredSettingsSchema);
|
||||
};
|
||||
@@ -180,6 +187,7 @@ export enum UserDataSyncErrorCode {
|
||||
// Local Errors
|
||||
LocalPreconditionFailed = 'LocalPreconditionFailed',
|
||||
LocalInvalidContent = 'LocalInvalidContent',
|
||||
LocalError = 'LocalError',
|
||||
Incompatible = 'Incompatible',
|
||||
|
||||
Unknown = 'Unknown',
|
||||
@@ -286,6 +294,7 @@ export interface IUserDataSyncService {
|
||||
readonly onDidChangeConflicts: Event<SyncSource[]>;
|
||||
|
||||
readonly onDidChangeLocal: Event<void>;
|
||||
readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]>;
|
||||
|
||||
readonly lastSyncTime: number | undefined;
|
||||
readonly onDidChangeLastSyncTime: Event<number>;
|
||||
@@ -313,6 +322,7 @@ export interface IUserDataSyncUtilService {
|
||||
_serviceBrand: undefined;
|
||||
resolveUserBindings(userbindings: string[]): Promise<IStringDictionary<string>>;
|
||||
resolveFormattingOptions(resource: URI): Promise<FormattingOptions>;
|
||||
resolveDefaultIgnoredSettings(): Promise<string[]>;
|
||||
}
|
||||
|
||||
export const IUserDataSyncLogService = createDecorator<IUserDataSyncLogService>('IUserDataSyncLogService');
|
||||
|
||||
@@ -20,6 +20,7 @@ export class UserDataSyncChannel implements IServerChannel {
|
||||
case 'onDidChangeConflicts': return this.service.onDidChangeConflicts;
|
||||
case 'onDidChangeLocal': return this.service.onDidChangeLocal;
|
||||
case 'onDidChangeLastSyncTime': return this.service.onDidChangeLastSyncTime;
|
||||
case 'onSyncErrors': return this.service.onSyncErrors;
|
||||
}
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
@@ -101,6 +102,7 @@ export class UserDataSycnUtilServiceChannel implements IServerChannel {
|
||||
|
||||
call(context: any, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'resolveDefaultIgnoredSettings': return this.service.resolveDefaultIgnoredSettings();
|
||||
case 'resolveUserKeybindings': return this.service.resolveUserBindings(args[0]);
|
||||
case 'resolveFormattingOptions': return this.service.resolveFormattingOptions(URI.revive(args[0]));
|
||||
}
|
||||
@@ -115,6 +117,10 @@ export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService {
|
||||
constructor(private readonly channel: IChannel) {
|
||||
}
|
||||
|
||||
async resolveDefaultIgnoredSettings(): Promise<string[]> {
|
||||
return this.channel.call('resolveDefaultIgnoredSettings');
|
||||
}
|
||||
|
||||
async resolveUserBindings(userbindings: string[]): Promise<IStringDictionary<string>> {
|
||||
return this.channel.call('resolveUserKeybindings', [userbindings]);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
private _onDidChangeConflicts: Emitter<SyncSource[]> = this._register(new Emitter<SyncSource[]>());
|
||||
readonly onDidChangeConflicts: Event<SyncSource[]> = this._onDidChangeConflicts.event;
|
||||
|
||||
private _syncErrors: [SyncSource, UserDataSyncError][] = [];
|
||||
private _onSyncErrors: Emitter<[SyncSource, UserDataSyncError][]> = this._register(new Emitter<[SyncSource, UserDataSyncError][]>());
|
||||
readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]> = this._onSyncErrors.event;
|
||||
|
||||
private _lastSyncTime: number | undefined = undefined;
|
||||
get lastSyncTime(): number | undefined { return this._lastSyncTime; }
|
||||
private _onDidChangeLastSyncTime: Emitter<number> = this._register(new Emitter<number>());
|
||||
@@ -82,6 +86,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
this.handleSyncError(e, synchroniser.source);
|
||||
}
|
||||
}
|
||||
this.updateLastSyncTime();
|
||||
}
|
||||
|
||||
async push(): Promise<void> {
|
||||
@@ -93,12 +98,14 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
this.handleSyncError(e, synchroniser.source);
|
||||
}
|
||||
}
|
||||
this.updateLastSyncTime();
|
||||
}
|
||||
|
||||
async sync(): Promise<void> {
|
||||
await this.checkEnablement();
|
||||
|
||||
const startTime = new Date().getTime();
|
||||
this._syncErrors = [];
|
||||
try {
|
||||
this.logService.trace('Sync started.');
|
||||
if (this.status !== SyncStatus.HasConflicts) {
|
||||
@@ -124,6 +131,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
await synchroniser.sync(manifest && manifest.latest ? manifest.latest[synchroniser.resourceKey] : undefined);
|
||||
} catch (e) {
|
||||
this.handleSyncError(e, synchroniser.source);
|
||||
this._syncErrors.push([synchroniser.source, UserDataSyncError.toUserDataSyncError(e)]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,9 +146,11 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
}
|
||||
|
||||
this.logService.info(`Sync done. Took ${new Date().getTime() - startTime}ms`);
|
||||
this.updateLastSyncTime();
|
||||
|
||||
} finally {
|
||||
this.updateStatus();
|
||||
this._onSyncErrors.fire(this._syncErrors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,8 +249,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
if (this._status !== status) {
|
||||
this._status = status;
|
||||
this._onDidChangeStatus.fire(status);
|
||||
if (oldStatus !== SyncStatus.Uninitialized && this.status === SyncStatus.Idle) {
|
||||
this.updateLastSyncTime(new Date().getTime());
|
||||
if (oldStatus === SyncStatus.HasConflicts) {
|
||||
this.updateLastSyncTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,11 +278,11 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
return SyncStatus.Idle;
|
||||
}
|
||||
|
||||
private updateLastSyncTime(lastSyncTime: number): void {
|
||||
if (this._lastSyncTime !== lastSyncTime) {
|
||||
this._lastSyncTime = lastSyncTime;
|
||||
this.storageService.store(LAST_SYNC_TIME_KEY, lastSyncTime, StorageScope.GLOBAL);
|
||||
this._onDidChangeLastSyncTime.fire(lastSyncTime);
|
||||
private updateLastSyncTime(): void {
|
||||
if (this.status === SyncStatus.Idle) {
|
||||
this._lastSyncTime = new Date().getTime();
|
||||
this.storageService.store(LAST_SYNC_TIME_KEY, this._lastSyncTime, StorageScope.GLOBAL);
|
||||
this._onDidChangeLastSyncTime.fire(this._lastSyncTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { IRequestService } from 'vs/platform/request/common/request';
|
||||
import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IUserData, ResourceKey, IUserDataManifest, ALL_RESOURCE_KEYS, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserData, ResourceKey, IUserDataManifest, ALL_RESOURCE_KEYS, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService, getDefaultIgnoredSettings } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { bufferToStream, VSBuffer } from 'vs/base/common/buffer';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
|
||||
@@ -231,6 +231,10 @@ export class TestUserDataSyncUtilService implements IUserDataSyncUtilService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
async resolveDefaultIgnoredSettings(): Promise<string[]> {
|
||||
return getDefaultIgnoredSettings();
|
||||
}
|
||||
|
||||
async resolveUserBindings(userbindings: string[]): Promise<IStringDictionary<string>> {
|
||||
const keys: IStringDictionary<string> = {};
|
||||
for (const keybinding of userbindings) {
|
||||
|
||||
Reference in New Issue
Block a user