mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Merge from vscode 073a24de05773f2261f89172987002dc0ae2f1cd (#9711)
This commit is contained in:
@@ -125,6 +125,7 @@ export class MenuId {
|
||||
static readonly TimelineItemContext = new MenuId('TimelineItemContext');
|
||||
static readonly TimelineTitle = new MenuId('TimelineTitle');
|
||||
static readonly TimelineTitleContext = new MenuId('TimelineTitleContext');
|
||||
static readonly AccountsContext = new MenuId('AccountsContext');
|
||||
|
||||
readonly id: number;
|
||||
readonly _debugName: string;
|
||||
|
||||
@@ -332,7 +332,11 @@ export class ElectronMainService implements IElectronMainService {
|
||||
}
|
||||
|
||||
async closeWindow(windowId: number | undefined): Promise<void> {
|
||||
const window = this.windowById(windowId);
|
||||
this.closeWindowById(windowId, windowId);
|
||||
}
|
||||
|
||||
async closeWindowById(currentWindowId: number | undefined, targetWindowId?: number | undefined): Promise<void> {
|
||||
const window = this.windowById(targetWindowId);
|
||||
if (window) {
|
||||
return window.win.close();
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ export interface IElectronService {
|
||||
relaunch(options?: { addArgs?: string[], removeArgs?: string[] }): Promise<void>;
|
||||
reload(options?: { disableExtensions?: boolean }): Promise<void>;
|
||||
closeWindow(): Promise<void>;
|
||||
closeWindowById(windowId: number): Promise<void>;
|
||||
quit(): Promise<void>;
|
||||
|
||||
// Development
|
||||
|
||||
@@ -151,7 +151,7 @@ export class InstantiationService implements IInstantiationService {
|
||||
graph.lookupOrInsertNode(item);
|
||||
|
||||
// a weak but working heuristic for cycle checks
|
||||
if (cycleCount++ > 200) {
|
||||
if (cycleCount++ > 1000) {
|
||||
throw new CyclicDependencyError(graph);
|
||||
}
|
||||
|
||||
|
||||
@@ -533,6 +533,7 @@ export class Menubar {
|
||||
[
|
||||
minimize,
|
||||
zoom,
|
||||
__separator__(),
|
||||
switchWindow,
|
||||
...nativeTabMenuItems,
|
||||
__separator__(),
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import { localize } from 'vs/nls';
|
||||
import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { PickerQuickAccessProvider, IPickerQuickAccessItem, IPickerQuickAccessProviderOptions } from 'vs/platform/quickinput/browser/pickerQuickAccess';
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { or, matchesPrefix, matchesWords, matchesContiguousSubString } from 'vs/base/common/filters';
|
||||
@@ -22,8 +21,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { isFirefox } from 'vs/base/browser/browser';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
|
||||
export interface ICommandQuickPick extends IPickerQuickAccessItem {
|
||||
commandId: string;
|
||||
@@ -74,12 +71,9 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicates
|
||||
const distinctCommandPicks = distinct(filteredCommandPicks, pick => `${pick.label}${pick.commandId}`);
|
||||
|
||||
// Add description to commands that have duplicate labels
|
||||
const mapLabelToCommand = new Map<string, ICommandQuickPick>();
|
||||
for (const commandPick of distinctCommandPicks) {
|
||||
for (const commandPick of filteredCommandPicks) {
|
||||
const existingCommandForLabel = mapLabelToCommand.get(commandPick.label);
|
||||
if (existingCommandForLabel) {
|
||||
commandPick.description = commandPick.commandId;
|
||||
@@ -90,7 +84,7 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc
|
||||
}
|
||||
|
||||
// Sort by MRU order and fallback to name otherwise
|
||||
distinctCommandPicks.sort((commandPickA, commandPickB) => {
|
||||
filteredCommandPicks.sort((commandPickA, commandPickB) => {
|
||||
const commandACounter = this.commandsHistory.peek(commandPickA.commandId);
|
||||
const commandBCounter = this.commandsHistory.peek(commandPickB.commandId);
|
||||
|
||||
@@ -113,8 +107,8 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc
|
||||
const commandPicks: Array<ICommandQuickPick | IQuickPickSeparator> = [];
|
||||
|
||||
let addSeparator = false;
|
||||
for (let i = 0; i < distinctCommandPicks.length; i++) {
|
||||
const commandPick = distinctCommandPicks[i];
|
||||
for (let i = 0; i < filteredCommandPicks.length; i++) {
|
||||
const commandPick = filteredCommandPicks[i];
|
||||
const keybinding = this.keybindingService.lookupKeybinding(commandPick.commandId);
|
||||
const ariaLabel = keybinding ?
|
||||
localize('commandPickAriaLabelWithKeybinding', "{0}, {1}, commands picker", commandPick.label, keybinding.getAriaLabel()) :
|
||||
@@ -143,13 +137,6 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc
|
||||
// Add to history
|
||||
this.commandsHistory.push(commandPick.commandId);
|
||||
|
||||
if (!isFirefox) {
|
||||
// Use a timeout to give the quick open widget a chance to close itself first
|
||||
// Firefox: since the browser is quite picky for certain commands, we do not
|
||||
// use a timeout (https://github.com/microsoft/vscode/issues/83288)
|
||||
await timeout(50);
|
||||
}
|
||||
|
||||
// Telementry
|
||||
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', {
|
||||
id: commandPick.commandId,
|
||||
@@ -191,7 +178,7 @@ interface ICommandsQuickAccessConfiguration {
|
||||
};
|
||||
}
|
||||
|
||||
class CommandsHistory extends Disposable {
|
||||
export class CommandsHistory extends Disposable {
|
||||
|
||||
static readonly DEFAULT_COMMANDS_HISTORY_LENGTH = 50;
|
||||
|
||||
|
||||
@@ -66,6 +66,10 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider {
|
||||
const editorProviders: IHelpQuickAccessPickItem[] = [];
|
||||
|
||||
for (const provider of this.registry.getQuickAccessProviders().sort((providerA, providerB) => providerA.prefix.localeCompare(providerB.prefix))) {
|
||||
if (provider.prefix === HelpQuickAccessProvider.PREFIX) {
|
||||
continue; // exclude help which is already active
|
||||
}
|
||||
|
||||
for (const helpEntry of provider.helpEntries) {
|
||||
const prefix = helpEntry.prefix || provider.prefix;
|
||||
const label = prefix || '\u2026' /* ... */;
|
||||
|
||||
@@ -25,7 +25,12 @@ export enum TriggerAction {
|
||||
/**
|
||||
* Update the results of the picker.
|
||||
*/
|
||||
REFRESH_PICKER
|
||||
REFRESH_PICKER,
|
||||
|
||||
/**
|
||||
* Remove the item from the picker.
|
||||
*/
|
||||
REMOVE_ITEM
|
||||
}
|
||||
|
||||
export interface IPickerQuickAccessItem extends IQuickPickItem {
|
||||
@@ -211,6 +216,14 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
|
||||
case TriggerAction.REFRESH_PICKER:
|
||||
updatePickerItems();
|
||||
break;
|
||||
case TriggerAction.REMOVE_ITEM:
|
||||
const index = picker.items.indexOf(item);
|
||||
if (index !== -1) {
|
||||
const items = picker.items.slice();
|
||||
items.splice(index, 1);
|
||||
picker.items = items;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,18 +5,32 @@
|
||||
|
||||
import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IQuickAccessController, IQuickAccessProvider, IQuickAccessRegistry, Extensions, IQuickAccessProviderDescriptor, IQuickAccessOptions } from 'vs/platform/quickinput/common/quickAccess';
|
||||
import { IQuickAccessController, IQuickAccessProvider, IQuickAccessRegistry, Extensions, IQuickAccessProviderDescriptor, IQuickAccessOptions, DefaultQuickAccessFilterValue } from 'vs/platform/quickinput/common/quickAccess';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
|
||||
interface IInternalQuickAccessOptions extends IQuickAccessOptions {
|
||||
|
||||
/**
|
||||
* Internal option to not rewrite the filter value at all but use it as is.
|
||||
*/
|
||||
preserveFilterValue?: boolean;
|
||||
}
|
||||
|
||||
export class QuickAccessController extends Disposable implements IQuickAccessController {
|
||||
|
||||
private readonly registry = Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess);
|
||||
private readonly mapProviderToDescriptor = new Map<IQuickAccessProviderDescriptor, IQuickAccessProvider>();
|
||||
|
||||
private lastActivePicker: IQuickPick<IQuickPickItem> | undefined = undefined;
|
||||
private readonly lastAcceptedPickerValues = new Map<IQuickAccessProviderDescriptor, string>();
|
||||
|
||||
private visibleQuickAccess: {
|
||||
picker: IQuickPick<IQuickPickItem>,
|
||||
descriptor: IQuickAccessProviderDescriptor | undefined,
|
||||
value: string
|
||||
} | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@@ -25,33 +39,131 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
|
||||
super();
|
||||
}
|
||||
|
||||
show(value = '', options?: IQuickAccessOptions): void {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
// Hide any previous picker if any
|
||||
this.lastActivePicker?.hide();
|
||||
show(value = '', options?: IInternalQuickAccessOptions): void {
|
||||
|
||||
// Find provider for the value to show
|
||||
const [provider, descriptor] = this.getOrInstantiateProvider(value);
|
||||
|
||||
// Return early if quick access is already showing on that
|
||||
// same prefix and simply take over the filter value if it
|
||||
// is more specific and select it for the user to be able
|
||||
// to type over
|
||||
const visibleQuickAccess = this.visibleQuickAccess;
|
||||
const visibleDescriptor = visibleQuickAccess?.descriptor;
|
||||
if (visibleQuickAccess && descriptor && visibleDescriptor === descriptor) {
|
||||
|
||||
// Take over the value only if it is not matching
|
||||
// the existing provider prefix or we are to preserve
|
||||
if (value !== descriptor.prefix && !options?.preserveFilterValue) {
|
||||
visibleQuickAccess.picker.value = value;
|
||||
}
|
||||
|
||||
// Always adjust selection
|
||||
this.adjustValueSelection(visibleQuickAccess.picker, descriptor, options);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Rewrite the filter value based on certain rules unless disabled
|
||||
if (descriptor && !options?.preserveFilterValue) {
|
||||
let newValue: string | undefined = undefined;
|
||||
|
||||
// If we have a visible provider with a value, take it's filter value but
|
||||
// rewrite to new provider prefix in case they differ
|
||||
if (visibleQuickAccess && visibleDescriptor && visibleDescriptor !== descriptor) {
|
||||
const newValueCandidateWithoutPrefix = visibleQuickAccess.value.substr(visibleDescriptor.prefix.length);
|
||||
if (newValueCandidateWithoutPrefix) {
|
||||
newValue = `${descriptor.prefix}${newValueCandidateWithoutPrefix}`;
|
||||
}
|
||||
}
|
||||
|
||||
// If the new provider wants to preserve the filter, take it's last remembered value
|
||||
// If the new provider wants to define the filter, take it as is
|
||||
if (!newValue) {
|
||||
const defaultFilterValue = provider?.defaultFilterValue;
|
||||
if (defaultFilterValue === DefaultQuickAccessFilterValue.LAST) {
|
||||
newValue = this.lastAcceptedPickerValues.get(descriptor);
|
||||
} else if (typeof defaultFilterValue === 'string') {
|
||||
newValue = `${descriptor.prefix}${defaultFilterValue}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof newValue === 'string') {
|
||||
value = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a picker for the provider to use with the initial value
|
||||
// and adjust the filtering to exclude the prefix from filtering
|
||||
const disposables = new DisposableStore();
|
||||
const picker = disposables.add(this.quickInputService.createQuickPick());
|
||||
picker.placeholder = descriptor?.placeholder;
|
||||
picker.value = value;
|
||||
this.adjustValueSelection(picker, descriptor, options);
|
||||
picker.placeholder = descriptor?.placeholder;
|
||||
picker.quickNavigate = options?.quickNavigateConfiguration;
|
||||
picker.valueSelection = options?.inputSelection ? [options.inputSelection.start, options.inputSelection.end] : [value.length, value.length];
|
||||
picker.hideInput = !!picker.quickNavigate && !visibleQuickAccess; // only hide input if there was no picker opened already
|
||||
picker.autoFocusSecondEntry = !!options?.quickNavigateConfiguration || !!options?.autoFocus?.autoFocusSecondEntry;
|
||||
picker.contextKey = descriptor?.contextKey;
|
||||
picker.filterValue = (value: string) => value.substring(descriptor ? descriptor.prefix.length : 0);
|
||||
|
||||
// Remember as last active picker and clean up once picker get's disposed
|
||||
this.lastActivePicker = picker;
|
||||
// Register listeners
|
||||
const cancellationToken = this.registerPickerListeners(disposables, picker, provider, descriptor, value);
|
||||
|
||||
// Ask provider to fill the picker as needed if we have one
|
||||
if (provider) {
|
||||
disposables.add(provider.provide(picker, cancellationToken));
|
||||
}
|
||||
|
||||
// Finally, show the picker. This is important because a provider
|
||||
// may not call this and then our disposables would leak that rely
|
||||
// on the onDidHide event.
|
||||
picker.show();
|
||||
}
|
||||
|
||||
private adjustValueSelection(picker: IQuickPick<IQuickPickItem>, descriptor?: IQuickAccessProviderDescriptor, options?: IInternalQuickAccessOptions): void {
|
||||
let valueSelection: [number, number];
|
||||
|
||||
// Preserve: just always put the cursor at the end
|
||||
if (options?.preserveFilterValue) {
|
||||
valueSelection = [picker.value.length, picker.value.length];
|
||||
}
|
||||
|
||||
// Otherwise: select the value up until the prefix
|
||||
else {
|
||||
valueSelection = [descriptor?.prefix.length ?? 0, picker.value.length];
|
||||
}
|
||||
|
||||
picker.valueSelection = valueSelection;
|
||||
}
|
||||
|
||||
private registerPickerListeners(disposables: DisposableStore, picker: IQuickPick<IQuickPickItem>, provider: IQuickAccessProvider | undefined, descriptor: IQuickAccessProviderDescriptor | undefined, value: string): CancellationToken {
|
||||
|
||||
// Remember as last visible picker and clean up once picker get's disposed
|
||||
const visibleQuickAccess = this.visibleQuickAccess = { picker, descriptor, value };
|
||||
disposables.add(toDisposable(() => {
|
||||
if (picker === this.lastActivePicker) {
|
||||
this.lastActivePicker = undefined;
|
||||
if (visibleQuickAccess === this.visibleQuickAccess) {
|
||||
this.visibleQuickAccess = undefined;
|
||||
}
|
||||
}));
|
||||
|
||||
// Whenever the value changes, check if the provider has
|
||||
// changed and if so - re-create the picker from the beginning
|
||||
disposables.add(picker.onDidChangeValue(value => {
|
||||
const [providerForValue] = this.getOrInstantiateProvider(value);
|
||||
if (providerForValue !== provider) {
|
||||
this.show(value, { preserveFilterValue: true } /* do not rewrite value from user typing! */);
|
||||
} else {
|
||||
visibleQuickAccess.value = value; // remember the value in our visible one
|
||||
}
|
||||
}));
|
||||
|
||||
// Remember picker input for future use when accepting
|
||||
if (descriptor) {
|
||||
disposables.add(picker.onDidAccept(() => {
|
||||
this.lastAcceptedPickerValues.set(descriptor, picker.value);
|
||||
}));
|
||||
}
|
||||
|
||||
// Create a cancellation token source that is valid as long as the
|
||||
// picker has not been closed without picking an item
|
||||
const cts = disposables.add(new CancellationTokenSource());
|
||||
@@ -64,24 +176,7 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
|
||||
disposables.dispose();
|
||||
});
|
||||
|
||||
// Whenever the value changes, check if the provider has
|
||||
// changed and if so - re-create the picker from the beginning
|
||||
disposables.add(picker.onDidChangeValue(value => {
|
||||
const [providerForValue] = this.getOrInstantiateProvider(value);
|
||||
if (providerForValue !== provider) {
|
||||
this.show(value);
|
||||
}
|
||||
}));
|
||||
|
||||
// Ask provider to fill the picker as needed if we have one
|
||||
if (provider) {
|
||||
disposables.add(provider.provide(picker, cts.token));
|
||||
}
|
||||
|
||||
// Finally, show the picker. This is important because a provider
|
||||
// may not call this and then our disposables would leak that rely
|
||||
// on the onDidHide event.
|
||||
picker.show();
|
||||
return cts.token;
|
||||
}
|
||||
|
||||
private getOrInstantiateProvider(value: string): [IQuickAccessProvider | undefined, IQuickAccessProviderDescriptor | undefined] {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IQuickInputService, IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInputButton, IInputBox, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IQuickInputService, IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInputButton, IInputBox, QuickPickInput, IKeyMods } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IThemeService, Themable } from 'vs/platform/theme/common/themeService';
|
||||
@@ -154,8 +154,8 @@ export class QuickInputService extends Themable implements IQuickInputService {
|
||||
this.controller.navigate(next, quickNavigate);
|
||||
}
|
||||
|
||||
accept() {
|
||||
return this.controller.accept();
|
||||
accept(keyMods?: IKeyMods) {
|
||||
return this.controller.accept(keyMods);
|
||||
}
|
||||
|
||||
back() {
|
||||
|
||||
@@ -12,15 +12,15 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IQuickAccessOptions {
|
||||
|
||||
/**
|
||||
* Allows to control the part of text in the input field that should be selected.
|
||||
*/
|
||||
inputSelection?: { start: number; end: number; };
|
||||
|
||||
/**
|
||||
* Allows to enable quick navigate support in quick input.
|
||||
*/
|
||||
quickNavigateConfiguration?: IQuickNavigateConfiguration;
|
||||
|
||||
/**
|
||||
* Wether to select the second pick item by default instead of the first.
|
||||
*/
|
||||
autoFocus?: { autoFocusSecondEntry?: boolean }
|
||||
}
|
||||
|
||||
export interface IQuickAccessController {
|
||||
@@ -31,8 +31,32 @@ export interface IQuickAccessController {
|
||||
show(value?: string, options?: IQuickAccessOptions): void;
|
||||
}
|
||||
|
||||
export enum DefaultQuickAccessFilterValue {
|
||||
|
||||
/**
|
||||
* Keep the value as it is given to quick access.
|
||||
*/
|
||||
PRESERVE = 0,
|
||||
|
||||
/**
|
||||
* Use the value that was used last time something was accepted from the picker.
|
||||
*/
|
||||
LAST = 1
|
||||
}
|
||||
|
||||
export interface IQuickAccessProvider {
|
||||
|
||||
/**
|
||||
* Allows to set a default filter value when the provider opens. This can be:
|
||||
* - `undefined` to not specify any default value
|
||||
* - `DefaultFilterValues.PRESERVE` to use the value that was last typed
|
||||
* - `string` for the actual value to use
|
||||
*
|
||||
* Note: the default filter will only be used if quick access was opened with
|
||||
* the exact prefix of the provider. Otherwise the filter value is preserved.
|
||||
*/
|
||||
readonly defaultFilterValue?: string | DefaultQuickAccessFilterValue;
|
||||
|
||||
/**
|
||||
* Called whenever a prefix was typed into quick pick that matches the provider.
|
||||
*
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInputButton, IInputBox, QuickPickInput } from 'vs/base/parts/quickinput/common/quickInput';
|
||||
import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInputButton, IInputBox, QuickPickInput, IKeyMods } from 'vs/base/parts/quickinput/common/quickInput';
|
||||
import { IQuickAccessController } from 'vs/platform/quickinput/common/quickAccess';
|
||||
|
||||
export * from 'vs/base/parts/quickinput/common/quickInput';
|
||||
@@ -84,8 +84,11 @@ export interface IQuickInputService {
|
||||
|
||||
/**
|
||||
* Accept the selected item.
|
||||
*
|
||||
* @param keyMods allows to override the state of key
|
||||
* modifiers that should be present when invoking.
|
||||
*/
|
||||
accept(): Promise<void>;
|
||||
accept(keyMods?: IKeyMods): Promise<void>;
|
||||
|
||||
/**
|
||||
* Cancels quick input and closes it.
|
||||
|
||||
@@ -30,6 +30,13 @@ export interface IWorkspaceUndoRedoElement {
|
||||
split(): IResourceUndoRedoElement[];
|
||||
}
|
||||
|
||||
export type IUndoRedoElement = IResourceUndoRedoElement | IWorkspaceUndoRedoElement;
|
||||
|
||||
export interface IPastFutureElements {
|
||||
past: IUndoRedoElement[];
|
||||
future: IUndoRedoElement[];
|
||||
}
|
||||
|
||||
export interface IUndoRedoService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
@@ -37,12 +44,18 @@ export interface IUndoRedoService {
|
||||
* Add a new element to the `undo` stack.
|
||||
* This will destroy the `redo` stack.
|
||||
*/
|
||||
pushElement(element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement): void;
|
||||
pushElement(element: IUndoRedoElement): void;
|
||||
|
||||
/**
|
||||
* Get the last pushed element. If the last pushed element has been undone, returns null.
|
||||
*/
|
||||
getLastElement(resource: URI): IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null;
|
||||
getLastElement(resource: URI): IUndoRedoElement | null;
|
||||
|
||||
getElements(resource: URI): IPastFutureElements;
|
||||
|
||||
hasElements(resource: URI): boolean;
|
||||
|
||||
setElementsIsValid(resource: URI, isValid: boolean): void;
|
||||
|
||||
/**
|
||||
* Remove elements that target `resource`.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IUndoRedoService, IResourceUndoRedoElement, IWorkspaceUndoRedoElement, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { IUndoRedoService, IResourceUndoRedoElement, IWorkspaceUndoRedoElement, UndoRedoElementType, IUndoRedoElement, IPastFutureElements } 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';
|
||||
@@ -23,6 +23,7 @@ class ResourceStackElement {
|
||||
public readonly strResource: string;
|
||||
public readonly resources: URI[];
|
||||
public readonly strResources: string[];
|
||||
public isValid: boolean;
|
||||
|
||||
constructor(actual: IResourceUndoRedoElement) {
|
||||
this.actual = actual;
|
||||
@@ -31,6 +32,11 @@ class ResourceStackElement {
|
||||
this.strResource = uriGetComparisonKey(this.resource);
|
||||
this.resources = [this.resource];
|
||||
this.strResources = [this.strResource];
|
||||
this.isValid = true;
|
||||
}
|
||||
|
||||
public setValid(isValid: boolean): void {
|
||||
this.isValid = isValid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,22 +45,57 @@ const enum RemovedResourceReason {
|
||||
NoParallelUniverses = 1
|
||||
}
|
||||
|
||||
class ResourceReasonPair {
|
||||
constructor(
|
||||
public readonly resource: URI,
|
||||
public readonly reason: RemovedResourceReason
|
||||
) { }
|
||||
}
|
||||
|
||||
class RemovedResources {
|
||||
public readonly set: Set<string> = new Set<string>();
|
||||
public readonly reason: [URI[], URI[]] = [[], []];
|
||||
private readonly elements = new Map<string, ResourceReasonPair>();
|
||||
|
||||
private _getPath(resource: URI): string {
|
||||
return resource.scheme === Schemas.file ? resource.fsPath : resource.path;
|
||||
}
|
||||
|
||||
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(', ')));
|
||||
const externalRemoval: string[] = [];
|
||||
const noParallelUniverses: string[] = [];
|
||||
for (const [, element] of this.elements) {
|
||||
const dest = (
|
||||
element.reason === RemovedResourceReason.ExternalRemoval
|
||||
? externalRemoval
|
||||
: noParallelUniverses
|
||||
);
|
||||
dest.push(this._getPath(element.resource));
|
||||
}
|
||||
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(', ')));
|
||||
|
||||
let messages: string[] = [];
|
||||
if (externalRemoval.length > 0) {
|
||||
messages.push(nls.localize('externalRemoval', "The following files have been closed: {0}.", externalRemoval.join(', ')));
|
||||
}
|
||||
if (noParallelUniverses.length > 0) {
|
||||
messages.push(nls.localize('noParallelUniverses', "The following files have been modified in an incompatible way: {0}.", noParallelUniverses.join(', ')));
|
||||
}
|
||||
return messages.join('\n');
|
||||
}
|
||||
|
||||
public get size(): number {
|
||||
return this.elements.size;
|
||||
}
|
||||
|
||||
public has(strResource: string): boolean {
|
||||
return this.elements.has(strResource);
|
||||
}
|
||||
|
||||
public set(strResource: string, value: ResourceReasonPair): void {
|
||||
this.elements.set(strResource, value);
|
||||
}
|
||||
|
||||
public delete(strResource: string): boolean {
|
||||
return this.elements.delete(strResource);
|
||||
}
|
||||
}
|
||||
|
||||
class WorkspaceStackElement {
|
||||
@@ -65,6 +106,7 @@ class WorkspaceStackElement {
|
||||
public readonly resources: URI[];
|
||||
public readonly strResources: string[];
|
||||
public removedResources: RemovedResources | null;
|
||||
public invalidatedResources: RemovedResources | null;
|
||||
|
||||
constructor(actual: IWorkspaceUndoRedoElement) {
|
||||
this.actual = actual;
|
||||
@@ -72,18 +114,37 @@ class WorkspaceStackElement {
|
||||
this.resources = actual.resources.slice(0);
|
||||
this.strResources = this.resources.map(resource => uriGetComparisonKey(resource));
|
||||
this.removedResources = null;
|
||||
this.invalidatedResources = null;
|
||||
}
|
||||
|
||||
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);
|
||||
if (!this.removedResources.has(strResource)) {
|
||||
this.removedResources.set(strResource, new ResourceReasonPair(resource, reason));
|
||||
}
|
||||
}
|
||||
|
||||
public setValid(resource: URI, strResource: string, isValid: boolean): void {
|
||||
if (isValid) {
|
||||
if (this.invalidatedResources) {
|
||||
this.invalidatedResources.delete(strResource);
|
||||
if (this.invalidatedResources.size === 0) {
|
||||
this.invalidatedResources = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!this.invalidatedResources) {
|
||||
this.invalidatedResources = new RemovedResources();
|
||||
}
|
||||
if (!this.invalidatedResources.has(strResource)) {
|
||||
this.invalidatedResources.set(strResource, new ResourceReasonPair(resource, RemovedResourceReason.ExternalRemoval));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type StackElement = ResourceStackElement | WorkspaceStackElement;
|
||||
|
||||
class ResourceEditStack {
|
||||
@@ -110,7 +171,7 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
this._editStacks = new Map<string, ResourceEditStack>();
|
||||
}
|
||||
|
||||
public pushElement(_element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement): void {
|
||||
public pushElement(_element: IUndoRedoElement): 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];
|
||||
@@ -131,11 +192,18 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
}
|
||||
}
|
||||
editStack.future = [];
|
||||
if (editStack.past.length > 0) {
|
||||
const lastElement = editStack.past[editStack.past.length - 1];
|
||||
if (lastElement.type === UndoRedoElementType.Resource && !lastElement.isValid) {
|
||||
// clear undo stack
|
||||
editStack.past = [];
|
||||
}
|
||||
}
|
||||
editStack.past.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
public getLastElement(resource: URI): IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null {
|
||||
public getLastElement(resource: URI): IUndoRedoElement | null {
|
||||
const strResource = uriGetComparisonKey(resource);
|
||||
if (this._editStacks.has(strResource)) {
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
@@ -150,7 +218,7 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
return null;
|
||||
}
|
||||
|
||||
private _splitPastWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: Set<string> | null): void {
|
||||
private _splitPastWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: RemovedResources | null): void {
|
||||
const individualArr = toRemove.actual.split();
|
||||
const individualMap = new Map<string, ResourceStackElement>();
|
||||
for (const _element of individualArr) {
|
||||
@@ -178,7 +246,7 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
}
|
||||
}
|
||||
|
||||
private _splitFutureWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: Set<string> | null): void {
|
||||
private _splitFutureWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: RemovedResources | null): void {
|
||||
const individualArr = toRemove.actual.split();
|
||||
const individualMap = new Map<string, ResourceStackElement>();
|
||||
for (const _element of individualArr) {
|
||||
@@ -224,6 +292,56 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
}
|
||||
}
|
||||
|
||||
public setElementsIsValid(resource: URI, isValid: boolean): void {
|
||||
const strResource = uriGetComparisonKey(resource);
|
||||
if (this._editStacks.has(strResource)) {
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
for (const element of editStack.past) {
|
||||
if (element.type === UndoRedoElementType.Workspace) {
|
||||
element.setValid(resource, strResource, isValid);
|
||||
} else {
|
||||
element.setValid(isValid);
|
||||
}
|
||||
}
|
||||
for (const element of editStack.future) {
|
||||
if (element.type === UndoRedoElementType.Workspace) {
|
||||
element.setValid(resource, strResource, isValid);
|
||||
} else {
|
||||
element.setValid(isValid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// resource
|
||||
|
||||
public hasElements(resource: URI): boolean {
|
||||
const strResource = uriGetComparisonKey(resource);
|
||||
if (this._editStacks.has(strResource)) {
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
return (editStack.past.length > 0 || editStack.future.length > 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public getElements(resource: URI): IPastFutureElements {
|
||||
const past: IUndoRedoElement[] = [];
|
||||
const future: IUndoRedoElement[] = [];
|
||||
|
||||
const strResource = uriGetComparisonKey(resource);
|
||||
if (this._editStacks.has(strResource)) {
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
for (const element of editStack.past) {
|
||||
past.push(element.actual);
|
||||
}
|
||||
for (const element of editStack.future) {
|
||||
future.push(element.actual);
|
||||
}
|
||||
}
|
||||
|
||||
return { past, future };
|
||||
}
|
||||
|
||||
public canUndo(resource: URI): boolean {
|
||||
const strResource = uriGetComparisonKey(resource);
|
||||
if (this._editStacks.has(strResource)) {
|
||||
@@ -257,11 +375,17 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
|
||||
private _workspaceUndo(resource: URI, element: WorkspaceStackElement): Promise<void> | void {
|
||||
if (element.removedResources) {
|
||||
this._splitPastWorkspaceElement(element, element.removedResources.set);
|
||||
this._splitPastWorkspaceElement(element, element.removedResources);
|
||||
const message = nls.localize('cannotWorkspaceUndo', "Could not undo '{0}' across all files. {1}", element.label, element.removedResources.createMessage());
|
||||
this._notificationService.info(message);
|
||||
return this.undo(resource);
|
||||
}
|
||||
if (element.invalidatedResources) {
|
||||
this._splitPastWorkspaceElement(element, element.invalidatedResources);
|
||||
const message = nls.localize('cannotWorkspaceUndo', "Could not undo '{0}' across all files. {1}", element.label, element.invalidatedResources.createMessage());
|
||||
this._notificationService.info(message);
|
||||
return this.undo(resource);
|
||||
}
|
||||
|
||||
// this must be the last past element in all the impacted resources!
|
||||
let affectedEditStacks: ResourceEditStack[] = [];
|
||||
@@ -313,6 +437,12 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
}
|
||||
|
||||
private _resourceUndo(editStack: ResourceEditStack, element: ResourceStackElement): Promise<void> | void {
|
||||
if (!element.isValid) {
|
||||
// invalid element => immediately flush edit stack!
|
||||
editStack.past = [];
|
||||
editStack.future = [];
|
||||
return;
|
||||
}
|
||||
editStack.past.pop();
|
||||
editStack.future.push(element);
|
||||
return this._safeInvoke(element, () => element.actual.undo());
|
||||
@@ -348,11 +478,17 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
|
||||
private _workspaceRedo(resource: URI, element: WorkspaceStackElement): Promise<void> | void {
|
||||
if (element.removedResources) {
|
||||
this._splitFutureWorkspaceElement(element, element.removedResources.set);
|
||||
this._splitFutureWorkspaceElement(element, element.removedResources);
|
||||
const message = nls.localize('cannotWorkspaceRedo', "Could not redo '{0}' across all files. {1}", element.label, element.removedResources.createMessage());
|
||||
this._notificationService.info(message);
|
||||
return this.redo(resource);
|
||||
}
|
||||
if (element.invalidatedResources) {
|
||||
this._splitFutureWorkspaceElement(element, element.invalidatedResources);
|
||||
const message = nls.localize('cannotWorkspaceRedo', "Could not redo '{0}' across all files. {1}", element.label, element.invalidatedResources.createMessage());
|
||||
this._notificationService.info(message);
|
||||
return this.redo(resource);
|
||||
}
|
||||
|
||||
// this must be the last future element in all the impacted resources!
|
||||
let affectedEditStacks: ResourceEditStack[] = [];
|
||||
@@ -383,6 +519,12 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
}
|
||||
|
||||
private _resourceRedo(editStack: ResourceEditStack, element: ResourceStackElement): Promise<void> | void {
|
||||
if (!element.isValid) {
|
||||
// invalid element => immediately flush edit stack!
|
||||
editStack.past = [];
|
||||
editStack.future = [];
|
||||
return;
|
||||
}
|
||||
editStack.future.pop();
|
||||
editStack.past.push(element);
|
||||
return this._safeInvoke(element, () => element.actual.redo());
|
||||
|
||||
@@ -7,9 +7,9 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IFileService, IFileContent, FileChangesEvent, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict, ISyncResourceHandle, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { joinPath, dirname, isEqual } from 'vs/base/common/resources';
|
||||
import { joinPath, dirname, isEqual, basename } from 'vs/base/common/resources';
|
||||
import { CancelablePromise } from 'vs/base/common/async';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
@@ -173,20 +173,36 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
return !!lastSyncData;
|
||||
}
|
||||
|
||||
async getConflictContent(conflictResource: URI): Promise<string | null> {
|
||||
async getRemoteSyncResourceHandles(): Promise<ISyncResourceHandle[]> {
|
||||
const handles = await this.userDataSyncStoreService.getAllRefs(this.resource);
|
||||
return handles.map(({ created, ref }) => ({ created, uri: this.toRemoteBackupResource(ref) }));
|
||||
}
|
||||
|
||||
async getLocalSyncResourceHandles(): Promise<ISyncResourceHandle[]> {
|
||||
const handles = await this.userDataSyncBackupStoreService.getAllRefs(this.resource);
|
||||
return handles.map(({ created, ref }) => ({ created, uri: this.toLocalBackupResource(ref) }));
|
||||
}
|
||||
|
||||
private toRemoteBackupResource(ref: string): URI {
|
||||
return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote-backup', path: `/${this.resource}/${ref}` });
|
||||
}
|
||||
|
||||
private toLocalBackupResource(ref: string): URI {
|
||||
return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local-backup', path: `/${this.resource}/${ref}` });
|
||||
}
|
||||
|
||||
async resolveContent(uri: URI): Promise<string | null> {
|
||||
const ref = basename(uri);
|
||||
if (isEqual(uri, this.toRemoteBackupResource(ref))) {
|
||||
const { content } = await this.getUserData(ref);
|
||||
return content;
|
||||
}
|
||||
if (isEqual(uri, this.toLocalBackupResource(ref))) {
|
||||
return this.userDataSyncBackupStoreService.resolveContent(this.resource, ref);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async getRemoteContent(ref?: string): Promise<string | null> {
|
||||
const refOrLastSyncUserData: string | IRemoteUserData | null = ref || await this.getLastSyncUserData();
|
||||
const { content } = await this.getUserData(refOrLastSyncUserData);
|
||||
return content;
|
||||
}
|
||||
|
||||
async getLocalBackupContent(ref?: string): Promise<string | null> {
|
||||
return this.userDataSyncBackupStoreService.resolveContent(this.resource, ref);
|
||||
}
|
||||
|
||||
async resetLocal(): Promise<void> {
|
||||
try {
|
||||
await this.fileService.del(this.lastSyncResource);
|
||||
@@ -265,9 +281,10 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
return this.userDataSyncBackupStoreService.backup(this.resource, JSON.stringify(syncData));
|
||||
}
|
||||
|
||||
abstract stop(): Promise<void>;
|
||||
|
||||
protected abstract readonly version: number;
|
||||
protected abstract performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<SyncStatus>;
|
||||
abstract stop(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IFileSyncPreviewResult {
|
||||
@@ -310,7 +327,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
}
|
||||
|
||||
async getConflictContent(conflictResource: URI): Promise<string | null> {
|
||||
protected async getConflictContent(conflictResource: URI): Promise<string | null> {
|
||||
if (isEqual(this.remotePreviewResource, conflictResource) || isEqual(this.localPreviewResource, conflictResource)) {
|
||||
if (this.syncPreviewResultPromise) {
|
||||
const result = await this.syncPreviewResultPromise;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle } 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';
|
||||
@@ -16,6 +16,9 @@ import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { AbstractSynchroniser, IRemoteUserData, ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath, dirname, basename } from 'vs/base/common/resources';
|
||||
import { format } from 'vs/base/common/jsonFormatter';
|
||||
import { applyEdits } from 'vs/base/common/jsonEdit';
|
||||
|
||||
interface ISyncPreviewResult {
|
||||
readonly localExtensions: ISyncExtension[];
|
||||
@@ -120,28 +123,24 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
|
||||
async stop(): Promise<void> { }
|
||||
|
||||
async getRemoteContent(ref?: string, fragment?: string): Promise<string | null> {
|
||||
const content = await super.getRemoteContent(ref);
|
||||
if (content !== null && fragment) {
|
||||
return this.getFragment(content, fragment);
|
||||
}
|
||||
return content;
|
||||
async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> {
|
||||
return [{ resource: joinPath(uri, 'extensions.json') }];
|
||||
}
|
||||
|
||||
async getLocalBackupContent(ref?: string, fragment?: string): Promise<string | null> {
|
||||
let content = await super.getLocalBackupContent(ref);
|
||||
if (content !== null && fragment) {
|
||||
return this.getFragment(content, fragment);
|
||||
async resolveContent(uri: URI): Promise<string | null> {
|
||||
let content = await super.resolveContent(uri);
|
||||
if (content) {
|
||||
return content;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private getFragment(content: string, fragment: string): string | null {
|
||||
const syncData = this.parseSyncData(content);
|
||||
if (syncData) {
|
||||
switch (fragment) {
|
||||
case 'extensions':
|
||||
return syncData.content;
|
||||
content = await super.resolveContent(dirname(uri));
|
||||
if (content) {
|
||||
const syncData = this.parseSyncData(content);
|
||||
if (syncData) {
|
||||
switch (basename(uri)) {
|
||||
case 'extensions.json':
|
||||
const edits = format(syncData.content, undefined, {});
|
||||
return applyEdits(syncData.content, edits);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle } 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';
|
||||
import { dirname } from 'vs/base/common/resources';
|
||||
import { dirname, joinPath, basename } from 'vs/base/common/resources';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { edit } from 'vs/platform/userDataSync/common/content';
|
||||
@@ -17,6 +17,8 @@ import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { format } from 'vs/base/common/jsonFormatter';
|
||||
import { applyEdits } from 'vs/base/common/jsonEdit';
|
||||
|
||||
const argvProperties: string[] = ['locale'];
|
||||
|
||||
@@ -105,28 +107,24 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
|
||||
async stop(): Promise<void> { }
|
||||
|
||||
async getRemoteContent(ref?: string, fragment?: string): Promise<string | null> {
|
||||
let content = await super.getRemoteContent(ref);
|
||||
if (content !== null && fragment) {
|
||||
return this.getFragment(content, fragment);
|
||||
}
|
||||
return content;
|
||||
async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> {
|
||||
return [{ resource: joinPath(uri, 'globalState.json') }];
|
||||
}
|
||||
|
||||
async getLocalBackupContent(ref?: string, fragment?: string): Promise<string | null> {
|
||||
let content = await super.getLocalBackupContent(ref);
|
||||
if (content !== null && fragment) {
|
||||
return this.getFragment(content, fragment);
|
||||
async resolveContent(uri: URI): Promise<string | null> {
|
||||
let content = await super.resolveContent(uri);
|
||||
if (content) {
|
||||
return content;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private getFragment(content: string, fragment: string): string | null {
|
||||
const syncData = this.parseSyncData(content);
|
||||
if (syncData) {
|
||||
switch (fragment) {
|
||||
case 'globalState':
|
||||
return syncData.content;
|
||||
content = await super.resolveContent(dirname(uri));
|
||||
if (content) {
|
||||
const syncData = this.parseSyncData(content);
|
||||
if (syncData) {
|
||||
switch (basename(uri)) {
|
||||
case 'globalState.json':
|
||||
const edits = format(syncData.content, undefined, {});
|
||||
return applyEdits(syncData.content, edits);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { parse } from 'vs/base/common/json';
|
||||
@@ -19,7 +19,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
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 { joinPath, isEqual } from 'vs/base/common/resources';
|
||||
import { joinPath, isEqual, dirname, basename } from 'vs/base/common/resources';
|
||||
|
||||
interface ISyncContent {
|
||||
mac?: string;
|
||||
@@ -160,38 +160,36 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
return false;
|
||||
}
|
||||
|
||||
async getConflictContent(conflictResource: URI): Promise<string | null> {
|
||||
const content = await super.getConflictContent(conflictResource);
|
||||
return content !== null ? this.getKeybindingsContentFromSyncContent(content) : null;
|
||||
async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> {
|
||||
return [{ resource: joinPath(uri, 'keybindings.json'), comparableResource: this.file }];
|
||||
}
|
||||
|
||||
async getRemoteContent(ref?: string, fragment?: string): Promise<string | null> {
|
||||
const content = await super.getRemoteContent(ref);
|
||||
if (content !== null && fragment) {
|
||||
return this.getFragment(content, fragment);
|
||||
async resolveContent(uri: URI): Promise<string | null> {
|
||||
if (isEqual(this.remotePreviewResource, uri)) {
|
||||
return this.getConflictContent(uri);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
async getLocalBackupContent(ref?: string, fragment?: string): Promise<string | null> {
|
||||
let content = await super.getLocalBackupContent(ref);
|
||||
if (content !== null && fragment) {
|
||||
return this.getFragment(content, fragment);
|
||||
let content = await super.resolveContent(uri);
|
||||
if (content) {
|
||||
return content;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private getFragment(content: string, fragment: string): string | null {
|
||||
const syncData = this.parseSyncData(content);
|
||||
if (syncData) {
|
||||
switch (fragment) {
|
||||
case 'keybindings':
|
||||
return this.getKeybindingsContentFromSyncContent(syncData.content);
|
||||
content = await super.resolveContent(dirname(uri));
|
||||
if (content) {
|
||||
const syncData = this.parseSyncData(content);
|
||||
if (syncData) {
|
||||
switch (basename(uri)) {
|
||||
case 'keybindings.json':
|
||||
return this.getKeybindingsContentFromSyncContent(syncData.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected async getConflictContent(conflictResource: URI): Promise<string | null> {
|
||||
const content = await super.getConflictContent(conflictResource);
|
||||
return content !== null ? this.getKeybindingsContentFromSyncContent(content) : null;
|
||||
}
|
||||
|
||||
protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<SyncStatus> {
|
||||
try {
|
||||
const result = await this.getPreview(remoteUserData, lastSyncUserData);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, CONFIGURATION_SYNC_STORE_KEY, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, CONFIGURATION_SYNC_STORE_KEY, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { parse } from 'vs/base/common/json';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -20,7 +20,7 @@ import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData }
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { joinPath, isEqual } from 'vs/base/common/resources';
|
||||
import { joinPath, isEqual, dirname, basename } from 'vs/base/common/resources';
|
||||
|
||||
export interface ISettingsSyncContent {
|
||||
settings: string;
|
||||
@@ -173,7 +173,35 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser {
|
||||
return false;
|
||||
}
|
||||
|
||||
async getConflictContent(conflictResource: URI): Promise<string | null> {
|
||||
async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> {
|
||||
return [{ resource: joinPath(uri, 'settings.json'), comparableResource: this.file }];
|
||||
}
|
||||
|
||||
async resolveContent(uri: URI): Promise<string | null> {
|
||||
if (isEqual(this.remotePreviewResource, uri)) {
|
||||
return this.getConflictContent(uri);
|
||||
}
|
||||
let content = await super.resolveContent(uri);
|
||||
if (content) {
|
||||
return content;
|
||||
}
|
||||
content = await super.resolveContent(dirname(uri));
|
||||
if (content) {
|
||||
const syncData = this.parseSyncData(content);
|
||||
if (syncData) {
|
||||
const settingsSyncContent = this.parseSettingsSyncContent(syncData.content);
|
||||
if (settingsSyncContent) {
|
||||
switch (basename(uri)) {
|
||||
case 'settings.json':
|
||||
return settingsSyncContent.settings;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected async getConflictContent(conflictResource: URI): Promise<string | null> {
|
||||
let content = await super.getConflictContent(conflictResource);
|
||||
if (content !== null) {
|
||||
const settingsSyncContent = this.parseSettingsSyncContent(content);
|
||||
@@ -188,36 +216,6 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser {
|
||||
return content;
|
||||
}
|
||||
|
||||
async getRemoteContent(ref?: string, fragment?: string): Promise<string | null> {
|
||||
let content = await super.getRemoteContent(ref);
|
||||
if (content !== null && fragment) {
|
||||
return this.getFragment(content, fragment);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
async getLocalBackupContent(ref?: string, fragment?: string): Promise<string | null> {
|
||||
let content = await super.getLocalBackupContent(ref);
|
||||
if (content !== null && fragment) {
|
||||
return this.getFragment(content, fragment);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private getFragment(content: string, fragment: string): string | null {
|
||||
const syncData = this.parseSyncData(content);
|
||||
if (syncData) {
|
||||
const settingsSyncContent = this.parseSettingsSyncContent(syncData.content);
|
||||
if (settingsSyncContent) {
|
||||
switch (fragment) {
|
||||
case 'settings':
|
||||
return settingsSyncContent.settings;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async acceptConflict(conflict: URI, content: string): Promise<void> {
|
||||
if (this.status === SyncStatus.HasConflicts
|
||||
&& (isEqual(this.localPreviewResource, conflict) || isEqual(this.remotePreviewResource, conflict))
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME, UserDataSyncError, UserDataSyncErrorCode, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IFileService, FileChangesEvent, IFileStat, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
@@ -11,7 +11,7 @@ import { AbstractSynchroniser, IRemoteUserData, ISyncData } from 'vs/platform/us
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath, extname, relativePath, isEqualOrParent, isEqual, basename } from 'vs/base/common/resources';
|
||||
import { joinPath, extname, relativePath, isEqualOrParent, isEqual, basename, dirname } from 'vs/base/common/resources';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { merge } from 'vs/platform/userDataSync/common/snippetsMerge';
|
||||
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
@@ -148,8 +148,46 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
}
|
||||
|
||||
async getConflictContent(conflictResource: URI): Promise<string | null> {
|
||||
if (isEqualOrParent(conflictResource.with({ scheme: this.syncFolder.scheme }), this.snippetsPreviewFolder) && this.syncPreviewResultPromise) {
|
||||
async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> {
|
||||
let content = await super.resolveContent(uri);
|
||||
if (content) {
|
||||
const syncData = this.parseSyncData(content);
|
||||
if (syncData) {
|
||||
const snippets = this.parseSnippets(syncData);
|
||||
const result = [];
|
||||
for (const snippet of Object.keys(snippets)) {
|
||||
const resource = joinPath(uri, snippet);
|
||||
const comparableResource = joinPath(this.snippetsFolder, snippet);
|
||||
const exists = await this.fileService.exists(comparableResource);
|
||||
result.push({ resource, comparableResource: exists ? comparableResource : undefined });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
async resolveContent(uri: URI): Promise<string | null> {
|
||||
if (isEqualOrParent(uri.with({ scheme: this.syncFolder.scheme }), this.snippetsPreviewFolder)) {
|
||||
return this.getConflictContent(uri);
|
||||
}
|
||||
let content = await super.resolveContent(uri);
|
||||
if (content) {
|
||||
return content;
|
||||
}
|
||||
content = await super.resolveContent(dirname(uri));
|
||||
if (content) {
|
||||
const syncData = this.parseSyncData(content);
|
||||
if (syncData) {
|
||||
const snippets = this.parseSnippets(syncData);
|
||||
return snippets[basename(uri)] || null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected async getConflictContent(conflictResource: URI): Promise<string | null> {
|
||||
if (this.syncPreviewResultPromise) {
|
||||
const result = await this.syncPreviewResultPromise;
|
||||
const key = relativePath(this.snippetsPreviewFolder, conflictResource.with({ scheme: this.snippetsPreviewFolder.scheme }))!;
|
||||
if (conflictResource.scheme === this.snippetsPreviewFolder.scheme) {
|
||||
@@ -162,37 +200,6 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
||||
return null;
|
||||
}
|
||||
|
||||
async getRemoteContent(ref?: string, fragment?: string): Promise<string | null> {
|
||||
const content = await super.getRemoteContent(ref);
|
||||
if (content !== null && fragment) {
|
||||
return this.getFragment(content, fragment);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
async getLocalBackupContent(ref?: string, fragment?: string): Promise<string | null> {
|
||||
let content = await super.getLocalBackupContent(ref);
|
||||
if (content !== null && fragment) {
|
||||
return this.getFragment(content, fragment);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
private getFragment(content: string, fragment: string): string | null {
|
||||
const syncData = this.parseSyncData(content);
|
||||
return syncData ? this.getFragmentFromSyncData(syncData, fragment) : null;
|
||||
}
|
||||
|
||||
private getFragmentFromSyncData(syncData: ISyncData, fragment: string): string | null {
|
||||
switch (fragment) {
|
||||
case 'snippets':
|
||||
return syncData.content;
|
||||
default:
|
||||
const remoteSnippets = this.parseSnippets(syncData);
|
||||
return remoteSnippets[fragment] || null;
|
||||
}
|
||||
}
|
||||
|
||||
async acceptConflict(conflictResource: URI, content: string): Promise<void> {
|
||||
const conflict = this.conflicts.filter(({ local, remote }) => isEqual(local, conflictResource) || isEqual(remote, conflictResource))[0];
|
||||
if (this.status === SyncStatus.HasConflicts && conflict) {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath, dirname, basename, isEqualOrParent } from 'vs/base/common/resources';
|
||||
import { joinPath, isEqualOrParent } from 'vs/base/common/resources';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
@@ -243,6 +243,11 @@ export const enum SyncStatus {
|
||||
HasConflicts = 'hasConflicts',
|
||||
}
|
||||
|
||||
export interface ISyncResourceHandle {
|
||||
created: number;
|
||||
uri: URI;
|
||||
}
|
||||
|
||||
export type Conflict = { remote: URI, local: URI };
|
||||
|
||||
export interface IUserDataSynchroniser {
|
||||
@@ -263,11 +268,12 @@ export interface IUserDataSynchroniser {
|
||||
hasLocalData(): Promise<boolean>;
|
||||
resetLocal(): Promise<void>;
|
||||
|
||||
getConflictContent(conflictResource: URI): Promise<string | null>;
|
||||
resolveContent(resource: URI): Promise<string | null>;
|
||||
acceptConflict(conflictResource: URI, content: string): Promise<void>;
|
||||
|
||||
getRemoteContent(ref?: string, fragment?: string): Promise<string | null>;
|
||||
getLocalBackupContent(ref?: string, fragment?: string): Promise<string | null>;
|
||||
getRemoteSyncResourceHandles(): Promise<ISyncResourceHandle[]>;
|
||||
getLocalSyncResourceHandles(): Promise<ISyncResourceHandle[]>;
|
||||
getAssociatedResources(syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]>;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -315,6 +321,10 @@ export interface IUserDataSyncService {
|
||||
isFirstTimeSyncWithMerge(): Promise<boolean>;
|
||||
resolveContent(resource: URI): Promise<string | null>;
|
||||
acceptConflict(conflictResource: URI, content: string): Promise<void>;
|
||||
|
||||
getLocalSyncResourceHandles(resource: SyncResource): Promise<ISyncResourceHandle[]>;
|
||||
getRemoteSyncResourceHandles(resource: SyncResource): Promise<ISyncResourceHandle[]>;
|
||||
getAssociatedResources(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]>;
|
||||
}
|
||||
|
||||
export const IUserDataAutoSyncService = createDecorator<IUserDataAutoSyncService>('IUserDataAutoSyncService');
|
||||
@@ -347,25 +357,6 @@ export const USER_DATA_SYNC_SCHEME = 'vscode-userdata-sync';
|
||||
export const CONTEXT_SYNC_STATE = new RawContextKey<string>('syncStatus', SyncStatus.Uninitialized);
|
||||
export const CONTEXT_SYNC_ENABLEMENT = new RawContextKey<boolean>('syncEnabled', false);
|
||||
|
||||
export function toRemoteBackupSyncResource(resource: SyncResource, ref?: string): URI {
|
||||
return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote-backup', path: `/${resource}/${ref ? ref : 'latest'}` });
|
||||
}
|
||||
export function toLocalBackupSyncResource(resource: SyncResource, ref?: string): URI {
|
||||
return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local-backup', path: `/${resource}/${ref ? ref : 'latest'}` });
|
||||
}
|
||||
export function resolveBackupSyncResource(resource: URI): { remote: boolean, resource: SyncResource, path: string } | null {
|
||||
if (resource.scheme === USER_DATA_SYNC_SCHEME
|
||||
&& resource.authority === 'remote-backup' || resource.authority === 'local-backup') {
|
||||
const resourceKey: SyncResource = basename(dirname(resource)) as SyncResource;
|
||||
const path = resource.path.substring(resourceKey.length + 1);
|
||||
if (resourceKey && path) {
|
||||
const remote = resource.authority === 'remote-backup';
|
||||
return { remote, resource: resourceKey, path };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export const PREVIEW_DIR_NAME = 'preview';
|
||||
export function getSyncResourceFromLocalPreview(localPreview: URI, environmentService: IEnvironmentService): SyncResource | undefined {
|
||||
if (localPreview.scheme === USER_DATA_SYNC_SCHEME) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IUserDataSyncService, IUserDataSyncUtilService, IUserDataAutoSyncService, IUserDataSyncStoreService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncService, IUserDataSyncUtilService, IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
@@ -28,14 +28,17 @@ export class UserDataSyncChannel implements IServerChannel {
|
||||
call(context: any, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case '_getInitialData': return Promise.resolve([this.service.status, this.service.conflicts, this.service.lastSyncTime]);
|
||||
case 'sync': return this.service.sync();
|
||||
case 'acceptConflict': return this.service.acceptConflict(URI.revive(args[0]), args[1]);
|
||||
case 'pull': return this.service.pull();
|
||||
case 'sync': return this.service.sync();
|
||||
case 'stop': this.service.stop(); return Promise.resolve();
|
||||
case 'reset': return this.service.reset();
|
||||
case 'resetLocal': return this.service.resetLocal();
|
||||
case 'resolveContent': return this.service.resolveContent(URI.revive(args[0]));
|
||||
case 'isFirstTimeSyncWithMerge': return this.service.isFirstTimeSyncWithMerge();
|
||||
case 'acceptConflict': return this.service.acceptConflict(URI.revive(args[0]), args[1]);
|
||||
case 'resolveContent': return this.service.resolveContent(URI.revive(args[0]));
|
||||
case 'getLocalSyncResourceHandles': return this.service.getLocalSyncResourceHandles(args[0]);
|
||||
case 'getRemoteSyncResourceHandles': return this.service.getRemoteSyncResourceHandles(args[0]);
|
||||
case 'getAssociatedResources': return this.service.getAssociatedResources(args[0], { created: args[1].created, uri: URI.revive(args[1].uri) });
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
@@ -98,38 +101,3 @@ export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class UserDataSyncStoreServiceChannel implements IServerChannel {
|
||||
|
||||
constructor(private readonly service: IUserDataSyncStoreService) { }
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
call(context: any, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'getAllRefs': return this.service.getAllRefs(args[0]);
|
||||
case 'resolveContent': return this.service.resolveContent(args[0], args[1]);
|
||||
case 'delete': return this.service.delete(args[0]);
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
}
|
||||
|
||||
export class UserDataSyncBackupStoreServiceChannel implements IServerChannel {
|
||||
|
||||
constructor(private readonly service: IUserDataSyncBackupStoreService) { }
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
call(context: any, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'getAllRefs': return this.service.getAllRefs(args[0]);
|
||||
case 'resolveContent': return this.service.resolveContent(args[0], args[1]);
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError, resolveBackupSyncResource, SyncResourceConflicts } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError, SyncResourceConflicts, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
@@ -188,25 +188,27 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
}
|
||||
|
||||
async resolveContent(resource: URI): Promise<string | null> {
|
||||
const result = resolveBackupSyncResource(resource);
|
||||
if (result) {
|
||||
const synchronizer = this.synchronisers.filter(s => s.resource === result.resource)[0];
|
||||
if (synchronizer) {
|
||||
const ref = result.path !== 'latest' ? result.path : undefined;
|
||||
return result.remote ? synchronizer.getRemoteContent(ref, resource.fragment) : synchronizer.getLocalBackupContent(ref, resource.fragment);
|
||||
}
|
||||
}
|
||||
|
||||
for (const synchronizer of this.synchronisers) {
|
||||
const content = await synchronizer.getConflictContent(resource);
|
||||
if (content !== null) {
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
const content = await synchroniser.resolveContent(resource);
|
||||
if (content) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
getRemoteSyncResourceHandles(resource: SyncResource): Promise<ISyncResourceHandle[]> {
|
||||
return this.getSynchroniser(resource).getRemoteSyncResourceHandles();
|
||||
}
|
||||
|
||||
getLocalSyncResourceHandles(resource: SyncResource): Promise<ISyncResourceHandle[]> {
|
||||
return this.getSynchroniser(resource).getLocalSyncResourceHandles();
|
||||
}
|
||||
|
||||
getAssociatedResources(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> {
|
||||
return this.getSynchroniser(resource).getAssociatedResources(syncResourceHandle);
|
||||
}
|
||||
|
||||
async isFirstTimeSyncWithMerge(): Promise<boolean> {
|
||||
await this.checkEnablement();
|
||||
if (!await this.userDataSyncStoreService.manifest()) {
|
||||
|
||||
Reference in New Issue
Block a user