mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-07 01:25:38 -05:00
Merge from vscode ad407028575a77ea387eb7cc219b323dc017b686
This commit is contained in:
committed by
Anthony Dresser
parent
404260b8a0
commit
4ad73d381c
@@ -304,7 +304,7 @@ export class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
super(action, Array.isArray(action.actions) ? action.actions : action.actions(), _contextMenuService, { classNames });
|
||||
super(action, action.actions, _contextMenuService, { classNames });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,9 @@ export class MenuId {
|
||||
static readonly CommentTitle = new MenuId('CommentTitle');
|
||||
static readonly CommentActions = new MenuId('CommentActions');
|
||||
static readonly NotebookCellTitle = new MenuId('NotebookCellTitle');
|
||||
static readonly NotebookCellInsert = new MenuId('NotebookCellInsert');
|
||||
static readonly NotebookCellBetween = new MenuId('NotebookCellBetween');
|
||||
static readonly NotebookCellListTop = new MenuId('NotebookCellTop');
|
||||
static readonly BulkEditTitle = new MenuId('BulkEditTitle');
|
||||
static readonly BulkEditContext = new MenuId('BulkEditContext');
|
||||
static readonly ObjectExplorerItemContext = new MenuId('ObjectExplorerItemContext'); // {{SQL CARBON EDIT}}
|
||||
|
||||
@@ -72,6 +72,7 @@ export class ContextMenuHandler {
|
||||
this.block.style.top = '0';
|
||||
this.block.style.width = '100%';
|
||||
this.block.style.height = '100%';
|
||||
this.block.style.zIndex = '-1';
|
||||
domEvent(this.block, EventType.MOUSE_DOWN)((e: MouseEvent) => e.stopPropagation());
|
||||
}
|
||||
|
||||
|
||||
@@ -5,15 +5,16 @@
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const ICredentialsService = createDecorator<ICredentialsService>('ICredentialsService');
|
||||
|
||||
export interface ICredentialsService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
export interface ICredentialsProvider {
|
||||
getPassword(service: string, account: string): Promise<string | null>;
|
||||
setPassword(service: string, account: string, password: string): Promise<void>;
|
||||
deletePassword(service: string, account: string): Promise<boolean>;
|
||||
findPassword(service: string): Promise<string | null>;
|
||||
findCredentials(service: string): Promise<Array<{ account: string, password: string }>>;
|
||||
}
|
||||
|
||||
export const ICredentialsService = createDecorator<ICredentialsService>('ICredentialsService');
|
||||
|
||||
export interface ICredentialsService extends ICredentialsProvider {
|
||||
readonly _serviceBrand: undefined;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogReturnValue, CrashReporterStartOptions, MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes';
|
||||
import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogReturnValue, MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes';
|
||||
import { IOpenedWindow, IWindowOpenable, IOpenEmptyWindowOptions, IOpenWindowOptions } from 'vs/platform/windows/common/windows';
|
||||
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
|
||||
@@ -98,7 +98,6 @@ export interface ICommonElectronService {
|
||||
// Development
|
||||
openDevTools(options?: OpenDevToolsOptions): Promise<void>;
|
||||
toggleDevTools(): Promise<void>;
|
||||
startCrashReporter(options: CrashReporterStartOptions): Promise<void>;
|
||||
sendInputEvent(event: MouseInputEvent): Promise<void>;
|
||||
|
||||
// Connectivity
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, CrashReporterStartOptions, crashReporter, Menu, BrowserWindow, app, clipboard, powerMonitor } from 'electron';
|
||||
import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, Menu, BrowserWindow, app, clipboard, powerMonitor } from 'electron';
|
||||
import { OpenContext } from 'vs/platform/windows/node/window';
|
||||
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { IOpenedWindow, IOpenWindowOptions, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows';
|
||||
@@ -20,7 +20,6 @@ import { dirExists } from 'vs/base/node/pfs';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes';
|
||||
import { totalmem } from 'os';
|
||||
@@ -38,8 +37,7 @@ export class ElectronMainService implements IElectronMainService {
|
||||
@IDialogMainService private readonly dialogMainService: IDialogMainService,
|
||||
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
|
||||
@IEnvironmentService private readonly environmentService: INativeEnvironmentService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -479,12 +477,6 @@ export class ElectronMainService implements IElectronMainService {
|
||||
}
|
||||
}
|
||||
|
||||
async startCrashReporter(windowId: number | undefined, options: CrashReporterStartOptions): Promise<void> {
|
||||
this.logService.trace('ElectronMainService#crashReporter', JSON.stringify(options));
|
||||
|
||||
crashReporter.start(options);
|
||||
}
|
||||
|
||||
async sendInputEvent(windowId: number | undefined, event: MouseInputEvent): Promise<void> {
|
||||
const window = this.windowById(windowId);
|
||||
if (window && (event.type === 'mouseDown' || event.type === 'mouseUp')) {
|
||||
|
||||
@@ -64,6 +64,7 @@ export interface ParsedArgs {
|
||||
'disable-updates'?: boolean;
|
||||
'disable-crash-reporter'?: boolean;
|
||||
'crash-reporter-directory'?: string;
|
||||
'crash-reporter-id'?: string;
|
||||
'skip-add-to-recently-opened'?: boolean;
|
||||
'max-memory'?: string;
|
||||
'file-write'?: boolean;
|
||||
@@ -191,6 +192,7 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
|
||||
'disable-updates': { type: 'boolean' },
|
||||
'disable-crash-reporter': { type: 'boolean' },
|
||||
'crash-reporter-directory': { type: 'string' },
|
||||
'crash-reporter-id': { type: 'string' },
|
||||
'disable-user-env-probe': { type: 'boolean' },
|
||||
'skip-add-to-recently-opened': { type: 'boolean' },
|
||||
'unity-launch': { type: 'boolean' },
|
||||
|
||||
@@ -256,7 +256,7 @@ export class EnvironmentService implements INativeEnvironmentService {
|
||||
get serviceMachineIdResource(): URI { return resources.joinPath(URI.file(this.userDataPath), 'machineid'); }
|
||||
|
||||
get disableUpdates(): boolean { return !!this._args['disable-updates']; }
|
||||
get disableCrashReporter(): boolean { return !!this._args['disable-crash-reporter']; }
|
||||
get crashReporterId(): string | undefined { return this._args['crash-reporter-id']; }
|
||||
get crashReporterDirectory(): string | undefined { return this._args['crash-reporter-directory']; }
|
||||
|
||||
get driverHandle(): string | undefined { return this._args['driver']; }
|
||||
|
||||
@@ -70,14 +70,18 @@ export class LaunchMainService implements ILaunchMainService {
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) { }
|
||||
|
||||
start(args: ParsedArgs, userEnv: IProcessEnvironment): Promise<void> {
|
||||
async start(args: ParsedArgs, userEnv: IProcessEnvironment): Promise<void> {
|
||||
this.logService.trace('Received data from other instance: ', args, userEnv);
|
||||
|
||||
const urlsToOpen = parseOpenUrl(args);
|
||||
// Since we now start to open a window, make sure the app has focus.
|
||||
// Focussing a window will not ensure that the application itself
|
||||
// has focus, so we use the `steal: true` hint to force focus.
|
||||
app.focus({ steal: true });
|
||||
|
||||
// Check early for open-url which is handled in URL service
|
||||
const urlsToOpen = parseOpenUrl(args);
|
||||
if (urlsToOpen.length) {
|
||||
let whenWindowReady: Promise<any> = Promise.resolve<any>(null);
|
||||
let whenWindowReady: Promise<unknown> = Promise.resolve();
|
||||
|
||||
// Create a window if there is none
|
||||
if (this.windowsMainService.getWindowCount() === 0) {
|
||||
@@ -91,12 +95,12 @@ export class LaunchMainService implements ILaunchMainService {
|
||||
this.urlService.open(url);
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
// Otherwise handle in windows service
|
||||
return this.startOpenWindow(args, userEnv);
|
||||
else {
|
||||
return this.startOpenWindow(args, userEnv);
|
||||
}
|
||||
}
|
||||
|
||||
private startOpenWindow(args: ParsedArgs, userEnv: IProcessEnvironment): Promise<void> {
|
||||
|
||||
@@ -21,7 +21,7 @@ if (isWeb) {
|
||||
if (Object.keys(product).length === 0) {
|
||||
Object.assign(product, {
|
||||
version: '1.17.0-dev',
|
||||
vscodeVersion: '1.48.0-dev',
|
||||
vscodeVersion: '1.49.0-dev',
|
||||
nameLong: 'Azure Data Studio Web Dev',
|
||||
nameShort: 'Azure Data Studio Web Dev',
|
||||
urlProtocol: 'azuredatastudio-oss',
|
||||
|
||||
@@ -56,7 +56,13 @@ export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address:
|
||||
};
|
||||
}
|
||||
|
||||
export function isLocalhost(host: string): boolean {
|
||||
return host === 'localhost' || host === '127.0.0.1';
|
||||
}
|
||||
|
||||
function getOtherLocalhost(host: string): string | undefined {
|
||||
return (host === 'localhost') ? '127.0.0.1' : ((host === '127.0.0.1') ? 'localhost' : undefined);
|
||||
}
|
||||
|
||||
export abstract class AbstractTunnelService implements ITunnelService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
@@ -107,7 +113,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!remoteHost || (remoteHost === '127.0.0.1')) {
|
||||
if (!remoteHost) {
|
||||
remoteHost = 'localhost';
|
||||
}
|
||||
|
||||
@@ -174,13 +180,29 @@ export abstract class AbstractTunnelService implements ITunnelService {
|
||||
this._tunnels.get(remoteHost)!.set(remotePort, { refcount: 1, value: tunnel });
|
||||
}
|
||||
|
||||
protected getTunnelFromMap(remoteHost: string, remotePort: number): { refcount: number, readonly value: Promise<RemoteTunnel> } | undefined {
|
||||
const otherLocalhost = getOtherLocalhost(remoteHost);
|
||||
let portMap: Map<number, { refcount: number, readonly value: Promise<RemoteTunnel> }> | undefined;
|
||||
if (otherLocalhost) {
|
||||
const firstMap = this._tunnels.get(remoteHost);
|
||||
const secondMap = this._tunnels.get(otherLocalhost);
|
||||
if (firstMap && secondMap) {
|
||||
portMap = new Map([...Array.from(firstMap.entries()), ...Array.from(secondMap.entries())]);
|
||||
} else {
|
||||
portMap = firstMap ?? secondMap;
|
||||
}
|
||||
} else {
|
||||
portMap = this._tunnels.get(remoteHost);
|
||||
}
|
||||
return portMap ? portMap.get(remotePort) : undefined;
|
||||
}
|
||||
|
||||
protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined;
|
||||
}
|
||||
|
||||
export class TunnelService extends AbstractTunnelService {
|
||||
protected retainOrCreateTunnel(_addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort?: number | undefined): Promise<RemoteTunnel> | undefined {
|
||||
const portMap = this._tunnels.get(remoteHost);
|
||||
const existing = portMap ? portMap.get(remotePort) : undefined;
|
||||
const existing = this.getTunnelFromMap(remoteHost, remotePort);
|
||||
if (existing) {
|
||||
++existing.refcount;
|
||||
return existing.value;
|
||||
|
||||
@@ -86,7 +86,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
|
||||
this.tunnelLocalPort = address.port;
|
||||
|
||||
await this._barrier.wait();
|
||||
this.localAddress = 'localhost:' + address.port;
|
||||
this.localAddress = `${this.tunnelRemoteHost === '127.0.0.1' ? '127.0.0.1' : 'localhost'}:${address.port}`;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -132,8 +132,7 @@ export class TunnelService extends AbstractTunnelService {
|
||||
}
|
||||
|
||||
protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined {
|
||||
const portMap = this._tunnels.get(remoteHost);
|
||||
const existing = portMap ? portMap.get(remotePort) : undefined;
|
||||
const existing = this.getTunnelFromMap(remoteHost, remotePort);
|
||||
if (existing) {
|
||||
++existing.refcount;
|
||||
return existing.value;
|
||||
|
||||
@@ -20,8 +20,9 @@ export namespace SeverityIcon {
|
||||
return Codicon.warning.classNames;
|
||||
case Severity.Error:
|
||||
return Codicon.error.classNames;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason, logStorage } from 'vs/platform/storage/common/storage';
|
||||
import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason, logStorage, IS_NEW_KEY } from 'vs/platform/storage/common/storage';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IFileService, FileChangeType } from 'vs/platform/files/common/files';
|
||||
@@ -20,8 +20,6 @@ export class BrowserStorageService extends Disposable implements IStorageService
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private static readonly WORKSPACE_IS_NEW_KEY = '__$__isNewStorageMarker';
|
||||
|
||||
private readonly _onDidChangeStorage = this._register(new Emitter<IWorkspaceStorageChangeEvent>());
|
||||
readonly onDidChangeStorage = this._onDidChangeStorage.event;
|
||||
|
||||
@@ -82,12 +80,20 @@ export class BrowserStorageService extends Disposable implements IStorageService
|
||||
this.globalStorage.init()
|
||||
]);
|
||||
|
||||
// Check to see if this is the first time we are "opening" this workspace
|
||||
const firstOpen = this.workspaceStorage.getBoolean(BrowserStorageService.WORKSPACE_IS_NEW_KEY);
|
||||
// Check to see if this is the first time we are "opening" the application
|
||||
const firstOpen = this.globalStorage.getBoolean(IS_NEW_KEY);
|
||||
if (firstOpen === undefined) {
|
||||
this.workspaceStorage.set(BrowserStorageService.WORKSPACE_IS_NEW_KEY, true);
|
||||
this.globalStorage.set(IS_NEW_KEY, true);
|
||||
} else if (firstOpen) {
|
||||
this.workspaceStorage.set(BrowserStorageService.WORKSPACE_IS_NEW_KEY, false);
|
||||
this.globalStorage.set(IS_NEW_KEY, false);
|
||||
}
|
||||
|
||||
// Check to see if this is the first time we are "opening" this workspace
|
||||
const firstWorkspaceOpen = this.workspaceStorage.getBoolean(IS_NEW_KEY);
|
||||
if (firstWorkspaceOpen === undefined) {
|
||||
this.workspaceStorage.set(IS_NEW_KEY, true);
|
||||
} else if (firstWorkspaceOpen) {
|
||||
this.workspaceStorage.set(IS_NEW_KEY, false);
|
||||
}
|
||||
|
||||
// In the browser we do not have support for long running unload sequences. As such,
|
||||
@@ -189,8 +195,8 @@ export class BrowserStorageService extends Disposable implements IStorageService
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
isNew(scope: StorageScope.WORKSPACE): boolean {
|
||||
return this.getBoolean(BrowserStorageService.WORKSPACE_IS_NEW_KEY, scope) === true;
|
||||
isNew(scope: StorageScope): boolean {
|
||||
return this.getBoolean(IS_NEW_KEY, scope) === true;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
@@ -9,6 +9,8 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
|
||||
|
||||
export const IS_NEW_KEY = '__$__isNewStorageMarker';
|
||||
|
||||
export const IStorageService = createDecorator<IStorageService>('storageService');
|
||||
|
||||
export enum WillSaveStateReason {
|
||||
@@ -104,12 +106,11 @@ export interface IStorageService {
|
||||
migrate(toWorkspace: IWorkspaceInitializationPayload): Promise<void>;
|
||||
|
||||
/**
|
||||
* Wether the storage for the given scope was created during this session or
|
||||
* Whether the storage for the given scope was created during this session or
|
||||
* existed before.
|
||||
*
|
||||
* Note: currently only implemented for `WORKSPACE` scope.
|
||||
*/
|
||||
isNew(scope: StorageScope.WORKSPACE): boolean;
|
||||
isNew(scope: StorageScope): boolean;
|
||||
|
||||
/**
|
||||
* Allows to flush state, e.g. in cases where a shutdown is
|
||||
|
||||
@@ -10,7 +10,7 @@ import { IUpdateRequest, IStorageDatabase, IStorageItemsChangeEvent } from 'vs/b
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey, currentSessionDateStorageKey, crashReporterIdStorageKey } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey, currentSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
type Key = string;
|
||||
type Value = string;
|
||||
@@ -49,16 +49,6 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC
|
||||
this.logService.error(`[storage] init(): Unable to init global storage due to ${error}`);
|
||||
}
|
||||
|
||||
// This is unique to the application instance and thereby
|
||||
// should be written from the main process once.
|
||||
//
|
||||
// THIS SHOULD NEVER BE SENT TO TELEMETRY.
|
||||
//
|
||||
const crashReporterId = this.storageMainService.get(crashReporterIdStorageKey, undefined);
|
||||
if (crashReporterId === undefined) {
|
||||
this.storageMainService.store(crashReporterIdStorageKey, generateUuid());
|
||||
}
|
||||
|
||||
// Apply global telemetry values as part of the initialization
|
||||
// These are global across all windows and thereby should be
|
||||
// written from the main process once.
|
||||
|
||||
@@ -12,6 +12,7 @@ import { INativeEnvironmentService } from 'vs/platform/environment/node/environm
|
||||
import { SQLiteStorageDatabase, ISQLiteStorageDatabaseLoggingOptions } from 'vs/base/parts/storage/node/storage';
|
||||
import { Storage, IStorage, InMemoryStorageDatabase } from 'vs/base/parts/storage/common/storage';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { IS_NEW_KEY } from 'vs/platform/storage/common/storage';
|
||||
|
||||
export const IStorageMainService = createDecorator<IStorageMainService>('storageMainService');
|
||||
|
||||
@@ -135,7 +136,7 @@ export class StorageMainService extends Disposable implements IStorageMainServic
|
||||
return this.initializePromise;
|
||||
}
|
||||
|
||||
private doInitialize(): Promise<void> {
|
||||
private async doInitialize(): Promise<void> {
|
||||
this.storage.dispose();
|
||||
this.storage = new Storage(new SQLiteStorageDatabase(this.storagePath, {
|
||||
logging: this.createLogginOptions()
|
||||
@@ -143,7 +144,15 @@ export class StorageMainService extends Disposable implements IStorageMainServic
|
||||
|
||||
this._register(this.storage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key })));
|
||||
|
||||
return this.storage.init();
|
||||
await this.storage.init();
|
||||
|
||||
// Check to see if this is the first time we are "opening" the application
|
||||
const firstOpen = this.storage.getBoolean(IS_NEW_KEY);
|
||||
if (firstOpen === undefined) {
|
||||
this.storage.set(IS_NEW_KEY, true);
|
||||
} else if (firstOpen) {
|
||||
this.storage.set(IS_NEW_KEY, false);
|
||||
}
|
||||
}
|
||||
|
||||
get(key: string, fallbackValue: string): string;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
|
||||
import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason, logStorage } from 'vs/platform/storage/common/storage';
|
||||
import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason, logStorage, IS_NEW_KEY } from 'vs/platform/storage/common/storage';
|
||||
import { SQLiteStorageDatabase, ISQLiteStorageDatabaseLoggingOptions } from 'vs/base/parts/storage/node/storage';
|
||||
import { Storage, IStorageDatabase, IStorage, StorageHint } from 'vs/base/parts/storage/common/storage';
|
||||
import { mark } from 'vs/base/common/performance';
|
||||
@@ -25,8 +25,6 @@ export class NativeStorageService extends Disposable implements IStorageService
|
||||
private static readonly WORKSPACE_STORAGE_NAME = 'state.vscdb';
|
||||
private static readonly WORKSPACE_META_NAME = 'workspace.json';
|
||||
|
||||
private static readonly WORKSPACE_IS_NEW_KEY = '__$__isNewStorageMarker';
|
||||
|
||||
private readonly _onDidChangeStorage = this._register(new Emitter<IWorkspaceStorageChangeEvent>());
|
||||
readonly onDidChangeStorage = this._onDidChangeStorage.event;
|
||||
|
||||
@@ -108,11 +106,11 @@ export class NativeStorageService extends Disposable implements IStorageService
|
||||
await workspaceStorage.init();
|
||||
|
||||
// Check to see if this is the first time we are "opening" this workspace
|
||||
const firstOpen = workspaceStorage.getBoolean(NativeStorageService.WORKSPACE_IS_NEW_KEY);
|
||||
if (firstOpen === undefined) {
|
||||
workspaceStorage.set(NativeStorageService.WORKSPACE_IS_NEW_KEY, result.wasCreated);
|
||||
} else if (firstOpen) {
|
||||
workspaceStorage.set(NativeStorageService.WORKSPACE_IS_NEW_KEY, false);
|
||||
const firstWorkspaceOpen = workspaceStorage.getBoolean(IS_NEW_KEY);
|
||||
if (firstWorkspaceOpen === undefined) {
|
||||
workspaceStorage.set(IS_NEW_KEY, result.wasCreated);
|
||||
} else if (firstWorkspaceOpen) {
|
||||
workspaceStorage.set(IS_NEW_KEY, false);
|
||||
}
|
||||
} finally {
|
||||
mark('didInitWorkspaceStorage');
|
||||
@@ -281,7 +279,7 @@ export class NativeStorageService extends Disposable implements IStorageService
|
||||
return this.createWorkspaceStorage(newWorkspaceStoragePath).init();
|
||||
}
|
||||
|
||||
isNew(scope: StorageScope.WORKSPACE): boolean {
|
||||
return this.getBoolean(NativeStorageService.WORKSPACE_IS_NEW_KEY, scope) === true;
|
||||
isNew(scope: StorageScope): boolean {
|
||||
return this.getBoolean(IS_NEW_KEY, scope) === true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,4 +57,3 @@ export const currentSessionDateStorageKey = 'telemetry.currentSessionDate';
|
||||
export const firstSessionDateStorageKey = 'telemetry.firstSessionDate';
|
||||
export const lastSessionDateStorageKey = 'telemetry.lastSessionDate';
|
||||
export const machineIdKey = 'telemetry.machineId';
|
||||
export const crashReporterIdStorageKey = 'crashReporter.guid';
|
||||
|
||||
@@ -312,9 +312,9 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry {
|
||||
};
|
||||
|
||||
constructor() {
|
||||
this.tokenTypeById = {};
|
||||
this.tokenModifierById = {};
|
||||
this.typeHierarchy = {};
|
||||
this.tokenTypeById = Object.create(null);
|
||||
this.tokenModifierById = Object.create(null);
|
||||
this.typeHierarchy = Object.create(null);
|
||||
}
|
||||
|
||||
public registerTokenType(id: string, description: string, superType?: string, deprecationMessage?: string): void {
|
||||
@@ -331,7 +331,7 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry {
|
||||
|
||||
const stylingSchemeEntry = getStylingSchemeEntry(description, deprecationMessage);
|
||||
this.tokenStylingSchema.properties[id] = stylingSchemeEntry;
|
||||
this.typeHierarchy = {};
|
||||
this.typeHierarchy = Object.create(null);
|
||||
}
|
||||
|
||||
public registerTokenModifier(id: string, description: string, deprecationMessage?: string): void {
|
||||
@@ -398,7 +398,7 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry {
|
||||
public deregisterTokenType(id: string): void {
|
||||
delete this.tokenTypeById[id];
|
||||
delete this.tokenStylingSchema.properties[id];
|
||||
this.typeHierarchy = {};
|
||||
this.typeHierarchy = Object.create(null);
|
||||
}
|
||||
|
||||
public deregisterTokenModifier(id: string): void {
|
||||
|
||||
@@ -53,6 +53,10 @@ function isSyncData(thing: any): thing is ISyncData {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getLastSyncResourceUri(syncResource: SyncResource, environmentService: IEnvironmentService): URI {
|
||||
return joinPath(environmentService.userDataSyncHome, syncResource, `lastSync${syncResource}.json`);
|
||||
}
|
||||
|
||||
export interface IResourcePreview {
|
||||
|
||||
readonly remoteResource: URI;
|
||||
@@ -133,7 +137,7 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
this.syncResourceLogLabel = uppercaseFirstLetter(this.resource);
|
||||
this.syncFolder = joinPath(environmentService.userDataSyncHome, resource);
|
||||
this.syncPreviewFolder = joinPath(this.syncFolder, PREVIEW_DIR_NAME);
|
||||
this.lastSyncResource = joinPath(this.syncFolder, `lastSync${this.resource}.json`);
|
||||
this.lastSyncResource = getLastSyncResourceUri(resource, environmentService);
|
||||
this.currentMachineIdPromise = getServiceMachineId(environmentService, fileService, storageService);
|
||||
}
|
||||
|
||||
@@ -324,6 +328,7 @@ export abstract class AbstractSynchroniser extends Disposable {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize ${this.syncResourceLogLabel} as there is a new local version available. Synchronizing again...`);
|
||||
return this.performSync(remoteUserData, lastSyncUserData, apply);
|
||||
|
||||
case UserDataSyncErrorCode.Conflict:
|
||||
case UserDataSyncErrorCode.PreconditionFailed:
|
||||
// Rejected as there is a new remote version. Syncing again...
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize as there is a new remote version available. Synchronizing again...`);
|
||||
@@ -796,3 +801,62 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export abstract class AbstractInitializer {
|
||||
|
||||
private readonly lastSyncResource: URI;
|
||||
|
||||
constructor(
|
||||
readonly resource: SyncResource,
|
||||
@IEnvironmentService protected readonly environmentService: IEnvironmentService,
|
||||
@IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService,
|
||||
@IFileService protected readonly fileService: IFileService,
|
||||
) {
|
||||
this.lastSyncResource = getLastSyncResourceUri(this.resource, environmentService);
|
||||
}
|
||||
|
||||
async initialize({ ref, content }: IUserData): Promise<void> {
|
||||
if (!content) {
|
||||
this.logService.info('Remote content does not exist.', this.resource);
|
||||
return;
|
||||
}
|
||||
|
||||
const syncData = this.parseSyncData(content);
|
||||
if (!syncData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isPreviouslySynced = await this.fileService.exists(this.lastSyncResource);
|
||||
if (isPreviouslySynced) {
|
||||
this.logService.info('Remote content does not exist.', this.resource);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.doInitialize({ ref, syncData });
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private parseSyncData(content: string): ISyncData | undefined {
|
||||
try {
|
||||
const syncData: ISyncData = JSON.parse(content);
|
||||
if (isSyncData(syncData)) {
|
||||
return syncData;
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
this.logService.info('Cannot parse sync data as it is not compatible with the current version.', this.resource);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected async updateLastSyncUserData(lastSyncRemoteUserData: IRemoteUserData, additionalProps: IStringDictionary<any> = {}): Promise<void> {
|
||||
const lastSyncUserData: IUserData = { ref: lastSyncRemoteUserData.ref, content: lastSyncRemoteUserData.syncData ? JSON.stringify(lastSyncRemoteUserData.syncData) : null, ...additionalProps };
|
||||
await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData)));
|
||||
}
|
||||
|
||||
protected abstract doInitialize(remoteUserData: IRemoteUserData): Promise<void>;
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { merge, getIgnoredExtensions } from 'vs/platform/userDataSync/common/extensionsMerge';
|
||||
import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } 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, isEqual } from 'vs/base/common/resources';
|
||||
@@ -42,6 +42,34 @@ interface ILastSyncUserData extends IRemoteUserData {
|
||||
skippedExtensions: ISyncExtension[] | undefined;
|
||||
}
|
||||
|
||||
async function parseAndMigrateExtensions(syncData: ISyncData, extensionManagementService: IExtensionManagementService): Promise<ISyncExtension[]> {
|
||||
const extensions = JSON.parse(syncData.content);
|
||||
if (syncData.version === 1
|
||||
|| syncData.version === 2
|
||||
) {
|
||||
const systemExtensions = await extensionManagementService.getInstalled(ExtensionType.System);
|
||||
for (const extension of extensions) {
|
||||
// #region Migration from v1 (enabled -> disabled)
|
||||
if (syncData.version === 1) {
|
||||
if ((<any>extension).enabled === false) {
|
||||
extension.disabled = true;
|
||||
}
|
||||
delete (<any>extension).enabled;
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #region Migration from v2 (set installed property on extension)
|
||||
if (syncData.version === 2) {
|
||||
if (systemExtensions.every(installed => !areSameExtensions(installed.identifier, extension.identifier))) {
|
||||
extension.installed = true;
|
||||
}
|
||||
}
|
||||
// #endregion
|
||||
}
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
|
||||
export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
|
||||
|
||||
private static readonly EXTENSIONS_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'extensions', path: `/extensions.json` });
|
||||
@@ -84,9 +112,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
}
|
||||
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<IExtensionResourcePreview[]> {
|
||||
const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await this.parseAndMigrateExtensions(remoteUserData.syncData) : null;
|
||||
const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await parseAndMigrateExtensions(remoteUserData.syncData, this.extensionManagementService) : null;
|
||||
const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : [];
|
||||
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? await this.parseAndMigrateExtensions(lastSyncUserData.syncData!) : null;
|
||||
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? await parseAndMigrateExtensions(lastSyncUserData.syncData!, this.extensionManagementService) : null;
|
||||
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const localExtensions = this.getLocalExtensions(installedExtensions);
|
||||
@@ -385,34 +413,6 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
return newSkippedExtensions;
|
||||
}
|
||||
|
||||
private async parseAndMigrateExtensions(syncData: ISyncData): Promise<ISyncExtension[]> {
|
||||
const extensions = this.parseExtensions(syncData);
|
||||
if (syncData.version === 1
|
||||
|| syncData.version === 2
|
||||
) {
|
||||
const systemExtensions = await this.extensionManagementService.getInstalled(ExtensionType.System);
|
||||
for (const extension of extensions) {
|
||||
// #region Migration from v1 (enabled -> disabled)
|
||||
if (syncData.version === 1) {
|
||||
if ((<any>extension).enabled === false) {
|
||||
extension.disabled = true;
|
||||
}
|
||||
delete (<any>extension).enabled;
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #region Migration from v2 (set installed property on extension)
|
||||
if (syncData.version === 2) {
|
||||
if (systemExtensions.every(installed => !areSameExtensions(installed.identifier, extension.identifier))) {
|
||||
extension.installed = true;
|
||||
}
|
||||
}
|
||||
// #endregion
|
||||
}
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
|
||||
private parseExtensions(syncData: ISyncData): ISyncExtension[] {
|
||||
return JSON.parse(syncData.content);
|
||||
}
|
||||
@@ -433,3 +433,68 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class ExtensionsInitializer extends AbstractInitializer {
|
||||
|
||||
constructor(
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
|
||||
@IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
) {
|
||||
super(SyncResource.Extensions, environmentService, logService, fileService);
|
||||
}
|
||||
|
||||
async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
|
||||
const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await parseAndMigrateExtensions(remoteUserData.syncData, this.extensionManagementService) : null;
|
||||
if (!remoteExtensions) {
|
||||
this.logService.info('Skipping initializing extensions because remote extensions does not exist.');
|
||||
return;
|
||||
}
|
||||
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const toInstall: { names: string[], uuids: string[] } = { names: [], uuids: [] };
|
||||
const toDisable: IExtensionIdentifier[] = [];
|
||||
for (const extension of remoteExtensions) {
|
||||
if (installedExtensions.some(i => areSameExtensions(i.identifier, extension.identifier))) {
|
||||
if (extension.disabled) {
|
||||
toDisable.push(extension.identifier);
|
||||
}
|
||||
} else {
|
||||
if (extension.installed) {
|
||||
if (extension.identifier.uuid) {
|
||||
toInstall.uuids.push(extension.identifier.uuid);
|
||||
} else {
|
||||
toInstall.names.push(extension.identifier.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toInstall.names.length || toInstall.uuids.length) {
|
||||
const galleryExtensions = (await this.galleryService.query({ ids: toInstall.uuids, names: toInstall.names, pageSize: toInstall.uuids.length + toInstall.names.length }, CancellationToken.None)).firstPage;
|
||||
for (const galleryExtension of galleryExtensions) {
|
||||
try {
|
||||
this.logService.trace(`Installing extension...`, galleryExtension.identifier.id);
|
||||
await this.extensionManagementService.installFromGallery(galleryExtension);
|
||||
this.logService.info(`Installed extension.`, galleryExtension.identifier.id);
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toDisable.length) {
|
||||
for (const identifier of toDisable) {
|
||||
this.logService.trace(`Enabling extension...`, identifier.id);
|
||||
await this.extensionEnablementService.disableExtension(identifier);
|
||||
this.logService.info(`Enabled extension`, identifier.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { edit } from 'vs/platform/userDataSync/common/content';
|
||||
import { merge } from 'vs/platform/userDataSync/common/globalStateMerge';
|
||||
import { parse } from 'vs/base/common/json';
|
||||
import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -341,3 +341,55 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
return [...this.storageKeysSyncRegistryService.storageKeys, ...argvProperties.map(argvProprety => (<IStorageKey>{ key: `${argvStoragePrefx}${argvProprety}`, version: 1 }))];
|
||||
}
|
||||
}
|
||||
|
||||
export class GlobalStateInitializer extends AbstractInitializer {
|
||||
|
||||
constructor(
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
) {
|
||||
super(SyncResource.GlobalState, environmentService, logService, fileService);
|
||||
}
|
||||
|
||||
async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
|
||||
const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
|
||||
if (!remoteGlobalState) {
|
||||
this.logService.info('Skipping initializing global state because remote global state does not exist.');
|
||||
return;
|
||||
}
|
||||
|
||||
const argv: IStringDictionary<any> = {};
|
||||
const storage: IStringDictionary<any> = {};
|
||||
for (const key of Object.keys(remoteGlobalState.storage)) {
|
||||
if (key.startsWith(argvStoragePrefx)) {
|
||||
argv[key.substring(argvStoragePrefx.length)] = remoteGlobalState.storage[key].value;
|
||||
} else {
|
||||
if (this.storageService.get(key, StorageScope.GLOBAL) === undefined) {
|
||||
storage[key] = remoteGlobalState.storage[key].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(argv).length) {
|
||||
let content = '{}';
|
||||
try {
|
||||
const fileContent = await this.fileService.readFile(this.environmentService.argvResource);
|
||||
content = fileContent.value.toString();
|
||||
} catch (error) { }
|
||||
for (const argvProperty of Object.keys(argv)) {
|
||||
content = edit(content, [argvProperty], argv[argvProperty], {});
|
||||
}
|
||||
await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(content));
|
||||
}
|
||||
|
||||
if (Object.keys(storage).length) {
|
||||
for (const key of Object.keys(storage)) {
|
||||
this.storageService.store(key, storage[key], StorageScope.GLOBAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -28,10 +28,14 @@ interface IMergeResult {
|
||||
conflicts: Set<string>;
|
||||
}
|
||||
|
||||
export function parseKeybindings(content: string): IUserFriendlyKeybinding[] {
|
||||
return parse(content) || [];
|
||||
}
|
||||
|
||||
export async function merge(localContent: string, remoteContent: string, baseContent: string | null, formattingOptions: FormattingOptions, userDataSyncUtilService: IUserDataSyncUtilService): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> {
|
||||
const local = <IUserFriendlyKeybinding[]>parse(localContent);
|
||||
const remote = <IUserFriendlyKeybinding[]>parse(remoteContent);
|
||||
const base = baseContent ? <IUserFriendlyKeybinding[]>parse(baseContent) : null;
|
||||
const local = parseKeybindings(localContent);
|
||||
const remote = parseKeybindings(remoteContent);
|
||||
const base = baseContent ? parseKeybindings(baseContent) : null;
|
||||
|
||||
const userbindings: string[] = [...local, ...remote, ...(base || [])].map(keybinding => keybinding.key);
|
||||
const normalizedKeys = await userDataSyncUtilService.resolveUserBindings(userbindings);
|
||||
@@ -331,7 +335,7 @@ function addKeybindings(content: string, keybindings: IUserFriendlyKeybinding[],
|
||||
}
|
||||
|
||||
function removeKeybindings(content: string, command: string, formattingOptions: FormattingOptions): string {
|
||||
const keybindings = <IUserFriendlyKeybinding[]>parse(content);
|
||||
const keybindings = parseKeybindings(content);
|
||||
for (let index = keybindings.length - 1; index >= 0; index--) {
|
||||
if (keybindings[index].command === command || keybindings[index].command === `-${command}`) {
|
||||
content = contentUtil.edit(content, [index], undefined, formattingOptions);
|
||||
@@ -341,7 +345,7 @@ function removeKeybindings(content: string, command: string, formattingOptions:
|
||||
}
|
||||
|
||||
function updateKeybindings(content: string, command: string, keybindings: IUserFriendlyKeybinding[], formattingOptions: FormattingOptions): string {
|
||||
const allKeybindings = <IUserFriendlyKeybinding[]>parse(content);
|
||||
const allKeybindings = parseKeybindings(content);
|
||||
const location = findFirstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`);
|
||||
// Remove all entries with this command
|
||||
for (let index = allKeybindings.length - 1; index >= 0; index--) {
|
||||
|
||||
@@ -18,11 +18,12 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { OS, OperatingSystem } from 'vs/base/common/platform';
|
||||
import { isUndefined } from 'vs/base/common/types';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath, isEqual, dirname, basename } from 'vs/base/common/resources';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
interface ISyncContent {
|
||||
mac?: string;
|
||||
@@ -35,6 +36,21 @@ interface IKeybindingsResourcePreview extends IFileResourcePreview {
|
||||
previewResult: IMergeResult;
|
||||
}
|
||||
|
||||
export function getKeybindingsContentFromSyncContent(syncContent: string, platformSpecific: boolean): string | null {
|
||||
const parsed = <ISyncContent>JSON.parse(syncContent);
|
||||
if (platformSpecific) {
|
||||
return isUndefined(parsed.all) ? null : parsed.all;
|
||||
}
|
||||
switch (OS) {
|
||||
case OperatingSystem.Macintosh:
|
||||
return isUndefined(parsed.mac) ? null : parsed.mac;
|
||||
case OperatingSystem.Linux:
|
||||
return isUndefined(parsed.linux) ? null : parsed.linux;
|
||||
case OperatingSystem.Windows:
|
||||
return isUndefined(parsed.windows) ? null : parsed.windows;
|
||||
}
|
||||
}
|
||||
|
||||
export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
|
||||
|
||||
/* Version 2: Change settings from `sync.${setting}` to `settingsSync.{setting}` */
|
||||
@@ -209,7 +225,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
|
||||
if (lastSyncUserData?.ref !== remoteUserData.ref) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized keybindings...`);
|
||||
const lastSyncContent = content !== null ? this.toSyncContent(content, null) : null;
|
||||
const lastSyncContent = content !== null ? this.toSyncContent(content, null) : remoteUserData.syncData?.content;
|
||||
await this.updateLastSyncUserData({
|
||||
ref: remoteUserData.ref,
|
||||
syncData: lastSyncContent ? {
|
||||
@@ -266,20 +282,9 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
return null;
|
||||
}
|
||||
|
||||
getKeybindingsContentFromSyncContent(syncContent: string): string | null {
|
||||
private getKeybindingsContentFromSyncContent(syncContent: string): string | null {
|
||||
try {
|
||||
const parsed = <ISyncContent>JSON.parse(syncContent);
|
||||
if (!this.syncKeybindingsPerPlatform()) {
|
||||
return isUndefined(parsed.all) ? null : parsed.all;
|
||||
}
|
||||
switch (OS) {
|
||||
case OperatingSystem.Macintosh:
|
||||
return isUndefined(parsed.mac) ? null : parsed.mac;
|
||||
case OperatingSystem.Linux:
|
||||
return isUndefined(parsed.linux) ? null : parsed.linux;
|
||||
case OperatingSystem.Windows:
|
||||
return isUndefined(parsed.windows) ? null : parsed.windows;
|
||||
}
|
||||
return getKeybindingsContentFromSyncContent(syncContent, this.syncKeybindingsPerPlatform());
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
return null;
|
||||
@@ -325,3 +330,52 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class KeybindingsInitializer extends AbstractInitializer {
|
||||
|
||||
constructor(
|
||||
@IFileService fileService: IFileService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
) {
|
||||
super(SyncResource.Keybindings, environmentService, logService, fileService);
|
||||
}
|
||||
|
||||
async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
|
||||
const keybindingsContent = remoteUserData.syncData ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null;
|
||||
if (!keybindingsContent) {
|
||||
this.logService.info('Skipping initializing keybindings because remote keybindings does not exist.');
|
||||
return;
|
||||
}
|
||||
|
||||
const isEmpty = await this.isEmpty();
|
||||
if (!isEmpty) {
|
||||
this.logService.info('Skipping initializing keybindings because local keybindings exist.');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.fileService.writeFile(this.environmentService.keybindingsResource, VSBuffer.fromString(keybindingsContent));
|
||||
|
||||
await this.updateLastSyncUserData(remoteUserData);
|
||||
}
|
||||
|
||||
private async isEmpty(): Promise<boolean> {
|
||||
try {
|
||||
const fileContent = await this.fileService.readFile(this.environmentService.settingsResource);
|
||||
const keybindings = parse(fileContent.value.toString());
|
||||
return !isNonEmptyArray(keybindings);
|
||||
} catch (error) {
|
||||
return (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
private getKeybindingsContentFromSyncContent(syncContent: string): string | null {
|
||||
try {
|
||||
return getKeybindingsContentFromSyncContent(syncContent, true);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -275,8 +275,11 @@ export function areSame(localContent: string, remoteContent: string, ignoredSett
|
||||
}
|
||||
|
||||
export function isEmpty(content: string): boolean {
|
||||
const nodes = parseSettings(content);
|
||||
return nodes.length === 0;
|
||||
if (content) {
|
||||
const nodes = parseSettings(content);
|
||||
return nodes.length === 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function compare(from: IStringDictionary<any> | null, to: IStringDictionary<any>, ignored: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
|
||||
@@ -17,7 +17,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { updateIgnoredSettings, merge, getIgnoredSettings, isEmpty } from 'vs/platform/userDataSync/common/settingsMerge';
|
||||
import { edit } from 'vs/platform/userDataSync/common/content';
|
||||
import { AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } 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';
|
||||
@@ -40,6 +40,11 @@ function isSettingsSyncContent(thing: any): thing is ISettingsSyncContent {
|
||||
&& Object.keys(thing).length === 1;
|
||||
}
|
||||
|
||||
export function parseSettingsSyncContent(syncContent: string): ISettingsSyncContent {
|
||||
const parsed = <ISettingsSyncContent>JSON.parse(syncContent);
|
||||
return isSettingsSyncContent(parsed) ? parsed : /* migrate */ { settings: syncContent };
|
||||
}
|
||||
|
||||
export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
|
||||
|
||||
/* Version 2: Change settings from `sync.${setting}` to `settingsSync.{setting}` */
|
||||
@@ -281,10 +286,9 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
return remoteUserData.syncData ? this.parseSettingsSyncContent(remoteUserData.syncData.content) : null;
|
||||
}
|
||||
|
||||
parseSettingsSyncContent(syncContent: string): ISettingsSyncContent | null {
|
||||
private parseSettingsSyncContent(syncContent: string): ISettingsSyncContent | null {
|
||||
try {
|
||||
const parsed = <ISettingsSyncContent>JSON.parse(syncContent);
|
||||
return isSettingsSyncContent(parsed) ? parsed : /* migrate */ { settings: syncContent };
|
||||
return parseSettingsSyncContent(syncContent);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
@@ -350,6 +354,54 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
}
|
||||
}
|
||||
|
||||
export class SettingsInitializer extends AbstractInitializer {
|
||||
|
||||
constructor(
|
||||
@IFileService fileService: IFileService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
) {
|
||||
super(SyncResource.Settings, environmentService, logService, fileService);
|
||||
}
|
||||
|
||||
async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
|
||||
const settingsSyncContent = remoteUserData.syncData ? this.parseSettingsSyncContent(remoteUserData.syncData.content) : null;
|
||||
if (!settingsSyncContent) {
|
||||
this.logService.info('Skipping initializing settings because remote settings does not exist.');
|
||||
return;
|
||||
}
|
||||
|
||||
const isEmpty = await this.isEmpty();
|
||||
if (!isEmpty) {
|
||||
this.logService.info('Skipping initializing settings because local settings exist.');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.fileService.writeFile(this.environmentService.settingsResource, VSBuffer.fromString(settingsSyncContent.settings));
|
||||
|
||||
await this.updateLastSyncUserData(remoteUserData);
|
||||
}
|
||||
|
||||
private async isEmpty(): Promise<boolean> {
|
||||
try {
|
||||
const fileContent = await this.fileService.readFile(this.environmentService.settingsResource);
|
||||
return isEmpty(fileContent.value.toString().trim());
|
||||
} catch (error) {
|
||||
return (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
private parseSettingsSyncContent(syncContent: string): ISettingsSyncContent | null {
|
||||
try {
|
||||
return parseSettingsSyncContent(syncContent);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function isSyncData(thing: any): thing is ISyncData {
|
||||
if (thing
|
||||
&& (thing.version !== undefined && typeof thing.version === 'number')
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
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';
|
||||
import { AbstractSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -499,3 +499,49 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
||||
return snippets;
|
||||
}
|
||||
}
|
||||
|
||||
export class SnippetsInitializer extends AbstractInitializer {
|
||||
|
||||
constructor(
|
||||
@IFileService fileService: IFileService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
) {
|
||||
super(SyncResource.Snippets, environmentService, logService, fileService);
|
||||
}
|
||||
|
||||
async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
|
||||
const remoteSnippets: IStringDictionary<string> | null = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
|
||||
if (!remoteSnippets) {
|
||||
this.logService.info('Skipping initializing snippets because remote snippets does not exist.');
|
||||
return;
|
||||
}
|
||||
|
||||
const isEmpty = await this.isEmpty();
|
||||
if (!isEmpty) {
|
||||
this.logService.info('Skipping initializing snippets because local snippets exist.');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const key of Object.keys(remoteSnippets)) {
|
||||
const content = remoteSnippets[key];
|
||||
if (content) {
|
||||
const resource = joinPath(this.environmentService.snippetsHome, key);
|
||||
await this.fileService.createFile(resource, VSBuffer.fromString(content));
|
||||
this.logService.info('Created snippet', basename(resource));
|
||||
}
|
||||
}
|
||||
|
||||
await this.updateLastSyncUserData(remoteUserData);
|
||||
}
|
||||
|
||||
private async isEmpty(): Promise<boolean> {
|
||||
try {
|
||||
const stat = await this.fileService.resolve(this.environmentService.snippetsHome);
|
||||
return !stat.children?.length;
|
||||
} catch (error) {
|
||||
return (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ export class UserDataAutoSyncEnablementService extends Disposable {
|
||||
case 'off':
|
||||
return false;
|
||||
}
|
||||
//@ts-expect-error
|
||||
return this.storageService.getBoolean(enablementKey, StorageScope.GLOBAL, this.environmentService.enableSyncByDefault);
|
||||
}
|
||||
|
||||
|
||||
@@ -164,10 +164,7 @@ export interface IUserDataSyncStoreManagementService {
|
||||
getPreviousUserDataSyncStore(): Promise<IUserDataSyncStore | undefined>;
|
||||
}
|
||||
|
||||
export const IUserDataSyncStoreService = createDecorator<IUserDataSyncStoreService>('IUserDataSyncStoreService');
|
||||
export interface IUserDataSyncStoreService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
export interface IUserDataSyncStoreClient {
|
||||
readonly onDidChangeDonotMakeRequestsUntil: Event<void>;
|
||||
readonly donotMakeRequestsUntil: Date | undefined;
|
||||
|
||||
@@ -186,6 +183,11 @@ export interface IUserDataSyncStoreService {
|
||||
resolveContent(resource: ServerResource, ref: string): Promise<string | null>;
|
||||
}
|
||||
|
||||
export const IUserDataSyncStoreService = createDecorator<IUserDataSyncStoreService>('IUserDataSyncStoreService');
|
||||
export interface IUserDataSyncStoreService extends IUserDataSyncStoreClient {
|
||||
readonly _serviceBrand: undefined;
|
||||
}
|
||||
|
||||
export const IUserDataSyncBackupStoreService = createDecorator<IUserDataSyncBackupStoreService>('IUserDataSyncBackupStoreService');
|
||||
export interface IUserDataSyncBackupStoreService {
|
||||
readonly _serviceBrand: undefined;
|
||||
@@ -208,6 +210,7 @@ export const HEADER_EXECUTION_ID = 'X-Execution-Id';
|
||||
export enum UserDataSyncErrorCode {
|
||||
// Client Errors (>= 400 )
|
||||
Unauthorized = 'Unauthorized', /* 401 */
|
||||
Conflict = 'Conflict', /* 409 */
|
||||
Gone = 'Gone', /* 410 */
|
||||
PreconditionFailed = 'PreconditionFailed', /* 412 */
|
||||
TooLarge = 'TooLarge', /* 413 */
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, } from 'vs/base/common/lifecycle';
|
||||
import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, ServerResource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, IResourceRefHandle, HEADER_OPERATION_ID, HEADER_EXECUTION_ID, CONFIGURATION_SYNC_STORE_KEY, IAuthenticationProvider, IUserDataSyncStoreManagementService, UserDataSyncStoreType } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, ServerResource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, IResourceRefHandle, HEADER_OPERATION_ID, HEADER_EXECUTION_ID, CONFIGURATION_SYNC_STORE_KEY, IAuthenticationProvider, IUserDataSyncStoreManagementService, UserDataSyncStoreType, IUserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IRequestService, asText, isSuccess as isSuccessContext, asJson } from 'vs/platform/request/common/request';
|
||||
import { joinPath, relativePath } from 'vs/base/common/resources';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
@@ -125,9 +125,7 @@ export class UserDataSyncStoreManagementService extends AbstractUserDataSyncStor
|
||||
}
|
||||
}
|
||||
|
||||
export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService {
|
||||
|
||||
_serviceBrand: any;
|
||||
export class UserDataSyncStoreClient extends Disposable implements IUserDataSyncStoreClient {
|
||||
|
||||
private readonly userDataSyncStoreUrl: URI | undefined;
|
||||
|
||||
@@ -147,16 +145,16 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
||||
readonly onDidChangeDonotMakeRequestsUntil = this._onDidChangeDonotMakeRequestsUntil.event;
|
||||
|
||||
constructor(
|
||||
userDataSyncStoreUrl: URI | undefined,
|
||||
@IProductService productService: IProductService,
|
||||
@IRequestService private readonly requestService: IRequestService,
|
||||
@IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService,
|
||||
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
) {
|
||||
super();
|
||||
this.userDataSyncStoreUrl = this.userDataSyncStoreManagementService.userDataSyncStore ? joinPath(this.userDataSyncStoreManagementService.userDataSyncStore.url, 'v1') : undefined;
|
||||
this.userDataSyncStoreUrl = userDataSyncStoreUrl ? joinPath(userDataSyncStoreUrl, 'v1') : undefined;
|
||||
this.commonHeadersPromise = getServiceMachineId(environmentService, fileService, storageService)
|
||||
.then(uuid => {
|
||||
const headers: IHeaders = {
|
||||
@@ -395,6 +393,10 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
||||
|
||||
this._onTokenSucceed.fire();
|
||||
|
||||
if (context.res.statusCode === 409) {
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of Conflict (409). There is new data exists for this resource. Make the request again with latest data.`, UserDataSyncErrorCode.Conflict, operationId);
|
||||
}
|
||||
|
||||
if (context.res.statusCode === 410) {
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because the requested resource is not longer available (410).`, UserDataSyncErrorCode.Gone, operationId);
|
||||
}
|
||||
@@ -444,6 +446,23 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
||||
|
||||
}
|
||||
|
||||
export class UserDataSyncStoreService extends UserDataSyncStoreClient implements IUserDataSyncStoreService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
constructor(
|
||||
@IUserDataSyncStoreManagementService userDataSyncStoreManagementService: IUserDataSyncStoreManagementService,
|
||||
@IProductService productService: IProductService,
|
||||
@IRequestService requestService: IRequestService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
) {
|
||||
super(userDataSyncStoreManagementService.userDataSyncStore?.url, productService, requestService, logService, environmentService, fileService, storageService);
|
||||
}
|
||||
}
|
||||
|
||||
export class RequestsSession {
|
||||
|
||||
private requests: string[] = [];
|
||||
|
||||
@@ -10,7 +10,7 @@ import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync';
|
||||
import { getKeybindingsContentFromSyncContent, KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
suite('KeybindingsSync', () => {
|
||||
@@ -70,8 +70,8 @@ suite('KeybindingsSync', () => {
|
||||
|
||||
const lastSyncUserData = await testObject.getLastSyncUserData();
|
||||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), '[]');
|
||||
assert.equal(testObject.getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!), '[]');
|
||||
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), '[]');
|
||||
assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), '[]');
|
||||
assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), '');
|
||||
});
|
||||
|
||||
@@ -95,11 +95,75 @@ suite('KeybindingsSync', () => {
|
||||
|
||||
const lastSyncUserData = await testObject.getLastSyncUserData();
|
||||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), content);
|
||||
assert.equal(testObject.getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!), content);
|
||||
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content);
|
||||
assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content);
|
||||
assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), content);
|
||||
});
|
||||
|
||||
test('when keybindings file is empty with comment and remote has no changes', async () => {
|
||||
const fileService = client.instantiationService.get(IFileService);
|
||||
const keybindingsResource = client.instantiationService.get(IEnvironmentService).keybindingsResource;
|
||||
const expectedContent = '// Empty Keybindings';
|
||||
await fileService.writeFile(keybindingsResource, VSBuffer.fromString(expectedContent));
|
||||
|
||||
await testObject.sync(await client.manifest());
|
||||
|
||||
const lastSyncUserData = await testObject.getLastSyncUserData();
|
||||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), expectedContent);
|
||||
assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), expectedContent);
|
||||
assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), expectedContent);
|
||||
});
|
||||
|
||||
test('when keybindings file is empty and remote has keybindings', async () => {
|
||||
const client2 = disposableStore.add(new UserDataSyncClient(server));
|
||||
await client2.setUp(true);
|
||||
const content = JSON.stringify([
|
||||
{
|
||||
'key': 'shift+cmd+w',
|
||||
'command': 'workbench.action.closeAllEditors',
|
||||
}
|
||||
]);
|
||||
await client2.instantiationService.get(IFileService).writeFile(client2.instantiationService.get(IEnvironmentService).keybindingsResource, VSBuffer.fromString(content));
|
||||
await client2.sync();
|
||||
|
||||
const fileService = client.instantiationService.get(IFileService);
|
||||
const keybindingsResource = client.instantiationService.get(IEnvironmentService).keybindingsResource;
|
||||
await fileService.writeFile(keybindingsResource, VSBuffer.fromString('// Empty Keybindings'));
|
||||
|
||||
await testObject.sync(await client.manifest());
|
||||
|
||||
const lastSyncUserData = await testObject.getLastSyncUserData();
|
||||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content);
|
||||
assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content);
|
||||
assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), content);
|
||||
});
|
||||
|
||||
test('when keybindings file is empty and remote has empty array', async () => {
|
||||
const client2 = disposableStore.add(new UserDataSyncClient(server));
|
||||
await client2.setUp(true);
|
||||
const content =
|
||||
`// Place your key bindings in this file to override the defaults
|
||||
[
|
||||
]`;
|
||||
await client2.instantiationService.get(IFileService).writeFile(client2.instantiationService.get(IEnvironmentService).keybindingsResource, VSBuffer.fromString(content));
|
||||
await client2.sync();
|
||||
|
||||
const fileService = client.instantiationService.get(IFileService);
|
||||
const keybindingsResource = client.instantiationService.get(IEnvironmentService).keybindingsResource;
|
||||
const expectedLocalContent = '// Empty Keybindings';
|
||||
await fileService.writeFile(keybindingsResource, VSBuffer.fromString(expectedLocalContent));
|
||||
|
||||
await testObject.sync(await client.manifest());
|
||||
|
||||
const lastSyncUserData = await testObject.getLastSyncUserData();
|
||||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content);
|
||||
assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content);
|
||||
assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), expectedLocalContent);
|
||||
});
|
||||
|
||||
test('when keybindings file is created after first sync', async () => {
|
||||
const fileService = client.instantiationService.get(IFileService);
|
||||
const keybindingsResource = client.instantiationService.get(IEnvironmentService).keybindingsResource;
|
||||
@@ -119,7 +183,7 @@ suite('KeybindingsSync', () => {
|
||||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.deepEqual(lastSyncUserData!.ref, remoteUserData.ref);
|
||||
assert.deepEqual(lastSyncUserData!.syncData, remoteUserData.syncData);
|
||||
assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), '[]');
|
||||
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), '[]');
|
||||
});
|
||||
|
||||
test('test apply remote when keybindings file does not exist', async () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as assert from 'assert';
|
||||
import { IUserDataSyncStoreService, IUserDataSyncService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, ISyncData, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient';
|
||||
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { SettingsSynchroniser, ISettingsSyncContent } from 'vs/platform/userDataSync/common/settingsSync';
|
||||
import { SettingsSynchroniser, ISettingsSyncContent, parseSettingsSyncContent } from 'vs/platform/userDataSync/common/settingsSync';
|
||||
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
@@ -88,8 +88,8 @@ suite('SettingsSync - Auto', () => {
|
||||
|
||||
const lastSyncUserData = await testObject.getLastSyncUserData();
|
||||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.equal(testObject.parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}');
|
||||
assert.equal(testObject.parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, '{}');
|
||||
assert.equal(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}');
|
||||
assert.equal(parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, '{}');
|
||||
assert.equal((await fileService.readFile(settingsResource)).value.toString(), '');
|
||||
});
|
||||
|
||||
@@ -129,8 +129,8 @@ suite('SettingsSync - Auto', () => {
|
||||
|
||||
const lastSyncUserData = await testObject.getLastSyncUserData();
|
||||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.equal(testObject.parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, content);
|
||||
assert.equal(testObject.parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, content);
|
||||
assert.equal(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, content);
|
||||
assert.equal(parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, content);
|
||||
assert.equal((await fileService.readFile(settingsResource)).value.toString(), content);
|
||||
});
|
||||
|
||||
@@ -154,7 +154,7 @@ suite('SettingsSync - Auto', () => {
|
||||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.deepEqual(lastSyncUserData!.ref, remoteUserData.ref);
|
||||
assert.deepEqual(lastSyncUserData!.syncData, remoteUserData.syncData);
|
||||
assert.equal(testObject.parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}');
|
||||
assert.equal(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}');
|
||||
});
|
||||
|
||||
test('sync for first time to the server', async () => {
|
||||
|
||||
@@ -1134,7 +1134,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
|
||||
if (forceOpenWorkspaceAsFile) {
|
||||
return { fileUri: uri, remoteAuthority };
|
||||
}
|
||||
} else if (posix.extname(anyPath).length > 0) {
|
||||
} else if (posix.basename(anyPath).indexOf('.') !== -1) { // file name starts with a dot or has an file extension
|
||||
return { fileUri: uri, remoteAuthority };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user