Merge from vscode f2d41726ba5a0e8abfe61b2c743022b1b6372010 (#7415)

* Merge from vscode f2d41726ba5a0e8abfe61b2c743022b1b6372010

* add missing files
This commit is contained in:
Anthony Dresser
2019-09-27 23:30:36 -07:00
committed by GitHub
parent d0fb6de390
commit bca7c8e6bd
123 changed files with 1704 additions and 1330 deletions

View File

@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
export const enum AuthTokenStatus {
Disabled = 'Disabled',
Inactive = 'Inactive',
Active = 'Active'
}
export const IAuthTokenService = createDecorator<IAuthTokenService>('IAuthTokenService');
export interface IAuthTokenService {
_serviceBrand: undefined;
readonly status: AuthTokenStatus;
readonly onDidChangeStatus: Event<AuthTokenStatus>;
getToken(): Promise<string | null>;
updateToken(token: string): Promise<void>;
refreshToken(): Promise<void>;
deleteToken(): Promise<void>;
}

View File

@@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event } from 'vs/base/common/event';
import { IAuthTokenService } from 'vs/platform/auth/common/auth';
export class AuthTokenChannel implements IServerChannel {
constructor(private readonly service: IAuthTokenService) { }
listen(_: unknown, event: string): Event<any> {
switch (event) {
case 'onDidChangeStatus': return this.service.onDidChangeStatus;
}
throw new Error(`Event not found: ${event}`);
}
call(context: any, command: string, args?: any): Promise<any> {
switch (command) {
case '_getInitialStatus': return Promise.resolve(this.service.status);
case 'getToken': return this.service.getToken();
case 'updateToken': return this.service.updateToken(args[0]);
case 'refreshToken': return this.service.refreshToken();
case 'deleteToken': return this.service.deleteToken();
}
throw new Error('Invalid call');
}
}

View File

@@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth';
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
import { Disposable } from 'vs/base/common/lifecycle';
import { IProductService } from 'vs/platform/product/common/productService';
const SERVICE_NAME = 'VS Code';
const ACCOUNT = 'MyAccount';
export class AuthTokenService extends Disposable implements IAuthTokenService {
_serviceBrand: undefined;
private _status: AuthTokenStatus = AuthTokenStatus.Disabled;
get status(): AuthTokenStatus { return this._status; }
private _onDidChangeStatus: Emitter<AuthTokenStatus> = this._register(new Emitter<AuthTokenStatus>());
readonly onDidChangeStatus: Event<AuthTokenStatus> = this._onDidChangeStatus.event;
constructor(
@ICredentialsService private readonly credentialsService: ICredentialsService,
@IProductService productService: IProductService,
) {
super();
if (productService.settingsSyncStoreUrl) {
this._status = AuthTokenStatus.Inactive;
this.getToken().then(token => {
if (token) {
this.setStatus(AuthTokenStatus.Active);
}
});
}
}
getToken(): Promise<string | null> {
if (this.status === AuthTokenStatus.Disabled) {
throw new Error('Not enabled');
}
return this.credentialsService.getPassword(SERVICE_NAME, ACCOUNT);
}
async updateToken(token: string): Promise<void> {
if (this.status === AuthTokenStatus.Disabled) {
throw new Error('Not enabled');
}
await this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, token);
this.setStatus(AuthTokenStatus.Active);
}
async refreshToken(): Promise<void> {
if (this.status === AuthTokenStatus.Disabled) {
throw new Error('Not enabled');
}
await this.deleteToken();
}
async deleteToken(): Promise<void> {
if (this.status === AuthTokenStatus.Disabled) {
throw new Error('Not enabled');
}
await this.credentialsService.deletePassword(SERVICE_NAME, ACCOUNT);
this.setStatus(AuthTokenStatus.Inactive);
}
private setStatus(status: AuthTokenStatus): void {
if (this._status !== status) {
this._status = status;
this._onDidChangeStatus.fire(status);
}
}
}

View File

@@ -1,56 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { URI } from 'vs/base/common/uri';
export class BrowserClipboardService implements IClipboardService {
_serviceBrand: undefined;
private _internalResourcesClipboard: URI[] | undefined;
async writeText(text: string, type?: string): Promise<void> {
if (type) {
return; // TODO@sbatten
}
return navigator.clipboard.writeText(text);
}
async readText(type?: string): Promise<string> {
if (type) {
return ''; // TODO@sbatten
}
return navigator.clipboard.readText();
}
readTextSync(): string | undefined {
return undefined;
}
readFindText(): string {
// @ts-ignore
return undefined;
}
writeFindText(text: string): void { }
writeResources(resources: URI[]): void {
this._internalResourcesClipboard = resources;
}
readResources(): URI[] {
return this._internalResourcesClipboard || [];
}
hasResources(): boolean {
return this._internalResourcesClipboard !== undefined && this._internalResourcesClipboard.length > 0;
}
}
registerSingleton(IClipboardService, BrowserClipboardService, true);

View File

@@ -1,77 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { clipboard } from 'electron';
import { URI } from 'vs/base/common/uri';
import { isMacintosh } from 'vs/base/common/platform';
export class ClipboardService implements IClipboardService {
private static FILE_FORMAT = 'code/file-list'; // Clipboard format for files
_serviceBrand: undefined;
async writeText(text: string, type?: 'selection' | 'clipboard'): Promise<void> {
clipboard.writeText(text, type);
}
async readText(type?: 'selection' | 'clipboard'): Promise<string> {
return clipboard.readText(type);
}
readTextSync(): string {
return clipboard.readText();
}
readFindText(): string {
if (isMacintosh) {
return clipboard.readFindText();
}
return '';
}
writeFindText(text: string): void {
if (isMacintosh) {
clipboard.writeFindText(text);
}
}
writeResources(resources: URI[]): void {
if (resources.length) {
clipboard.writeBuffer(ClipboardService.FILE_FORMAT, this.resourcesToBuffer(resources));
}
}
readResources(): URI[] {
return this.bufferToResources(clipboard.readBuffer(ClipboardService.FILE_FORMAT));
}
hasResources(): boolean {
return clipboard.has(ClipboardService.FILE_FORMAT);
}
private resourcesToBuffer(resources: URI[]): Buffer {
return Buffer.from(resources.map(r => r.toString()).join('\n'));
}
private bufferToResources(buffer: Buffer): URI[] {
if (!buffer) {
return [];
}
const bufferValue = buffer.toString();
if (!bufferValue) {
return [];
}
try {
return bufferValue.split('\n').map(f => URI.parse(f));
} catch (error) {
return []; // do not trust clipboard data
}
}
}

View File

@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const ICredentialsService = createDecorator<ICredentialsService>('ICredentialsService');
export interface ICredentialsService {
_serviceBrand: undefined;
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 }>>;
}

View File

@@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
import { IdleValue } from 'vs/base/common/async';
type KeytarModule = typeof import('keytar');
export class KeytarCredentialsService implements ICredentialsService {
_serviceBrand: undefined;
private readonly _keytar = new IdleValue<Promise<KeytarModule>>(() => import('keytar'));
async getPassword(service: string, account: string): Promise<string | null> {
const keytar = await this._keytar.getValue();
return keytar.getPassword(service, account);
}
async setPassword(service: string, account: string, password: string): Promise<void> {
const keytar = await this._keytar.getValue();
return keytar.setPassword(service, account, password);
}
async deletePassword(service: string, account: string): Promise<boolean> {
const keytar = await this._keytar.getValue();
return keytar.deletePassword(service, account);
}
async findPassword(service: string): Promise<string | null> {
const keytar = await this._keytar.getValue();
return keytar.findPassword(service);
}
async findCredentials(service: string): Promise<Array<{ account: string, password: string }>> {
const keytar = await this._keytar.getValue();
return keytar.findCredentials(service);
}
}

View File

@@ -6,17 +6,15 @@
import { Event } from 'vs/base/common/event';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, CrashReporterStartOptions, crashReporter, Menu, BrowserWindow, app } from 'electron';
import { INativeOpenInWindowOptions } from 'vs/platform/windows/node/window';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IOpenedWindow, OpenContext, IWindowOpenable, IOpenInWindowOptions, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows';
import { IOpenedWindow, OpenContext, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows';
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs';
import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform';
import { IElectronService } from 'vs/platform/electron/node/electron';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { AddFirstParameterToFunctions } from 'vs/base/common/types';
import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
import { IRecentlyOpened, IRecent } from 'vs/platform/workspaces/common/workspacesHistory';
import { URI } from 'vs/base/common/uri';
export class ElectronMainService implements AddFirstParameterToFunctions<IElectronService, Promise<any> /* only methods, not events */, number /* window ID */> {
@@ -25,8 +23,7 @@ export class ElectronMainService implements AddFirstParameterToFunctions<IElectr
constructor(
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService
@IEnvironmentService private readonly environmentService: IEnvironmentService
) {
}
@@ -76,7 +73,7 @@ export class ElectronMainService implements AddFirstParameterToFunctions<IElectr
this.windowsMainService.openEmptyWindow(OpenContext.API, options);
}
async openInWindow(windowId: number, toOpen: IWindowOpenable[], options: IOpenInWindowOptions = Object.create(null)): Promise<void> {
async openInWindow(windowId: number, toOpen: IWindowOpenable[], options: INativeOpenInWindowOptions = Object.create(null)): Promise<void> {
if (toOpen.length > 0) {
this.windowsMainService.open({
context: OpenContext.API,
@@ -332,33 +329,6 @@ export class ElectronMainService implements AddFirstParameterToFunctions<IElectr
//#endregion
//#region Workspaces History
readonly onRecentlyOpenedChange = this.workspacesHistoryMainService.onRecentlyOpenedChange;
async getRecentlyOpened(windowId: number): Promise<IRecentlyOpened> {
const window = this.windowsMainService.getWindowById(windowId);
if (window) {
return this.workspacesHistoryMainService.getRecentlyOpened(window.config.workspace, window.config.folderUri, window.config.filesToOpenOrCreate);
}
return this.workspacesHistoryMainService.getRecentlyOpened();
}
async addRecentlyOpened(windowId: number, recents: IRecent[]): Promise<void> {
return this.workspacesHistoryMainService.addRecentlyOpened(recents);
}
async removeFromRecentlyOpened(windowId: number, paths: URI[]): Promise<void> {
return this.workspacesHistoryMainService.removeFromRecentlyOpened(paths);
}
async clearRecentlyOpened(windowId: number): Promise<void> {
return this.workspacesHistoryMainService.clearRecentlyOpened();
}
//#endregion
//#region Debug
// TODO@Isidor move into debug IPC channel (https://github.com/microsoft/vscode/issues/81060)

View File

@@ -6,13 +6,12 @@
import { Event } from 'vs/base/common/event';
import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogReturnValue, CrashReporterStartOptions } from 'electron';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWindowOpenable, IOpenInWindowOptions, IOpenEmptyWindowOptions, IOpenedWindow } from 'vs/platform/windows/common/windows';
import { IWindowOpenable, IOpenEmptyWindowOptions, IOpenedWindow } from 'vs/platform/windows/common/windows';
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { IRecentlyOpened, IRecent } from 'vs/platform/workspaces/common/workspacesHistory';
import { URI } from 'vs/base/common/uri';
import { ParsedArgs } from 'vscode-minimist';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { INativeOpenInWindowOptions } from 'vs/platform/windows/node/window';
export const IElectronService = createDecorator<IElectronService>('electronService');
@@ -35,7 +34,7 @@ export interface IElectronService {
getActiveWindowId(): Promise<number | undefined>;
openEmptyWindow(options?: IOpenEmptyWindowOptions): Promise<void>;
openInWindow(toOpen: IWindowOpenable[], options?: IOpenInWindowOptions): Promise<void>;
openInWindow(toOpen: IWindowOpenable[], options?: INativeOpenInWindowOptions): Promise<void>;
toggleFullScreen(): Promise<void>;
@@ -89,13 +88,6 @@ export interface IElectronService {
// Connectivity
resolveProxy(url: string): Promise<string | undefined>;
// Workspaces History
readonly onRecentlyOpenedChange: Event<void>;
getRecentlyOpened(): Promise<IRecentlyOpened>;
addRecentlyOpened(recents: IRecent[]): Promise<void>;
removeFromRecentlyOpened(paths: URI[]): Promise<void>;
clearRecentlyOpened(): Promise<void>;
// Debug (TODO@Isidor move into debug IPC channel (https://github.com/microsoft/vscode/issues/81060)
openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise<void>;
}

View File

@@ -22,40 +22,42 @@ export namespace _util {
// --- interfaces ------
type BrandedService = { _serviceBrand: undefined };
export interface IConstructorSignature0<T> {
new(...services: { _serviceBrand: undefined; }[]): T;
new(...services: BrandedService[]): T;
}
export interface IConstructorSignature1<A1, T> {
new(first: A1, ...services: { _serviceBrand: undefined; }[]): T;
new(first: A1, ...services: BrandedService[]): T;
}
export interface IConstructorSignature2<A1, A2, T> {
new(first: A1, second: A2, ...services: { _serviceBrand: undefined; }[]): T;
new(first: A1, second: A2, ...services: BrandedService[]): T;
}
export interface IConstructorSignature3<A1, A2, A3, T> {
new(first: A1, second: A2, third: A3, ...services: { _serviceBrand: undefined; }[]): T;
new(first: A1, second: A2, third: A3, ...services: BrandedService[]): T;
}
export interface IConstructorSignature4<A1, A2, A3, A4, T> {
new(first: A1, second: A2, third: A3, fourth: A4, ...services: { _serviceBrand: undefined; }[]): T;
new(first: A1, second: A2, third: A3, fourth: A4, ...services: BrandedService[]): T;
}
export interface IConstructorSignature5<A1, A2, A3, A4, A5, T> {
new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, ...services: { _serviceBrand: undefined; }[]): T;
new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, ...services: BrandedService[]): T;
}
export interface IConstructorSignature6<A1, A2, A3, A4, A5, A6, T> {
new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, ...services: { _serviceBrand: undefined; }[]): T;
new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, ...services: BrandedService[]): T;
}
export interface IConstructorSignature7<A1, A2, A3, A4, A5, A6, A7, T> {
new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7, ...services: { _serviceBrand: undefined; }[]): T;
new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7, ...services: BrandedService[]): T;
}
export interface IConstructorSignature8<A1, A2, A3, A4, A5, A6, A7, A8, T> {
new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7, eigth: A8, ...services: { _serviceBrand: undefined; }[]): T;
new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7, eigth: A8, ...services: BrandedService[]): T;
}
export interface ServicesAccessor {

View File

@@ -1,17 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IIssueService } from 'vs/platform/issue/node/issue';
import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
import { createChannelSender } from 'vs/base/parts/ipc/node/ipcChannelCreator';
export class IssueService {
_serviceBrand: undefined;
constructor(@IMainProcessService mainProcessService: IMainProcessService) {
return createChannelSender<IIssueService>(mainProcessService.getChannel('issue'));
}
}

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { ILogService } from 'vs/platform/log/common/log';
import { IURLService } from 'vs/platform/url/common/url';
import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform';
@@ -16,7 +15,6 @@ import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/wor
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { URI } from 'vs/base/common/uri';
import { BrowserWindow, ipcMain, Event as IpcEvent, app } from 'electron';
import { Event } from 'vs/base/common/event';
import { coalesce } from 'vs/base/common/arrays';
import { IDiagnosticInfoOptions, IDiagnosticInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics';
import { IMainProcessInfo, IWindowInfo } from 'vs/platform/launch/common/launch';
@@ -60,64 +58,6 @@ export interface ILaunchMainService {
getRemoteDiagnostics(options: IRemoteDiagnosticOptions): Promise<(IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]>;
}
export class LaunchChannel implements IServerChannel {
constructor(private service: ILaunchMainService) { }
listen<T>(_: unknown, event: string): Event<T> {
throw new Error(`Event not found: ${event}`);
}
call(_: unknown, command: string, arg: any): Promise<any> {
switch (command) {
case 'start':
const { args, userEnv } = arg as IStartArguments;
return this.service.start(args, userEnv);
case 'get-main-process-id':
return this.service.getMainProcessId();
case 'get-main-process-info':
return this.service.getMainProcessInfo();
case 'get-logs-path':
return this.service.getLogsPath();
case 'get-remote-diagnostics':
return this.service.getRemoteDiagnostics(arg);
}
throw new Error(`Call not found: ${command}`);
}
}
export class LaunchChannelClient implements ILaunchMainService {
_serviceBrand: undefined;
constructor(private channel: IChannel) { }
start(args: ParsedArgs, userEnv: IProcessEnvironment): Promise<void> {
return this.channel.call('start', { args, userEnv });
}
getMainProcessId(): Promise<number> {
return this.channel.call('get-main-process-id', null);
}
getMainProcessInfo(): Promise<IMainProcessInfo> {
return this.channel.call('get-main-process-info', null);
}
getLogsPath(): Promise<string> {
return this.channel.call('get-logs-path', null);
}
getRemoteDiagnostics(options: IRemoteDiagnosticOptions): Promise<IRemoteDiagnosticInfo[]> {
return this.channel.call('get-remote-diagnostics', options);
}
}
export class LaunchMainService implements ILaunchMainService {
_serviceBrand: undefined;

View File

@@ -1,26 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event } from 'vs/base/common/event';
import { ILocalizationsService, LanguageType } from 'vs/platform/localizations/common/localizations';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
export class LocalizationsService implements ILocalizationsService {
_serviceBrand: undefined;
private channel: IChannel;
constructor(@ISharedProcessService sharedProcessService: ISharedProcessService) {
this.channel = sharedProcessService.getChannel('localizations');
}
get onDidLanguagesChange(): Event<void> { return this.channel.listen('onDidLanguagesChange'); }
getLanguageIds(type?: LanguageType): Promise<string[]> {
return this.channel.call('getLanguageIds', type);
}
}

View File

@@ -1,33 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event } from 'vs/base/common/event';
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
export class LocalizationsChannel implements IServerChannel {
onDidLanguagesChange: Event<void>;
constructor(private service: ILocalizationsService) {
this.onDidLanguagesChange = Event.buffer(service.onDidLanguagesChange, true);
}
listen(_: unknown, event: string): Event<any> {
switch (event) {
case 'onDidLanguagesChange': return this.onDidLanguagesChange;
}
throw new Error(`Event not found: ${event}`);
}
call(_: unknown, command: string, arg?: any): Promise<any> {
switch (command) {
case 'getLanguageIds': return this.service.getLanguageIds(arg);
}
throw new Error(`Call not found: ${command}`);
}
}

View File

@@ -1,17 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IMenubarService } from 'vs/platform/menubar/node/menubar';
import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
import { createChannelSender } from 'vs/base/parts/ipc/node/ipcChannelCreator';
export class MenubarService {
_serviceBrand: undefined;
constructor(@IMainProcessService mainProcessService: IMainProcessService) {
return createChannelSender<IMenubarService>(mainProcessService.getChannel('menubar'));
}
}

View File

@@ -21,7 +21,7 @@ export interface IRequestService {
resolveProxy(url: string): Promise<string | undefined>;
}
function isSuccess(context: IRequestContext): boolean {
export function isSuccess(context: IRequestContext): boolean {
return (context.res.statusCode && context.res.statusCode >= 200 && context.res.statusCode < 300) || context.res.statusCode === 1223;
}

View File

@@ -1,58 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event, Emitter } from 'vs/base/common/event';
import { IUpdateService, State } from 'vs/platform/update/common/update';
import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
export class UpdateService implements IUpdateService {
_serviceBrand: undefined;
private readonly _onStateChange = new Emitter<State>();
readonly onStateChange: Event<State> = this._onStateChange.event;
private _state: State = State.Uninitialized;
get state(): State { return this._state; }
private channel: IChannel;
constructor(@IMainProcessService mainProcessService: IMainProcessService) {
this.channel = mainProcessService.getChannel('update');
// always set this._state as the state changes
this.onStateChange(state => this._state = state);
this.channel.call<State>('_getInitialState').then(state => {
// fire initial state
this._onStateChange.fire(state);
// fire subsequent states as they come in from remote
this.channel.listen<State>('onStateChange')(state => this._onStateChange.fire(state));
});
}
checkForUpdates(context: any): Promise<void> {
return this.channel.call('checkForUpdates', context);
}
downloadUpdate(): Promise<void> {
return this.channel.call('downloadUpdate');
}
applyUpdate(): Promise<void> {
return this.channel.call('applyUpdate');
}
quitAndInstall(): Promise<void> {
return this.channel.call('quitAndInstall');
}
isLatestVersion(): Promise<boolean> {
return this.channel.call('isLatestVersion');
}
}

View File

@@ -4,49 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import { IChannel, IServerChannel, IClientRouter, IConnectionHub, Client } from 'vs/base/parts/ipc/common/ipc';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { IURLService, IURLHandler } from 'vs/platform/url/common/url';
import { IURLHandler } from 'vs/platform/url/common/url';
import { CancellationToken } from 'vs/base/common/cancellation';
import { first } from 'vs/base/common/arrays';
export class URLServiceChannel implements IServerChannel {
constructor(private service: IURLService) { }
listen<T>(_: unknown, event: string): Event<T> {
throw new Error(`Event not found: ${event}`);
}
call(_: unknown, command: string, arg?: any): Promise<any> {
switch (command) {
case 'open': return this.service.open(URI.revive(arg));
}
throw new Error(`Call not found: ${command}`);
}
}
export class URLServiceChannelClient implements IURLService {
_serviceBrand: undefined;
constructor(private channel: IChannel) { }
open(url: URI): Promise<boolean> {
return this.channel.call('open', url.toJSON());
}
registerHandler(handler: IURLHandler): IDisposable {
throw new Error('Not implemented.');
}
create(_options?: Partial<UriComponents>): URI {
throw new Error('Method not implemented.');
}
}
export class URLHandlerChannel implements IServerChannel {
constructor(private handler: IURLHandler) { }

View File

@@ -79,6 +79,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
return false;
}
this.logService.trace('Extensions: Started synchronising extensions...');
this.setStatus(SyncStatus.Syncing);
try {
@@ -142,7 +143,9 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
remoteData = await this.writeToRemote(remote, remoteData.ref);
}
if (remoteData.content) {
if (remoteData.content
&& (!lastSyncData || lastSyncData.ref !== remoteData.ref)
) {
// update last sync
this.logService.info('Extensions: Updating last synchronised extensions...');
await this.updateLastSyncValue(remoteData);

View File

@@ -177,6 +177,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
// Delete the preview
await this.fileService.del(this.environmentService.settingsSyncPreviewResource);
} else {
this.logService.trace('Settings: No changes found during synchronising settings.');
}
this.logService.trace('Settings: Finised synchronising settings.');

View File

@@ -104,11 +104,6 @@ export interface IUserDataSyncStoreService {
readonly enabled: boolean;
readonly loggedIn: boolean;
readonly onDidChangeLoggedIn: Event<boolean>;
login(): Promise<void>;
logout(): Promise<void>;
read(key: string, oldValue: IUserData | null): Promise<IUserData>;
write(key: string, content: string, ref: string | null): Promise<string>;
}

View File

@@ -12,6 +12,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { timeout } from 'vs/base/common/async';
import { ExtensionsSynchroniser } from 'vs/platform/userDataSync/common/extensionsSync';
import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth';
export class UserDataSyncService extends Disposable implements IUserDataSyncService {
@@ -35,6 +36,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
constructor(
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IAuthTokenService private readonly authTokenService: IAuthTokenService,
) {
super();
this.settingsSynchroniser = this._register(this.instantiationService.createInstance(SettingsSynchroniser));
@@ -43,12 +45,16 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
this.updateStatus();
this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus()));
this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal));
this._register(authTokenService.onDidChangeStatus(() => this.onDidChangeAuthTokenStatus()));
}
async sync(_continue?: boolean): Promise<boolean> {
if (!this.userDataSyncStoreService.enabled) {
throw new Error('Not enabled');
}
if (this.authTokenService.status === AuthTokenStatus.Inactive) {
return Promise.reject('Not Authenticated. Please sign in to start sync.');
}
for (const synchroniser of this.synchronisers) {
if (!await synchroniser.sync(_continue)) {
return false;
@@ -105,36 +111,51 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return null;
}
private onDidChangeAuthTokenStatus(): void {
if (this.authTokenService.status === AuthTokenStatus.Inactive) {
this.stop();
}
}
}
export class UserDataAutoSync extends Disposable {
private enabled: boolean = false;
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@IUserDataSyncLogService private readonly userDataSyncLogService: IUserDataSyncLogService,
@IAuthTokenService private readonly authTokenService: IAuthTokenService,
) {
super();
if (userDataSyncStoreService.enabled) {
this.sync(true);
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('configurationSync.enable'))(() => {
if (this.isSyncEnabled()) {
userDataSyncLogService.info('Syncing configuration started...');
this.sync(true);
} else {
this.userDataSyncService.stop();
userDataSyncLogService.info('Syncing configuration stopped.');
}
}));
this.updateEnablement();
this.sync(true);
this._register(Event.any<any>(authTokenService.onDidChangeStatus, userDataSyncService.onDidChangeStatus)(() => this.updateEnablement()));
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('configurationSync.enable'))(() => this.updateEnablement()));
// Sync immediately if there is a local change.
this._register(Event.debounce(this.userDataSyncService.onDidChangeLocal, () => undefined, 500)(() => this.sync(false)));
// Sync immediately if there is a local change.
this._register(Event.debounce(this.userDataSyncService.onDidChangeLocal, () => undefined, 500)(() => this.sync(false)));
}
private updateEnablement(): void {
const enabled = this.isSyncEnabled();
if (this.enabled !== enabled) {
this.enabled = enabled;
if (this.enabled) {
this.userDataSyncLogService.info('Syncing configuration started');
this.sync(true);
} else {
this.userDataSyncService.stop();
this.userDataSyncLogService.info('Syncing configuration stopped.');
}
}
}
private async sync(loop: boolean): Promise<void> {
if (this.isSyncEnabled()) {
if (this.enabled) {
try {
await this.userDataSyncService.sync();
} catch (e) {
@@ -148,7 +169,9 @@ export class UserDataAutoSync extends Disposable {
}
private isSyncEnabled(): boolean {
return this.configurationService.getValue<boolean>('configurationSync.enable');
return this.configurationService.getValue<boolean>('configurationSync.enable')
&& this.userDataSyncService.status !== SyncStatus.Uninitialized
&& this.authTokenService.status !== AuthTokenStatus.Inactive;
}
}

View File

@@ -4,14 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, } from 'vs/base/common/lifecycle';
import { IUserData, IUserDataSyncStoreService, UserDataSyncStoreErrorCode, UserDataSyncStoreError } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserData, IUserDataSyncStoreService, UserDataSyncStoreErrorCode, UserDataSyncStoreError, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
import { IProductService } from 'vs/platform/product/common/productService';
import { Emitter, Event } from 'vs/base/common/event';
import { IRequestService, asText } from 'vs/platform/request/common/request';
import { IRequestService, asText, isSuccess } from 'vs/platform/request/common/request';
import { URI } from 'vs/base/common/uri';
import { joinPath } from 'vs/base/common/resources';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IHeaders } from 'vs/base/parts/request/common/request';
import { IHeaders, IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/request';
import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth';
export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService {
@@ -19,24 +19,15 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
get enabled(): boolean { return !!this.productService.settingsSyncStoreUrl; }
private _loggedIn: boolean = false;
get loggedIn(): boolean { return this._loggedIn; }
private readonly _onDidChangeLoggedIn: Emitter<boolean> = this._register(new Emitter<boolean>());
readonly onDidChangeLoggedIn: Event<boolean> = this._onDidChangeLoggedIn.event;
constructor(
@IProductService private readonly productService: IProductService,
@IRequestService private readonly requestService: IRequestService,
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
@IAuthTokenService private readonly authTokenService: IAuthTokenService,
) {
super();
}
async login(): Promise<void> {
}
async logout(): Promise<void> {
}
async read(key: string, oldValue: IUserData | null): Promise<IUserData> {
if (!this.enabled) {
return Promise.reject(new Error('No settings sync store url configured.'));
@@ -48,13 +39,17 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
headers['If-None-Match'] = oldValue.ref;
}
const context = await this.requestService.request({ type: 'GET', url, headers }, CancellationToken.None);
const context = await this.request({ type: 'GET', url, headers }, CancellationToken.None);
if (context.res.statusCode === 304) {
// There is no new value. Hence return the old value.
return oldValue!;
}
if (!isSuccess(context)) {
throw new Error('Server returned ' + context.res.statusCode);
}
const ref = context.res.headers['etag'];
if (!ref) {
throw new Error('Server did not return the ref');
@@ -74,13 +69,17 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
headers['If-Match'] = ref;
}
const context = await this.requestService.request({ type: 'POST', url, data, headers }, CancellationToken.None);
const context = await this.request({ type: 'POST', url, data, headers }, CancellationToken.None);
if (context.res.statusCode === 412) {
// There is a new value. Throw Rejected Error
throw new UserDataSyncStoreError('New data exists', UserDataSyncStoreErrorCode.Rejected);
}
if (!isSuccess(context)) {
throw new Error('Server returned ' + context.res.statusCode);
}
const newRef = context.res.headers['etag'];
if (!newRef) {
throw new Error('Server did not return the ref');
@@ -88,4 +87,27 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
return newRef;
}
private async request(options: IRequestOptions, token: CancellationToken): Promise<IRequestContext> {
if (this.authTokenService.status !== AuthTokenStatus.Disabled) {
const authToken = await this.authTokenService.getToken();
if (!authToken) {
return Promise.reject(new Error('No Auth Token Available.'));
}
options.headers = options.headers || {};
options.headers['authorization'] = `Bearer ${authToken}`;
}
const context = await this.requestService.request(options, token);
if (context.res.statusCode === 401) {
// Not Authorized
this.logService.info('Authroization Failed.');
this.authTokenService.refreshToken();
Promise.reject('Authroization Failed.');
}
return context;
}
}

View File

@@ -22,11 +22,8 @@ export interface IOpenedWindow {
export interface IOpenInWindowOptions {
forceNewWindow?: boolean;
forceReuseWindow?: boolean;
diffMode?: boolean;
addMode?: boolean;
gotoLineMode?: boolean;
noRecentEntry?: boolean;
waitMarkerFileURI?: URI;
}
export interface IOpenEmptyWindowOptions {

View File

@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IOpenInWindowOptions } from 'vs/platform/windows/common/windows';
import { URI } from 'vs/base/common/uri';
export interface INativeOpenInWindowOptions extends IOpenInWindowOptions {
diffMode?: boolean;
addMode?: boolean;
gotoLineMode?: boolean;
waitMarkerFileURI?: URI;
}

View File

@@ -12,35 +12,23 @@ import { IWorkspaceIdentifier, IStoredWorkspaceFolder, isRawFileWorkspaceFolder,
export const IWorkspaceContextService = createDecorator<IWorkspaceContextService>('contextService');
export const enum WorkbenchState {
EMPTY = 1,
FOLDER,
WORKSPACE
}
export interface IWorkspaceFoldersChangeEvent {
added: IWorkspaceFolder[];
removed: IWorkspaceFolder[];
changed: IWorkspaceFolder[];
}
export interface IWorkspaceContextService {
_serviceBrand: undefined;
/**
* An event which fires on workbench state changes.
*/
onDidChangeWorkbenchState: Event<WorkbenchState>;
readonly onDidChangeWorkbenchState: Event<WorkbenchState>;
/**
* An event which fires on workspace name changes.
*/
onDidChangeWorkspaceName: Event<void>;
readonly onDidChangeWorkspaceName: Event<void>;
/**
* An event which fires on workspace folders change.
*/
onDidChangeWorkspaceFolders: Event<IWorkspaceFoldersChangeEvent>;
readonly onDidChangeWorkspaceFolders: Event<IWorkspaceFoldersChangeEvent>;
/**
* Provides access to the complete workspace object.
@@ -79,6 +67,18 @@ export interface IWorkspaceContextService {
isInsideWorkspace(resource: URI): boolean;
}
export const enum WorkbenchState {
EMPTY = 1,
FOLDER,
WORKSPACE
}
export interface IWorkspaceFoldersChangeEvent {
added: IWorkspaceFolder[];
removed: IWorkspaceFolder[];
changed: IWorkspaceFolder[];
}
export namespace IWorkspace {
export function isIWorkspace(thing: any): thing is IWorkspace {
return thing && typeof thing === 'object'
@@ -106,6 +106,7 @@ export interface IWorkspace {
}
export interface IWorkspaceFolderData {
/**
* The associated URI for this workspace folder.
*/
@@ -264,5 +265,6 @@ export function toWorkspaceFolders(configuredFolders: IStoredWorkspaceFolder[],
}
}
}
return result;
}

View File

@@ -17,11 +17,67 @@ import { normalizeDriveLetter } from 'vs/base/common/labels';
import { toSlashes } from 'vs/base/common/extpath';
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
import { ILogService } from 'vs/platform/log/common/log';
import { Event as CommonEvent } from 'vs/base/common/event';
export const WORKSPACE_EXTENSION = 'code-workspace';
export const WORKSPACE_FILTER = [{ name: localize('codeWorkspace', "Code Workspace"), extensions: [WORKSPACE_EXTENSION] }];
export const UNTITLED_WORKSPACE_NAME = 'workspace.json';
export const IWorkspacesService = createDecorator<IWorkspacesService>('workspacesService');
export interface IWorkspacesService {
_serviceBrand: undefined;
// Management
enterWorkspace(path: URI): Promise<IEnterWorkspaceResult | undefined>;
createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier>;
deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise<void>;
getWorkspaceIdentifier(workspacePath: URI): Promise<IWorkspaceIdentifier>;
// History
readonly onRecentlyOpenedChange: CommonEvent<void>;
addRecentlyOpened(recents: IRecent[]): Promise<void>;
removeFromRecentlyOpened(workspaces: URI[]): Promise<void>;
clearRecentlyOpened(): Promise<void>;
getRecentlyOpened(): Promise<IRecentlyOpened>;
}
export interface IRecentlyOpened {
workspaces: Array<IRecentWorkspace | IRecentFolder>;
files: IRecentFile[];
}
export type IRecent = IRecentWorkspace | IRecentFolder | IRecentFile;
export interface IRecentWorkspace {
workspace: IWorkspaceIdentifier;
label?: string;
}
export interface IRecentFolder {
folderUri: ISingleFolderWorkspaceIdentifier;
label?: string;
}
export interface IRecentFile {
fileUri: URI;
label?: string;
}
export function isRecentWorkspace(curr: IRecent): curr is IRecentWorkspace {
return curr.hasOwnProperty('workspace');
}
export function isRecentFolder(curr: IRecent): curr is IRecentFolder {
return curr.hasOwnProperty('folderUri');
}
export function isRecentFile(curr: IRecent): curr is IRecentFile {
return curr.hasOwnProperty('fileUri');
}
/**
* A single folder workspace identifier is just the path to the folder.
*/
@@ -76,11 +132,6 @@ export interface IStoredWorkspace {
remoteAuthority?: string;
}
export interface IWorkspaceSavedEvent {
workspace: IWorkspaceIdentifier;
oldConfigPath: string;
}
export interface IWorkspaceFolderCreationData {
uri: URI;
name?: string;
@@ -96,21 +147,6 @@ export interface IEnterWorkspaceResult {
backupPath?: string;
}
export const IWorkspacesService = createDecorator<IWorkspacesService>('workspacesService');
export interface IWorkspacesService {
_serviceBrand: undefined;
enterWorkspace(path: URI): Promise<IEnterWorkspaceResult | undefined>;
createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier>;
deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise<void>;
getWorkspaceIdentifier(workspacePath: URI): Promise<IWorkspaceIdentifier>;
}
export function isSingleFolderWorkspaceIdentifier(obj: any): obj is ISingleFolderWorkspaceIdentifier {
return obj instanceof URI;
}
@@ -267,3 +303,129 @@ export function useSlashForPath(storedFolders: IStoredWorkspaceFolder[]): boolea
}
return true;
}
//#region Workspace Storage
interface ISerializedRecentlyOpened {
workspaces3: Array<ISerializedWorkspace | string>; // workspace or URI.toString() // added in 1.32
workspaceLabels?: Array<string | null>; // added in 1.33
files2: string[]; // files as URI.toString() // added in 1.32
fileLabels?: Array<string | null>; // added in 1.33
}
interface ILegacySerializedRecentlyOpened {
workspaces2: Array<ILegacySerializedWorkspace | string>; // legacy, configPath as file path
workspaces: Array<ILegacySerializedWorkspace | string | UriComponents>; // legacy (UriComponents was also supported for a few insider builds)
files: string[]; // files as paths
}
interface ISerializedWorkspace { id: string; configURIPath: string; }
interface ILegacySerializedWorkspace { id: string; configPath: string; }
function isLegacySerializedWorkspace(curr: any): curr is ILegacySerializedWorkspace {
return typeof curr === 'object' && typeof curr['id'] === 'string' && typeof curr['configPath'] === 'string';
}
function isUriComponents(curr: any): curr is UriComponents {
return curr && typeof curr['path'] === 'string' && typeof curr['scheme'] === 'string';
}
export type RecentlyOpenedStorageData = object;
export function restoreRecentlyOpened(data: RecentlyOpenedStorageData | undefined, logService: ILogService): IRecentlyOpened {
const result: IRecentlyOpened = { workspaces: [], files: [] };
if (data) {
const restoreGracefully = function <T>(entries: T[], func: (entry: T, index: number) => void) {
for (let i = 0; i < entries.length; i++) {
try {
func(entries[i], i);
} catch (e) {
logService.warn(`Error restoring recent entry ${JSON.stringify(entries[i])}: ${e.toString()}. Skip entry.`);
}
}
};
const storedRecents = data as ISerializedRecentlyOpened & ILegacySerializedRecentlyOpened;
if (Array.isArray(storedRecents.workspaces3)) {
restoreGracefully(storedRecents.workspaces3, (workspace, i) => {
const label: string | undefined = (Array.isArray(storedRecents.workspaceLabels) && storedRecents.workspaceLabels[i]) || undefined;
if (typeof workspace === 'object' && typeof workspace.id === 'string' && typeof workspace.configURIPath === 'string') {
result.workspaces.push({ label, workspace: { id: workspace.id, configPath: URI.parse(workspace.configURIPath) } });
} else if (typeof workspace === 'string') {
result.workspaces.push({ label, folderUri: URI.parse(workspace) });
}
});
} else if (Array.isArray(storedRecents.workspaces2)) {
restoreGracefully(storedRecents.workspaces2, workspace => {
if (typeof workspace === 'object' && typeof workspace.id === 'string' && typeof workspace.configPath === 'string') {
result.workspaces.push({ workspace: { id: workspace.id, configPath: URI.file(workspace.configPath) } });
} else if (typeof workspace === 'string') {
result.workspaces.push({ folderUri: URI.parse(workspace) });
}
});
} else if (Array.isArray(storedRecents.workspaces)) {
// TODO@martin legacy support can be removed at some point (6 month?)
// format of 1.25 and before
restoreGracefully(storedRecents.workspaces, workspace => {
if (typeof workspace === 'string') {
result.workspaces.push({ folderUri: URI.file(workspace) });
} else if (isLegacySerializedWorkspace(workspace)) {
result.workspaces.push({ workspace: { id: workspace.id, configPath: URI.file(workspace.configPath) } });
} else if (isUriComponents(workspace)) {
// added by 1.26-insiders
result.workspaces.push({ folderUri: URI.revive(<UriComponents>workspace) });
}
});
}
if (Array.isArray(storedRecents.files2)) {
restoreGracefully(storedRecents.files2, (file, i) => {
const label: string | undefined = (Array.isArray(storedRecents.fileLabels) && storedRecents.fileLabels[i]) || undefined;
if (typeof file === 'string') {
result.files.push({ label, fileUri: URI.parse(file) });
}
});
} else if (Array.isArray(storedRecents.files)) {
restoreGracefully(storedRecents.files, file => {
if (typeof file === 'string') {
result.files.push({ fileUri: URI.file(file) });
}
});
}
}
return result;
}
export function toStoreData(recents: IRecentlyOpened): RecentlyOpenedStorageData {
const serialized: ISerializedRecentlyOpened = { workspaces3: [], files2: [] };
let hasLabel = false;
const workspaceLabels: (string | null)[] = [];
for (const recent of recents.workspaces) {
if (isRecentFolder(recent)) {
serialized.workspaces3.push(recent.folderUri.toString());
} else {
serialized.workspaces3.push({ id: recent.workspace.id, configURIPath: recent.workspace.configPath.toString() });
}
workspaceLabels.push(recent.label || null);
hasLabel = hasLabel || !!recent.label;
}
if (hasLabel) {
serialized.workspaceLabels = workspaceLabels;
}
hasLabel = false;
const fileLabels: (string | null)[] = [];
for (const recent of recents.files) {
serialized.files2.push(recent.fileUri.toString());
fileLabels.push(recent.label || null);
hasLabel = hasLabel || !!recent.label;
}
if (hasLabel) {
serialized.fileLabels = fileLabels;
}
return serialized;
}
//#endregion

View File

@@ -1,41 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';
export interface IRecentlyOpened {
workspaces: Array<IRecentWorkspace | IRecentFolder>;
files: IRecentFile[];
}
export type IRecent = IRecentWorkspace | IRecentFolder | IRecentFile;
export interface IRecentWorkspace {
workspace: IWorkspaceIdentifier;
label?: string;
}
export interface IRecentFolder {
folderUri: ISingleFolderWorkspaceIdentifier;
label?: string;
}
export interface IRecentFile {
fileUri: URI;
label?: string;
}
export function isRecentWorkspace(curr: IRecent): curr is IRecentWorkspace {
return curr.hasOwnProperty('workspace');
}
export function isRecentFolder(curr: IRecent): curr is IRecentFolder {
return curr.hasOwnProperty('folderUri');
}
export function isRecentFile(curr: IRecent): curr is IRecentFile {
return curr.hasOwnProperty('fileUri');
}

View File

@@ -1,129 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { UriComponents, URI } from 'vs/base/common/uri';
import { IRecentlyOpened, isRecentFolder } from 'vs/platform/workspaces/common/workspacesHistory';
import { ILogService } from 'vs/platform/log/common/log';
interface ISerializedRecentlyOpened {
workspaces3: Array<ISerializedWorkspace | string>; // workspace or URI.toString() // added in 1.32
workspaceLabels?: Array<string | null>; // added in 1.33
files2: string[]; // files as URI.toString() // added in 1.32
fileLabels?: Array<string | null>; // added in 1.33
}
interface ILegacySerializedRecentlyOpened {
workspaces2: Array<ILegacySerializedWorkspace | string>; // legacy, configPath as file path
workspaces: Array<ILegacySerializedWorkspace | string | UriComponents>; // legacy (UriComponents was also supported for a few insider builds)
files: string[]; // files as paths
}
interface ISerializedWorkspace { id: string; configURIPath: string; }
interface ILegacySerializedWorkspace { id: string; configPath: string; }
function isLegacySerializedWorkspace(curr: any): curr is ILegacySerializedWorkspace {
return typeof curr === 'object' && typeof curr['id'] === 'string' && typeof curr['configPath'] === 'string';
}
function isUriComponents(curr: any): curr is UriComponents {
return curr && typeof curr['path'] === 'string' && typeof curr['scheme'] === 'string';
}
export type RecentlyOpenedStorageData = object;
export function restoreRecentlyOpened(data: RecentlyOpenedStorageData | undefined, logService: ILogService): IRecentlyOpened {
const result: IRecentlyOpened = { workspaces: [], files: [] };
if (data) {
const restoreGracefully = function <T>(entries: T[], func: (entry: T, index: number) => void) {
for (let i = 0; i < entries.length; i++) {
try {
func(entries[i], i);
} catch (e) {
logService.warn(`Error restoring recent entry ${JSON.stringify(entries[i])}: ${e.toString()}. Skip entry.`);
}
}
};
const storedRecents = data as ISerializedRecentlyOpened & ILegacySerializedRecentlyOpened;
if (Array.isArray(storedRecents.workspaces3)) {
restoreGracefully(storedRecents.workspaces3, (workspace, i) => {
const label: string | undefined = (Array.isArray(storedRecents.workspaceLabels) && storedRecents.workspaceLabels[i]) || undefined;
if (typeof workspace === 'object' && typeof workspace.id === 'string' && typeof workspace.configURIPath === 'string') {
result.workspaces.push({ label, workspace: { id: workspace.id, configPath: URI.parse(workspace.configURIPath) } });
} else if (typeof workspace === 'string') {
result.workspaces.push({ label, folderUri: URI.parse(workspace) });
}
});
} else if (Array.isArray(storedRecents.workspaces2)) {
restoreGracefully(storedRecents.workspaces2, workspace => {
if (typeof workspace === 'object' && typeof workspace.id === 'string' && typeof workspace.configPath === 'string') {
result.workspaces.push({ workspace: { id: workspace.id, configPath: URI.file(workspace.configPath) } });
} else if (typeof workspace === 'string') {
result.workspaces.push({ folderUri: URI.parse(workspace) });
}
});
} else if (Array.isArray(storedRecents.workspaces)) {
// TODO@martin legacy support can be removed at some point (6 month?)
// format of 1.25 and before
restoreGracefully(storedRecents.workspaces, workspace => {
if (typeof workspace === 'string') {
result.workspaces.push({ folderUri: URI.file(workspace) });
} else if (isLegacySerializedWorkspace(workspace)) {
result.workspaces.push({ workspace: { id: workspace.id, configPath: URI.file(workspace.configPath) } });
} else if (isUriComponents(workspace)) {
// added by 1.26-insiders
result.workspaces.push({ folderUri: URI.revive(<UriComponents>workspace) });
}
});
}
if (Array.isArray(storedRecents.files2)) {
restoreGracefully(storedRecents.files2, (file, i) => {
const label: string | undefined = (Array.isArray(storedRecents.fileLabels) && storedRecents.fileLabels[i]) || undefined;
if (typeof file === 'string') {
result.files.push({ label, fileUri: URI.parse(file) });
}
});
} else if (Array.isArray(storedRecents.files)) {
restoreGracefully(storedRecents.files, file => {
if (typeof file === 'string') {
result.files.push({ fileUri: URI.file(file) });
}
});
}
}
return result;
}
export function toStoreData(recents: IRecentlyOpened): RecentlyOpenedStorageData {
const serialized: ISerializedRecentlyOpened = { workspaces3: [], files2: [] };
let hasLabel = false;
const workspaceLabels: (string | null)[] = [];
for (const recent of recents.workspaces) {
if (isRecentFolder(recent)) {
serialized.workspaces3.push(recent.folderUri.toString());
} else {
serialized.workspaces3.push({ id: recent.workspace.id, configURIPath: recent.workspace.configPath.toString() });
}
workspaceLabels.push(recent.label || null);
hasLabel = hasLabel || !!recent.label;
}
if (hasLabel) {
serialized.workspaceLabels = workspaceLabels;
}
hasLabel = false;
const fileLabels: (string | null)[] = [];
for (const recent of recents.files) {
serialized.files2.push(recent.fileUri.toString());
fileLabels.push(recent.label || null);
hasLabel = hasLabel || !!recent.label;
}
if (hasLabel) {
serialized.fileLabels = fileLabels;
}
return serialized;
}

View File

@@ -12,16 +12,14 @@ import { getBaseLabel, getPathLabel } from 'vs/base/common/labels';
import { IPath } from 'vs/platform/windows/common/windows';
import { Event as CommonEvent, Emitter } from 'vs/base/common/event';
import { isWindows, isMacintosh } from 'vs/base/common/platform';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile, IRecentFolder, IRecentWorkspace, IRecentFile, toStoreData, restoreRecentlyOpened, RecentlyOpenedStorageData } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile, IRecentFolder, IRecentWorkspace, IRecentFile } from 'vs/platform/workspaces/common/workspacesHistory';
import { ThrottledDelayer } from 'vs/base/common/async';
import { isEqual as areResourcesEqual, dirname, originalFSPath, basename } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { getSimpleWorkspaceLabel } from 'vs/platform/label/common/label';
import { toStoreData, restoreRecentlyOpened, RecentlyOpenedStorageData } from 'vs/platform/workspaces/common/workspacesHistoryStorage';
import { exists } from 'vs/base/node/pfs';
import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
@@ -32,7 +30,7 @@ export interface IWorkspacesHistoryMainService {
_serviceBrand: undefined;
onRecentlyOpenedChange: CommonEvent<void>;
readonly onRecentlyOpenedChange: CommonEvent<void>;
addRecentlyOpened(recents: IRecent[]): void;
getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier, currentFolder?: ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened;

View File

@@ -1,58 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IWorkspaceIdentifier, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
export class WorkspacesChannel implements IServerChannel {
constructor(
private workspacesMainService: IWorkspacesMainService,
private windowsMainService: IWindowsMainService
) { }
listen<T>(_: unknown, event: string): Event<T> {
throw new Error(`Event not found: ${event}`);
}
call(_: unknown, command: string, arg?: any): Promise<any> {
switch (command) {
case 'createUntitledWorkspace': {
const rawFolders: IWorkspaceFolderCreationData[] = arg[0];
const remoteAuthority: string = arg[1];
let folders: IWorkspaceFolderCreationData[] | undefined = undefined;
if (Array.isArray(rawFolders)) {
folders = rawFolders.map(rawFolder => {
return {
uri: URI.revive(rawFolder.uri), // convert raw URI back to real URI
name: rawFolder.name
};
});
}
return this.workspacesMainService.createUntitledWorkspace(folders, remoteAuthority);
}
case 'deleteUntitledWorkspace': {
const identifier: IWorkspaceIdentifier = arg;
return this.workspacesMainService.deleteUntitledWorkspace({ id: identifier.id, configPath: URI.revive(identifier.configPath) });
}
case 'getWorkspaceIdentifier': {
return this.workspacesMainService.getWorkspaceIdentifier(URI.revive(arg));
}
case 'enterWorkspace': {
const window = this.windowsMainService.getWindowById(arg[0]);
if (window) {
return this.windowsMainService.enterWorkspace(window, URI.revive(arg[1]));
}
}
}
throw new Error(`Call not found: ${command}`);
}
}

View File

@@ -20,18 +20,13 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { originalFSPath, isEqualOrParent, joinPath } from 'vs/base/common/resources';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export interface IStoredWorkspace {
folders: IStoredWorkspaceFolder[];
remoteAuthority?: string;
}
export const IWorkspacesMainService = createDecorator<IWorkspacesMainService>('workspacesMainService');
export interface IWorkspacesMainService {
_serviceBrand: undefined;
onUntitledWorkspaceDeleted: Event<IWorkspaceIdentifier>;
readonly onUntitledWorkspaceDeleted: Event<IWorkspaceIdentifier>;
createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[]): IWorkspaceIdentifier;
@@ -50,6 +45,11 @@ export interface IWorkspacesMainService {
getWorkspaceIdentifier(workspacePath: URI): Promise<IWorkspaceIdentifier>;
}
export interface IStoredWorkspace {
folders: IStoredWorkspaceFolder[];
remoteAuthority?: string;
}
export class WorkspacesMainService extends Disposable implements IWorkspacesMainService {
_serviceBrand: undefined;

View File

@@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AddFirstParameterToFunctions } from 'vs/base/common/types';
import { IWorkspacesService, IEnterWorkspaceResult, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IRecentlyOpened, IRecent } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';
import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
export class WorkspacesService implements AddFirstParameterToFunctions<IWorkspacesService, Promise<any> /* only methods, not events */, number /* window ID */> {
_serviceBrand: undefined;
constructor(
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
@IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService
) {
}
//#region Workspace Management
async enterWorkspace(windowId: number, path: URI): Promise<IEnterWorkspaceResult | undefined> {
const window = this.windowsMainService.getWindowById(windowId);
if (window) {
return this.windowsMainService.enterWorkspace(window, path);
}
return undefined;
}
createUntitledWorkspace(windowId: number, folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier> {
return this.workspacesMainService.createUntitledWorkspace(folders, remoteAuthority);
}
deleteUntitledWorkspace(windowId: number, workspace: IWorkspaceIdentifier): Promise<void> {
return this.workspacesMainService.deleteUntitledWorkspace(workspace);
}
getWorkspaceIdentifier(windowId: number, workspacePath: URI): Promise<IWorkspaceIdentifier> {
return this.workspacesMainService.getWorkspaceIdentifier(workspacePath);
}
//#endregion
//#region Workspaces History
readonly onRecentlyOpenedChange = this.workspacesHistoryMainService.onRecentlyOpenedChange;
async getRecentlyOpened(windowId: number): Promise<IRecentlyOpened> {
const window = this.windowsMainService.getWindowById(windowId);
if (window) {
return this.workspacesHistoryMainService.getRecentlyOpened(window.config.workspace, window.config.folderUri, window.config.filesToOpenOrCreate);
}
return this.workspacesHistoryMainService.getRecentlyOpened();
}
async addRecentlyOpened(windowId: number, recents: IRecent[]): Promise<void> {
return this.workspacesHistoryMainService.addRecentlyOpened(recents);
}
async removeFromRecentlyOpened(windowId: number, paths: URI[]): Promise<void> {
return this.workspacesHistoryMainService.removeFromRecentlyOpened(paths);
}
async clearRecentlyOpened(windowId: number): Promise<void> {
return this.workspacesHistoryMainService.clearRecentlyOpened();
}
//#endregion
}

View File

@@ -2,14 +2,12 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as os from 'os';
import * as path from 'vs/base/common/path';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceIdentifier, IRecentlyOpened, isRecentFolder, IRecentFolder, IRecentWorkspace, toStoreData, restoreRecentlyOpened } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';
import { IRecentlyOpened, isRecentFolder, IRecentFolder, IRecentWorkspace } from 'vs/platform/workspaces/common/workspacesHistory';
import { toStoreData, restoreRecentlyOpened } from 'vs/platform/workspaces/common/workspacesHistoryStorage';
import { NullLogService } from 'vs/platform/log/common/log';
function toWorkspace(uri: URI): IWorkspaceIdentifier {