Merge from vscode 817eb6b0c720a4ecbc13c020afbbebfed667aa09 (#7356)

This commit is contained in:
Anthony Dresser
2019-09-24 21:36:17 -07:00
committed by GitHub
parent a29ae4d3b9
commit 6a6048d40f
541 changed files with 7045 additions and 7287 deletions

View File

@@ -7,9 +7,6 @@ import { IConfigurationCache, ConfigurationKey } from 'vs/workbench/services/con
export class ConfigurationCache implements IConfigurationCache {
constructor() {
}
async read(key: ConfigurationKey): Promise<string> {
return '';
}
@@ -19,4 +16,4 @@ export class ConfigurationCache implements IConfigurationCache {
async remove(key: ConfigurationKey): Promise<void> {
}
}
}

View File

@@ -507,17 +507,17 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
private registerConfigurationSchemas(): void {
if (this.workspace) {
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
const allSettingsSchema: IJSONSchema = { properties: allSettings.properties, patternProperties: allSettings.patternProperties, additionalProperties: true, allowsTrailingCommas: true, allowComments: true };
const userSettingsSchema: IJSONSchema = this.remoteUserConfiguration ? { properties: { ...applicationSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowsTrailingCommas: true, allowComments: true } : allSettingsSchema;
const machineSettingsSchema: IJSONSchema = { properties: { ...machineSettings.properties, ...machineOverridableSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowsTrailingCommas: true, allowComments: true };
const workspaceSettingsSchema: IJSONSchema = { properties: { ...machineOverridableSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowsTrailingCommas: true, allowComments: true };
const allSettingsSchema: IJSONSchema = { properties: allSettings.properties, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true };
const userSettingsSchema: IJSONSchema = this.remoteUserConfiguration ? { properties: { ...applicationSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true } : allSettingsSchema;
const machineSettingsSchema: IJSONSchema = { properties: { ...machineSettings.properties, ...machineOverridableSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true };
const workspaceSettingsSchema: IJSONSchema = { properties: { ...machineOverridableSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true };
jsonRegistry.registerSchema(defaultSettingsSchemaId, allSettingsSchema);
jsonRegistry.registerSchema(userSettingsSchemaId, userSettingsSchema);
jsonRegistry.registerSchema(machineSettingsSchemaId, machineSettingsSchema);
if (WorkbenchState.WORKSPACE === this.getWorkbenchState()) {
const folderSettingsSchema: IJSONSchema = { properties: { ...machineOverridableSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowsTrailingCommas: true, allowComments: true };
const folderSettingsSchema: IJSONSchema = { properties: { ...machineOverridableSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true };
jsonRegistry.registerSchema(workspaceSettingsSchemaId, workspaceSettingsSchema);
jsonRegistry.registerSchema(folderSettingsSchemaId, folderSettingsSchema);
} else {

View File

@@ -0,0 +1,194 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { IWindowService, IURIToOpen, FileFilter } from 'vs/platform/windows/common/windows';
import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import * as resources from 'vs/base/common/resources';
import { IInstantiationService, } from 'vs/platform/instantiation/common/instantiation';
import { SimpleFileDialog } from 'vs/workbench/services/dialogs/browser/simpleFileDialog';
import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFileService } from 'vs/platform/files/common/files';
import { isWeb } from 'vs/base/common/platform';
import { IOpenerService } from 'vs/platform/opener/common/opener';
export class AbstractFileDialogService {
_serviceBrand: undefined;
constructor(
@IWindowService protected readonly windowService: IWindowService,
@IWorkspaceContextService protected readonly contextService: IWorkspaceContextService,
@IHistoryService protected readonly historyService: IHistoryService,
@IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService,
@IInstantiationService protected readonly instantiationService: IInstantiationService,
@IConfigurationService protected readonly configurationService: IConfigurationService,
@IFileService protected readonly fileService: IFileService,
@IOpenerService protected readonly openerService: IOpenerService,
) { }
defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined {
// Check for last active file first...
let candidate = this.historyService.getLastActiveFile(schemeFilter);
// ...then for last active file root
if (!candidate) {
candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter);
} else {
candidate = candidate && resources.dirname(candidate);
}
return candidate || undefined;
}
defaultFolderPath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined {
// Check for last active file root first...
let candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter);
// ...then for last active file
if (!candidate) {
candidate = this.historyService.getLastActiveFile(schemeFilter);
}
return candidate && resources.dirname(candidate) || undefined;
}
defaultWorkspacePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined {
// Check for current workspace config file first...
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
const configuration = this.contextService.getWorkspace().configuration;
if (configuration && !isUntitledWorkspace(configuration, this.environmentService)) {
return resources.dirname(configuration) || undefined;
}
}
// ...then fallback to default file path
return this.defaultFilePath(schemeFilter);
}
protected addFileSchemaIfNeeded(schema: string): string[] {
// Include File schema unless the schema is web
// Don't allow untitled schema through.
if (isWeb) {
return schema === Schemas.untitled ? [Schemas.file] : [schema];
} else {
return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]);
}
}
protected async pickFileFolderAndOpenSimplified(schema: string, options: IPickAndOpenOptions, preferNewWindow: boolean): Promise<any> {
const title = nls.localize('openFileOrFolder.title', 'Open File Or Folder');
const availableFileSystems = this.addFileSchemaIfNeeded(schema);
const uri = await this.pickResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
if (uri) {
const stat = await this.fileService.resolve(uri);
const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri };
if (stat.isDirectory || options.forceNewWindow || preferNewWindow) {
return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow });
} else {
return this.openerService.open(uri);
}
}
}
protected async pickFileAndOpenSimplified(schema: string, options: IPickAndOpenOptions, preferNewWindow: boolean): Promise<any> {
const title = nls.localize('openFile.title', 'Open File');
const availableFileSystems = this.addFileSchemaIfNeeded(schema);
const uri = await this.pickResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
if (uri) {
if (options.forceNewWindow || preferNewWindow) {
return this.windowService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow });
} else {
return this.openerService.open(uri);
}
}
}
protected async pickFolderAndOpenSimplified(schema: string, options: IPickAndOpenOptions): Promise<any> {
const title = nls.localize('openFolder.title', 'Open Folder');
const availableFileSystems = this.addFileSchemaIfNeeded(schema);
const uri = await this.pickResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
if (uri) {
return this.windowService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow });
}
}
protected async pickWorkspaceAndOpenSimplified(schema: string, options: IPickAndOpenOptions): Promise<any> {
const title = nls.localize('openWorkspace.title', 'Open Workspace');
const filters: FileFilter[] = [{ name: nls.localize('filterName.workspace', 'Workspace'), extensions: [WORKSPACE_EXTENSION] }];
const availableFileSystems = this.addFileSchemaIfNeeded(schema);
const uri = await this.pickResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems });
if (uri) {
return this.windowService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow });
}
}
protected async pickFileToSaveSimplified(schema: string, options: ISaveDialogOptions): Promise<URI | undefined> {
if (!options.availableFileSystems) {
options.availableFileSystems = this.addFileSchemaIfNeeded(schema);
}
options.title = nls.localize('saveFileAs.title', 'Save As');
return this.saveRemoteResource(options);
}
protected async showSaveDialogSimplified(schema: string, options: ISaveDialogOptions): Promise<URI | undefined> {
if (!options.availableFileSystems) {
options.availableFileSystems = this.addFileSchemaIfNeeded(schema);
}
return this.saveRemoteResource(options);
}
protected async showOpenDialogSimplified(schema: string, options: IOpenDialogOptions): Promise<URI[] | undefined> {
if (!options.availableFileSystems) {
options.availableFileSystems = this.addFileSchemaIfNeeded(schema);
}
const uri = await this.pickResource(options);
return uri ? [uri] : undefined;
}
private pickResource(options: IOpenDialogOptions): Promise<URI | undefined> {
const simpleFileDialog = this.instantiationService.createInstance(SimpleFileDialog);
return simpleFileDialog.showOpenDialog(options);
}
private saveRemoteResource(options: ISaveDialogOptions): Promise<URI | undefined> {
const remoteFileDialog = this.instantiationService.createInstance(SimpleFileDialog);
return remoteFileDialog.showSaveDialog(options);
}
protected getSchemeFilterForWindow(): string {
return !this.environmentService.configuration.remoteAuthority ? Schemas.file : REMOTE_HOST_SCHEME;
}
protected getFileSystemSchema(options: { availableFileSystems?: string[], defaultUri?: URI }): string {
return options.availableFileSystems && options.availableFileSystems[0] || this.getSchemeFilterForWindow();
}
}
function isUntitledWorkspace(path: URI, environmentService: IWorkbenchEnvironmentService): boolean {
return resources.isEqualOrParent(path, environmentService.untitledWorkspacesHome);
}

View File

@@ -3,105 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { IWindowService, INativeOpenDialogOptions, OpenDialogOptions, IURIToOpen, FileFilter } from 'vs/platform/windows/common/windows';
import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import * as resources from 'vs/base/common/resources';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { RemoteFileDialog } from 'vs/workbench/services/dialogs/browser/remoteFileDialog';
import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { isWeb } from 'vs/base/common/platform';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { AbstractFileDialogService } from 'vs/workbench/services/dialogs/browser/abstractFileDialogService';
export class FileDialogService implements IFileDialogService {
_serviceBrand: undefined;
constructor(
@IWindowService private readonly windowService: IWindowService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IHistoryService private readonly historyService: IHistoryService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IFileService private readonly fileService: IFileService,
@IOpenerService private readonly openerService: IOpenerService
) { }
defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined {
// Check for last active file first...
let candidate = this.historyService.getLastActiveFile(schemeFilter);
// ...then for last active file root
if (!candidate) {
candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter);
} else {
candidate = candidate && resources.dirname(candidate);
}
return candidate || undefined;
}
defaultFolderPath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined {
// Check for last active file root first...
let candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter);
// ...then for last active file
if (!candidate) {
candidate = this.historyService.getLastActiveFile(schemeFilter);
}
return candidate && resources.dirname(candidate) || undefined;
}
defaultWorkspacePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined {
// Check for current workspace config file first...
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
const configuration = this.contextService.getWorkspace().configuration;
if (configuration && !isUntitledWorkspace(configuration, this.environmentService)) {
return resources.dirname(configuration) || undefined;
}
}
// ...then fallback to default file path
return this.defaultFilePath(schemeFilter);
}
private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions {
return {
forceNewWindow: options.forceNewWindow,
telemetryExtraData: options.telemetryExtraData,
defaultPath: options.defaultUri && options.defaultUri.fsPath
};
}
private shouldUseSimplified(schema: string): { useSimplified: boolean, isSetting: boolean } {
const setting = (this.configurationService.getValue('files.simpleDialog.enable') === true);
return { useSimplified: (schema !== Schemas.file) || setting, isSetting: (schema === Schemas.file) && setting };
}
private addFileSchemaIfNeeded(schema: string): string[] {
// Include File schema unless the schema is web
// Don't allow untitled schema through.
if (isWeb) {
return schema === Schemas.untitled ? [Schemas.file] : [schema];
} else {
return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]);
}
}
export class FileDialogService extends AbstractFileDialogService implements IFileDialogService {
async pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
const schema = this.getFileSystemSchema(options);
@@ -110,28 +17,7 @@ export class FileDialogService implements IFileDialogService {
options.defaultUri = this.defaultFilePath(schema);
}
const shouldUseSimplified = this.shouldUseSimplified(schema);
if (shouldUseSimplified.useSimplified) {
const title = nls.localize('openFileOrFolder.title', 'Open File Or Folder');
const availableFileSystems = this.addFileSchemaIfNeeded(schema);
const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
if (uri) {
const stat = await this.fileService.resolve(uri);
const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri };
if (stat.isDirectory || options.forceNewWindow || shouldUseSimplified.isSetting) {
return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow });
} else {
return this.openerService.open(uri);
}
}
return;
}
return this.windowService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options));
return this.pickFileFolderAndOpenSimplified(schema, options, false);
}
async pickFileAndOpen(options: IPickAndOpenOptions): Promise<any> {
@@ -141,24 +27,7 @@ export class FileDialogService implements IFileDialogService {
options.defaultUri = this.defaultFilePath(schema);
}
const shouldUseSimplified = this.shouldUseSimplified(schema);
if (shouldUseSimplified.useSimplified) {
const title = nls.localize('openFile.title', 'Open File');
const availableFileSystems = this.addFileSchemaIfNeeded(schema);
const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
if (uri) {
if (options.forceNewWindow || shouldUseSimplified.isSetting) {
return this.windowService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow });
} else {
return this.openerService.open(uri);
}
}
return;
}
return this.windowService.pickFileAndOpen(this.toNativeOpenDialogOptions(options));
return this.pickFileAndOpenSimplified(schema, options, false);
}
async pickFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
@@ -168,19 +37,7 @@ export class FileDialogService implements IFileDialogService {
options.defaultUri = this.defaultFolderPath(schema);
}
if (this.shouldUseSimplified(schema).useSimplified) {
const title = nls.localize('openFolder.title', 'Open Folder');
const availableFileSystems = this.addFileSchemaIfNeeded(schema);
const uri = await this.pickRemoteResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
if (uri) {
return this.windowService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow });
}
return;
}
return this.windowService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options));
return this.pickFolderAndOpenSimplified(schema, options);
}
async pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise<void> {
@@ -190,133 +47,23 @@ export class FileDialogService implements IFileDialogService {
options.defaultUri = this.defaultWorkspacePath(schema);
}
if (this.shouldUseSimplified(schema).useSimplified) {
const title = nls.localize('openWorkspace.title', 'Open Workspace');
const filters: FileFilter[] = [{ name: nls.localize('filterName.workspace', 'Workspace'), extensions: [WORKSPACE_EXTENSION] }];
const availableFileSystems = this.addFileSchemaIfNeeded(schema);
const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems });
if (uri) {
return this.windowService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow });
}
return;
}
return this.windowService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options));
return this.pickWorkspaceAndOpenSimplified(schema, options);
}
async pickFileToSave(options: ISaveDialogOptions): Promise<URI | undefined> {
const schema = this.getFileSystemSchema(options);
if (this.shouldUseSimplified(schema).useSimplified) {
if (!options.availableFileSystems) {
options.availableFileSystems = this.addFileSchemaIfNeeded(schema);
}
options.title = nls.localize('saveFileAs.title', 'Save As');
return this.saveRemoteResource(options);
}
const result = await this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options));
if (result) {
return URI.file(result);
}
return undefined; // {{SQL CARBON EDIT}} strict-null-check
}
private toNativeSaveDialogOptions(options: ISaveDialogOptions): Electron.SaveDialogOptions {
options.defaultUri = options.defaultUri ? URI.file(options.defaultUri.path) : undefined;
return {
defaultPath: options.defaultUri && options.defaultUri.fsPath,
buttonLabel: options.saveLabel,
filters: options.filters,
title: options.title
};
return this.pickFileToSaveSimplified(schema, options);
}
async showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined> {
const schema = this.getFileSystemSchema(options);
if (this.shouldUseSimplified(schema).useSimplified) {
if (!options.availableFileSystems) {
options.availableFileSystems = this.addFileSchemaIfNeeded(schema);
}
return this.saveRemoteResource(options);
}
const result = await this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options));
if (result) {
return URI.file(result);
}
return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check
return this.showSaveDialogSimplified(schema, options);
}
async showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined> {
const schema = this.getFileSystemSchema(options);
if (this.shouldUseSimplified(schema).useSimplified) {
if (!options.availableFileSystems) {
options.availableFileSystems = this.addFileSchemaIfNeeded(schema);
}
const uri = await this.pickRemoteResource(options);
return uri ? [uri] : undefined;
}
const defaultUri = options.defaultUri;
const newOptions: OpenDialogOptions = {
title: options.title,
defaultPath: defaultUri && defaultUri.fsPath,
buttonLabel: options.openLabel,
filters: options.filters,
properties: []
};
newOptions.properties!.push('createDirectory');
if (options.canSelectFiles) {
newOptions.properties!.push('openFile');
}
if (options.canSelectFolders) {
newOptions.properties!.push('openDirectory');
}
if (options.canSelectMany) {
newOptions.properties!.push('multiSelections');
}
const result = await this.windowService.showOpenDialog(newOptions);
return result ? result.map(URI.file) : undefined;
return this.showOpenDialogSimplified(schema, options);
}
private pickRemoteResource(options: IOpenDialogOptions): Promise<URI | undefined> {
const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog);
return remoteFileDialog.showOpenDialog(options);
}
private saveRemoteResource(options: ISaveDialogOptions): Promise<URI | undefined> {
const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog);
return remoteFileDialog.showSaveDialog(options);
}
private getSchemeFilterForWindow(): string {
return !this.environmentService.configuration.remoteAuthority ? Schemas.file : REMOTE_HOST_SCHEME;
}
private getFileSystemSchema(options: { availableFileSystems?: string[], defaultUri?: URI }): string {
return options.availableFileSystems && options.availableFileSystems[0] || this.getSchemeFilterForWindow();
}
}
function isUntitledWorkspace(path: URI, environmentService: IWorkbenchEnvironmentService): boolean {
return resources.isEqualOrParent(path, environmentService.untitledWorkspacesHome);
}
registerSingleton(IFileDialogService, FileDialogService, true);

View File

@@ -98,7 +98,7 @@ enum UpdateResult {
InvalidPath
}
export class RemoteFileDialog {
export class SimpleFileDialog {
private options!: IOpenDialogOptions;
private currentFolder!: URI;
private filePickBox!: IQuickPick<FileQuickPickItem>;
@@ -117,7 +117,7 @@ export class RemoteFileDialog {
private badPath: string | undefined;
private remoteAgentEnvironment: IRemoteAgentEnvironment | null | undefined;
private separator: string = '/';
private onBusyChangeEmitter = new Emitter<boolean>();
private readonly onBusyChangeEmitter = new Emitter<boolean>();
private updatingPromise: CancelablePromise<void> | undefined;
protected disposables: IDisposable[] = [
@@ -303,7 +303,7 @@ export class RemoteFileDialog {
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
this.filePickBox.items = [];
function doResolve(dialog: RemoteFileDialog, uri: URI | undefined) {
function doResolve(dialog: SimpleFileDialog, uri: URI | undefined) {
if (uri) {
uri = resources.removeTrailingPathSeparator(uri);
}
@@ -335,7 +335,7 @@ export class RemoteFileDialog {
}
});
function handleAccept(dialog: RemoteFileDialog) {
function handleAccept(dialog: SimpleFileDialog) {
if (dialog.busy) {
// Save the accept until the file picker is not busy.
dialog.onBusyChangeEmitter.event((busy: boolean) => {

View File

@@ -8,7 +8,6 @@ import * as os from 'os';
import product from 'vs/platform/product/common/product';
import Severity from 'vs/base/common/severity';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { IDialogService, IConfirmation, IConfirmationResult, IDialogOptions, IShowResult } from 'vs/platform/dialogs/common/dialogs';
import { DialogService as HTMLDialogService } from 'vs/workbench/services/dialogs/browser/dialogService';
@@ -23,13 +22,14 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IProductService } from 'vs/platform/product/common/productService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IElectronService } from 'vs/platform/electron/node/electron';
import { MessageBoxOptions } from 'electron';
interface IMassagedMessageBoxOptions {
/**
* OS massaged message box options.
*/
options: Electron.MessageBoxOptions;
options: MessageBoxOptions;
/**
* Since the massaged result of the message box options potentially
@@ -50,7 +50,6 @@ export class DialogService implements IDialogService {
@ILogService logService: ILogService,
@ILayoutService layoutService: ILayoutService,
@IThemeService themeService: IThemeService,
@IWindowService windowService: IWindowService,
@ISharedProcessService sharedProcessService: ISharedProcessService,
@IKeybindingService keybindingService: IKeybindingService,
@IProductService productService: IProductService,
@@ -64,7 +63,7 @@ export class DialogService implements IDialogService {
}
// Electron dialog service
else {
this.impl = new NativeDialogService(windowService, logService, sharedProcessService, electronService, clipboardService);
this.impl = new NativeDialogService(logService, sharedProcessService, electronService, clipboardService);
}
}
@@ -86,7 +85,6 @@ class NativeDialogService implements IDialogService {
_serviceBrand: undefined;
constructor(
@IWindowService private readonly windowService: IWindowService,
@ILogService private readonly logService: ILogService,
@ISharedProcessService sharedProcessService: ISharedProcessService,
@IElectronService private readonly electronService: IElectronService,
@@ -100,14 +98,14 @@ class NativeDialogService implements IDialogService {
const { options, buttonIndexMap } = this.massageMessageBoxOptions(this.getConfirmOptions(confirmation));
const result = await this.windowService.showMessageBox(options);
const result = await this.electronService.showMessageBox(options);
return {
confirmed: buttonIndexMap[result.button] === 0 ? true : false,
confirmed: buttonIndexMap[result.response] === 0 ? true : false,
checkboxChecked: result.checkboxChecked
};
}
private getConfirmOptions(confirmation: IConfirmation): Electron.MessageBoxOptions {
private getConfirmOptions(confirmation: IConfirmation): MessageBoxOptions {
const buttons: string[] = [];
if (confirmation.primaryButton) {
buttons.push(confirmation.primaryButton);
@@ -121,7 +119,7 @@ class NativeDialogService implements IDialogService {
buttons.push(nls.localize('cancelButton', "Cancel"));
}
const opts: Electron.MessageBoxOptions = {
const opts: MessageBoxOptions = {
title: confirmation.title,
message: confirmation.message,
buttons,
@@ -157,11 +155,11 @@ class NativeDialogService implements IDialogService {
checkboxChecked: dialogOptions && dialogOptions.checkbox ? dialogOptions.checkbox.checked : undefined
});
const result = await this.windowService.showMessageBox(options);
return { choice: buttonIndexMap[result.button], checkboxChecked: result.checkboxChecked };
const result = await this.electronService.showMessageBox(options);
return { choice: buttonIndexMap[result.response], checkboxChecked: result.checkboxChecked };
}
private massageMessageBoxOptions(options: Electron.MessageBoxOptions): IMassagedMessageBoxOptions {
private massageMessageBoxOptions(options: MessageBoxOptions): IMassagedMessageBoxOptions {
let buttonIndexMap = (options.buttons || []).map((button, index) => index);
let buttons = (options.buttons || []).map(button => mnemonicButtonLabel(button));
let cancelId = options.cancelId;

View File

@@ -0,0 +1,177 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWindowService, OpenDialogOptions, SaveDialogOptions, INativeOpenDialogOptions } from 'vs/platform/windows/common/windows';
import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IElectronService } from 'vs/platform/electron/node/electron';
import { AbstractFileDialogService } from 'vs/workbench/services/dialogs/browser/abstractFileDialogService';
import { Schemas } from 'vs/base/common/network';
export class FileDialogService extends AbstractFileDialogService implements IFileDialogService {
_serviceBrand: undefined;
constructor(
@IWindowService windowService: IWindowService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IHistoryService historyService: IHistoryService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService,
@IFileService fileService: IFileService,
@IOpenerService openerService: IOpenerService,
@IElectronService private readonly electronService: IElectronService
) { super(windowService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService); }
private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions {
return {
forceNewWindow: options.forceNewWindow,
telemetryExtraData: options.telemetryExtraData,
defaultPath: options.defaultUri && options.defaultUri.fsPath
};
}
private shouldUseSimplified(schema: string): { useSimplified: boolean, isSetting: boolean } {
const setting = (this.configurationService.getValue('files.simpleDialog.enable') === true);
return { useSimplified: (schema !== Schemas.file) || setting, isSetting: (schema === Schemas.file) && setting };
}
async pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
const schema = this.getFileSystemSchema(options);
if (!options.defaultUri) {
options.defaultUri = this.defaultFilePath(schema);
}
const shouldUseSimplified = this.shouldUseSimplified(schema);
if (shouldUseSimplified.useSimplified) {
return this.pickFileFolderAndOpenSimplified(schema, options, shouldUseSimplified.isSetting);
}
return this.electronService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options));
}
async pickFileAndOpen(options: IPickAndOpenOptions): Promise<any> {
const schema = this.getFileSystemSchema(options);
if (!options.defaultUri) {
options.defaultUri = this.defaultFilePath(schema);
}
const shouldUseSimplified = this.shouldUseSimplified(schema);
if (shouldUseSimplified.useSimplified) {
return this.pickFileAndOpenSimplified(schema, options, shouldUseSimplified.isSetting);
}
return this.electronService.pickFileAndOpen(this.toNativeOpenDialogOptions(options));
}
async pickFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
const schema = this.getFileSystemSchema(options);
if (!options.defaultUri) {
options.defaultUri = this.defaultFolderPath(schema);
}
if (this.shouldUseSimplified(schema).useSimplified) {
return this.pickFolderAndOpenSimplified(schema, options);
}
return this.electronService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options));
}
async pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise<void> {
const schema = this.getFileSystemSchema(options);
if (!options.defaultUri) {
options.defaultUri = this.defaultWorkspacePath(schema);
}
if (this.shouldUseSimplified(schema).useSimplified) {
return this.pickWorkspaceAndOpenSimplified(schema, options);
}
return this.electronService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options));
}
async pickFileToSave(options: ISaveDialogOptions): Promise<URI | undefined> {
const schema = this.getFileSystemSchema(options);
if (this.shouldUseSimplified(schema).useSimplified) {
return this.pickFileToSaveSimplified(schema, options);
} else {
const result = await this.electronService.showSaveDialog(this.toNativeSaveDialogOptions(options));
if (result && !result.canceled && result.filePath) {
return URI.file(result.filePath);
}
}
return undefined; // {{SQL CARBON EDIT}} strict-null-check
}
private toNativeSaveDialogOptions(options: ISaveDialogOptions): SaveDialogOptions {
options.defaultUri = options.defaultUri ? URI.file(options.defaultUri.path) : undefined;
return {
defaultPath: options.defaultUri && options.defaultUri.fsPath,
buttonLabel: options.saveLabel,
filters: options.filters,
title: options.title
};
}
async showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined> {
const schema = this.getFileSystemSchema(options);
if (this.shouldUseSimplified(schema).useSimplified) {
return this.showSaveDialogSimplified(schema, options);
}
const result = await this.electronService.showSaveDialog(this.toNativeSaveDialogOptions(options));
if (result && !result.canceled && result.filePath) {
return URI.file(result.filePath);
}
return undefined; // {{SQL CARBON EDIT}} strict-null-check
}
async showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined> {
const schema = this.getFileSystemSchema(options);
if (this.shouldUseSimplified(schema).useSimplified) {
return this.showOpenDialogSimplified(schema, options);
}
const defaultUri = options.defaultUri;
const newOptions: OpenDialogOptions = {
title: options.title,
defaultPath: defaultUri && defaultUri.fsPath,
buttonLabel: options.openLabel,
filters: options.filters,
properties: []
};
newOptions.properties!.push('createDirectory');
if (options.canSelectFiles) {
newOptions.properties!.push('openFile');
}
if (options.canSelectFolders) {
newOptions.properties!.push('openDirectory');
}
if (options.canSelectMany) {
newOptions.properties!.push('multiSelections');
}
const result = await this.electronService.showOpenDialog(newOptions);
return result && Array.isArray(result.filePaths) && result.filePaths.length > 0 ? result.filePaths.map(URI.file) : undefined;
}
}
registerSingleton(IFileDialogService, FileDialogService, true);

View File

@@ -22,7 +22,7 @@ export class CodeEditorService extends CodeEditorServiceImpl {
super(themeService);
}
getActiveCodeEditor(): ICodeEditor | null {
getActiveCodeEditor(): ICodeEditor | undefined {
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
if (isCodeEditor(activeTextEditorWidget)) {
return activeTextEditorWidget;
@@ -32,10 +32,10 @@ export class CodeEditorService extends CodeEditorServiceImpl {
return activeTextEditorWidget.getModifiedEditor();
}
return null;
return undefined;
}
openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
openCodeEditor(input: IResourceInput, source: ICodeEditor | undefined, sideBySide?: boolean): Promise<ICodeEditor | undefined> {
// Special case: If the active editor is a diff editor and the request to open originates and
// targets the modified side of it, we just apply the request there to prevent opening the modified
@@ -62,7 +62,7 @@ export class CodeEditorService extends CodeEditorServiceImpl {
return this.doOpenCodeEditor(input, source, sideBySide);
}
private async doOpenCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
private async doOpenCodeEditor(input: IResourceInput, _source: ICodeEditor | undefined, sideBySide?: boolean): Promise<ICodeEditor | undefined> {
const control = await this.editorService.openEditor(input, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
if (control) {
const widget = control.getControl();
@@ -71,8 +71,8 @@ export class CodeEditorService extends CodeEditorServiceImpl {
}
}
return null;
return undefined;
}
}
registerSingleton(ICodeEditorService, CodeEditorService, true);
registerSingleton(ICodeEditorService, CodeEditorService, true);

View File

@@ -61,8 +61,6 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite
class TestEditorInputFactory implements IEditorInputFactory {
constructor() { }
serialize(editorInput: EditorInput): string {
const testEditorInput = <TestEditorInput>editorInput;
const testInput: ISerializedTestEditorInput = {

View File

@@ -26,7 +26,7 @@ export class ExtensionEnablementService extends Disposable implements IExtension
_serviceBrand: undefined;
private _onEnablementChanged = new Emitter<IExtension[]>();
private readonly _onEnablementChanged = new Emitter<IExtension[]>();
public readonly onEnablementChanged: Event<IExtension[]> = this._onEnablementChanged.event;
private readonly storageManger: StorageManager;
@@ -148,8 +148,11 @@ export class ExtensionEnablementService extends Disposable implements IExtension
private _isDisabledByExtensionKind(extension: IExtension): boolean {
if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {
const server = isUIExtension(extension.manifest, this.productService, this.configurationService) ? this.extensionManagementServerService.localExtensionManagementServer : this.extensionManagementServerService.remoteExtensionManagementServer;
return this.extensionManagementServerService.getExtensionManagementServer(extension.location) !== server;
if (!isUIExtension(extension.manifest, this.productService, this.configurationService)) {
// workspace extensions must run on the remote, but UI extensions can run on either side
const server = this.extensionManagementServerService.remoteExtensionManagementServer;
return this.extensionManagementServerService.getExtensionManagementServer(extension.location) !== server;
}
}
return false;
}

View File

@@ -520,8 +520,8 @@ suite('ExtensionEnablementService Test', () => {
instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService));
const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
testObject = new TestExtensionEnablementService(instantiationService);
assert.ok(!testObject.isEnabled(localWorkspaceExtension));
assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind);
assert.ok(testObject.isEnabled(localWorkspaceExtension));
assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally);
});
test('test remote workspace extension is not disabled by kind', async () => {
@@ -536,7 +536,7 @@ suite('ExtensionEnablementService Test', () => {
instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService));
const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
testObject = new TestExtensionEnablementService(instantiationService);
assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), false);
assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), true);
});
test('test canChangeEnablement return true for remote workspace extension', () => {

View File

@@ -16,7 +16,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IURLHandler, IURLService } from 'vs/platform/url/common/url';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
@@ -65,7 +65,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
@INotificationService private readonly notificationService: INotificationService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService,
@IWindowService private readonly windowService: IWindowService,
@IHostService private readonly hostService: IHostService,
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
@IStorageService private readonly storageService: IStorageService,
@IConfigurationService private readonly configurationService: IConfigurationService
@@ -277,7 +277,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
private async reloadAndHandle(url: URI): Promise<void> {
this.storageService.store(URL_TO_HANDLE, JSON.stringify(url.toJSON()), StorageScope.WORKSPACE);
await this.windowService.reloadWindow();
await this.hostService.reload();
}
// forget about all uris buffered more than 5 minutes ago

View File

@@ -24,6 +24,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio
import { IFileService } from 'vs/platform/files/common/files';
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
import { IProductService } from 'vs/platform/product/common/productService';
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
const hasOwnProperty = Object.hasOwnProperty;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
@@ -290,17 +291,21 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
}
protected _isEnabled(extension: IExtensionDescription): boolean {
return !this._isDisabled(extension);
}
protected _isDisabled(extension: IExtensionDescription): boolean {
if (this._isExtensionUnderDevelopment(extension)) {
// Never disable extensions under development
return true;
return false;
}
if (ExtensionIdentifier.equals(extension.identifier, BetterMergeId)) {
// Check if this is the better merge extension which was migrated to a built-in extension
return false;
return true;
}
return this._extensionEnablementService.isEnabled(toExtension(extension));
return !this._extensionEnablementService.isEnabled(toExtension(extension));
}
protected _doHandleExtensionPoints(affectedExtensions: IExtensionDescription[]): void {
@@ -406,9 +411,9 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
}
}
public async _activateById(extensionId: ExtensionIdentifier, activationEvent: string): Promise<void> {
public async _activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> {
const results = await Promise.all(
this._extensionHostProcessManagers.map(manager => manager.activate(extensionId, activationEvent))
this._extensionHostProcessManagers.map(manager => manager.activate(extensionId, reason))
);
const activated = results.some(e => e);
if (!activated) {
@@ -420,8 +425,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
this._extensionHostActiveExtensions.set(ExtensionIdentifier.toKey(extensionId), extensionId);
}
public _onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void {
this._extensionHostProcessActivationTimes.set(ExtensionIdentifier.toKey(extensionId), new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent));
public _onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void {
this._extensionHostProcessActivationTimes.set(ExtensionIdentifier.toKey(extensionId), new ActivationTimes(codeLoadingTime, activateCallTime, activateResolvedTime, activationReason));
this._onDidChangeExtensionsStatus.fire([extensionId]);
}

View File

@@ -26,6 +26,7 @@ import { IUntitledResourceInput } from 'vs/workbench/common/editor';
import { StopWatch } from 'vs/base/common/stopwatch';
import { VSBuffer } from 'vs/base/common/buffer';
import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
// Enable to see detailed message communication between window and extension host
const LOG_EXTENSION_HOST_COMMUNICATION = false;
@@ -215,12 +216,12 @@ export class ExtensionHostProcessManager extends Disposable {
return this._extensionHostProcessRPCProtocol.getProxy(ExtHostContext.ExtHostExtensionService);
}
public async activate(extension: ExtensionIdentifier, activationEvent: string): Promise<boolean> {
public async activate(extension: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<boolean> {
const proxy = await this._getExtensionHostProcessProxy();
if (!proxy) {
return false;
}
return proxy.$activate(extension, activationEvent);
return proxy.$activate(extension, reason);
}
public activateByEvent(activationEvent: string): Promise<void> {

View File

@@ -15,6 +15,10 @@ export interface IExtHostSocketMessage {
skipWebSocketFrames: boolean;
}
export interface IExtHostReduceGraceTimeMessage {
type: 'VSCODE_EXTHOST_IPC_REDUCE_GRACE_TIME';
}
export const enum MessageType {
Initialized,
Ready,
@@ -44,4 +48,4 @@ export function isMessageOfType(message: VSBuffer, type: MessageType): boolean {
case 3: return type === MessageType.Terminate;
default: return false;
}
}
}

View File

@@ -11,6 +11,7 @@ import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensi
import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
export const nullExtensionDescription = Object.freeze(<IExtensionDescription>{
identifier: new ExtensionIdentifier('nullExtensionDescription'),
@@ -99,11 +100,10 @@ export type ProfileSegmentId = string | 'idle' | 'program' | 'gc' | 'self';
export class ActivationTimes {
constructor(
public readonly startup: boolean,
public readonly codeLoadingTime: number,
public readonly activateCallTime: number,
public readonly activateResolvedTime: number,
public readonly activationEvent: string
public readonly activationReason: ExtensionActivationReason
) {
}
}
@@ -226,9 +226,9 @@ export interface IExtensionService {
setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void>;
_logOrShowMessage(severity: Severity, msg: string): void;
_activateById(extensionId: ExtensionIdentifier, activationEvent: string): Promise<void>;
_activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void>;
_onWillActivateExtension(extensionId: ExtensionIdentifier): void;
_onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void;
_onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void;
_onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void;
_onExtensionHostExit(code: number): void;
}
@@ -276,9 +276,9 @@ export class NullExtensionService implements IExtensionService {
canAddExtension(): boolean { return false; }
canRemoveExtension(): boolean { return false; }
_logOrShowMessage(_severity: Severity, _msg: string): void { }
_activateById(_extensionId: ExtensionIdentifier, _activationEvent: string): Promise<void> { return Promise.resolve(); }
_activateById(_extensionId: ExtensionIdentifier, _reason: ExtensionActivationReason): Promise<void> { return Promise.resolve(); }
_onWillActivateExtension(_extensionId: ExtensionIdentifier): void { }
_onDidActivateExtension(_extensionId: ExtensionIdentifier, _startup: boolean, _codeLoadingTime: number, _activateCallTime: number, _activateResolvedTime: number, _activationEvent: string): void { }
_onDidActivateExtension(_extensionId: ExtensionIdentifier, _codeLoadingTime: number, _activateCallTime: number, _activateResolvedTime: number, _activationReason: ExtensionActivationReason): void { }
_onExtensionRuntimeError(_extensionId: ExtensionIdentifier, _err: Error): void { }
_onExtensionHostExit(code: number): void { }
}

View File

@@ -19,7 +19,7 @@ import { IExtensionEnablementService } from 'vs/workbench/services/extensionMana
import { BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE, ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import product from 'vs/platform/product/common/product';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { ExtensionScanner, ExtensionScannerInput, IExtensionReference, IExtensionResolver, IRelaxedExtensionDescription } from 'vs/workbench/services/extensions/node/extensionPoints';
import { Translations, ILog } from 'vs/workbench/services/extensions/common/extensionPoints';
@@ -55,7 +55,7 @@ export class CachedExtensionScanner {
@INotificationService private readonly _notificationService: INotificationService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService,
@IWindowService private readonly _windowService: IWindowService,
@IHostService private readonly _hostService: IHostService,
) {
this.scannedExtensions = new Promise<IExtensionDescription[]>((resolve, reject) => {
this._scannedExtensionsResolve = resolve;
@@ -78,7 +78,7 @@ export class CachedExtensionScanner {
public async startScanningExtensions(log: ILog): Promise<void> {
try {
const translations = await this.translationConfig;
const { system, user, development } = await CachedExtensionScanner._scanInstalledExtensions(this._windowService, this._notificationService, this._environmentService, this._extensionEnablementService, log, translations);
const { system, user, development } = await CachedExtensionScanner._scanInstalledExtensions(this._hostService, this._notificationService, this._environmentService, this._extensionEnablementService, log, translations);
let result = new Map<string, IExtensionDescription>();
system.forEach((systemExtension) => {
@@ -111,7 +111,7 @@ export class CachedExtensionScanner {
}
}
private static async _validateExtensionsCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput): Promise<void> {
private static async _validateExtensionsCache(hostService: IHostService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput): Promise<void> {
const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER);
const cacheFile = path.join(cacheFolder, cacheKey);
@@ -141,7 +141,7 @@ export class CachedExtensionScanner {
nls.localize('extensionCache.invalid', "Extensions have been modified on disk. Please reload the window."),
[{
label: nls.localize('reloadWindow', "Reload Window"),
run: () => windowService.reloadWindow()
run: () => hostService.reload()
}]
);
}
@@ -177,7 +177,7 @@ export class CachedExtensionScanner {
}
}
private static async _scanExtensionsWithCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput, log: ILog): Promise<IExtensionDescription[]> {
private static async _scanExtensionsWithCache(hostService: IHostService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput, log: ILog): Promise<IExtensionDescription[]> {
if (input.devMode) {
// Do not cache when running out of sources...
return ExtensionScanner.scanExtensions(input, log);
@@ -195,7 +195,7 @@ export class CachedExtensionScanner {
// Validate the cache asynchronously after 5s
setTimeout(async () => {
try {
await this._validateExtensionsCache(windowService, notificationService, environmentService, cacheKey, input);
await this._validateExtensionsCache(hostService, notificationService, environmentService, cacheKey, input);
} catch (err) {
errors.onUnexpectedError(err);
}
@@ -234,7 +234,7 @@ export class CachedExtensionScanner {
}
private static _scanInstalledExtensions(
windowService: IWindowService,
hostService: IHostService,
notificationService: INotificationService,
environmentService: IEnvironmentService,
extensionEnablementService: IExtensionEnablementService,
@@ -248,7 +248,7 @@ export class CachedExtensionScanner {
const locale = platform.language;
const builtinExtensions = this._scanExtensionsWithCache(
windowService,
hostService,
notificationService,
environmentService,
BUILTIN_MANIFEST_CACHE_FILE,
@@ -282,7 +282,7 @@ export class CachedExtensionScanner {
extensionEnablementService.allUserExtensionsDisabled || !environmentService.extensionsPath
? Promise.resolve([])
: this._scanExtensionsWithCache(
windowService,
hostService,
notificationService,
environmentService,
USER_MANIFEST_CACHE_FILE,

View File

@@ -27,7 +27,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import product from 'vs/platform/product/common/product';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IElectronService } from 'vs/platform/electron/node/electron';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IInitData, UIKind } from 'vs/workbench/api/common/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
@@ -38,6 +38,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug';
import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions';
import { isEqualOrParent } from 'vs/base/common/resources';
import { IHostService } from 'vs/workbench/services/host/browser/host';
export class ExtensionHostProcessWorker implements IExtensionHostStarter {
@@ -68,13 +69,14 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
private readonly _extensionHostLogsLocation: URI,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@INotificationService private readonly _notificationService: INotificationService,
@IWindowService private readonly _windowService: IWindowService,
@IElectronService private readonly _electronService: IElectronService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@ILogService private readonly _logService: ILogService,
@ILabelService private readonly _labelService: ILabelService,
@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService
@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService,
@IHostService private readonly _hostService: IHostService
) {
const devOpts = parseExtensionDevOptions(this._environmentService);
this._isExtensionDevHost = devOpts.isExtensionDevHost;
@@ -96,12 +98,12 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
this._toDispose.add(this._lifecycleService.onShutdown(reason => this.terminate()));
this._toDispose.add(this._extensionHostDebugService.onClose(event => {
if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId === event.sessionId) {
this._windowService.closeWindow();
this._electronService.closeWindow();
}
}));
this._toDispose.add(this._extensionHostDebugService.onReload(event => {
if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId === event.sessionId) {
this._windowService.reloadWindow();
this._hostService.reload();
}
}));
@@ -234,7 +236,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
this._notificationService.prompt(Severity.Warning, msg,
[{
label: nls.localize('reloadWindow', "Reload Window"),
run: () => this._windowService.reloadWindow()
run: () => this._hostService.reload()
}],
{ sticky: true }
);

View File

@@ -19,13 +19,14 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { isUIExtension as isUIExtensionFunc } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager';
import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
@@ -36,6 +37,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
import { Logger } from 'vs/workbench/services/extensions/common/extensionPoints';
import { flatten } from 'vs/base/common/arrays';
import { IStaticExtensionsService } from 'vs/workbench/services/extensions/common/staticExtensions';
import { IElectronService } from 'vs/platform/electron/node/electron';
class DeltaExtensionsQueueItem {
constructor(
@@ -67,6 +69,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IWindowService protected readonly _windowService: IWindowService,
@IStaticExtensionsService private readonly _staticExtensions: IStaticExtensionsService,
@IElectronService private readonly _electronService: IElectronService,
@IHostService private readonly _hostService: IHostService
) {
super(
instantiationService,
@@ -82,7 +86,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
this._notificationService.prompt(Severity.Info, nls.localize('extensionsDisabled', "All installed extensions are temporarily disabled. Reload the window to return to the previous state."), [{
label: nls.localize('Reload', "Reload"),
run: () => {
this._windowService.reloadWindow();
this._hostService.reload();
}
}]);
}
@@ -326,7 +330,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
if (shouldActivate) {
await Promise.all(
this._extensionHostProcessManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, shouldActivateReason!))
this._extensionHostProcessManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, { startup: false, extensionId: extensionDescription.identifier, activationEvent: shouldActivateReason! }))
).then(() => { });
}
}
@@ -384,8 +388,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
label: nls.localize('relaunch', "Relaunch VS Code"),
run: () => {
this._instantiationService.invokeFunction((accessor) => {
const windowsService = accessor.get(IWindowsService);
windowsService.relaunch({});
const hostService = accessor.get(IHostService);
hostService.restart();
});
}
}]
@@ -396,7 +400,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
this._notificationService.prompt(Severity.Error, nls.localize('extensionService.crash', "Extension host terminated unexpectedly."),
[{
label: nls.localize('devTools', "Open Developer Tools"),
run: () => this._windowService.openDevTools()
run: () => this._electronService.openDevTools()
},
{
label: nls.localize('restart', "Restart Extension Host"),
@@ -435,6 +439,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
}
protected async _scanAndHandleExtensions(): Promise<void> {
const isUIExtension = (extension: IExtensionDescription) => isUIExtensionFunc(extension, this._productService, this._configurationService);
this._extensionScanner.startScanningExtensions(this.createLogger());
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
@@ -446,7 +452,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
this._checkEnableProposedApi(localExtensions);
// remove disabled extensions
localExtensions = localExtensions.filter(extension => this._isEnabled(extension));
localExtensions = remove(localExtensions, extension => this._isDisabled(extension));
if (remoteAuthority) {
let resolvedAuthority: ResolverResult;
@@ -502,18 +508,16 @@ export class ExtensionService extends AbstractExtensionService implements IExten
this._checkEnableProposedApi(remoteEnv.extensions);
// remove disabled extensions
remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension));
// remove UI extensions from the remote extensions
remoteEnv.extensions = remoteEnv.extensions.filter(extension => !isUIExtension(extension, this._productService, this._configurationService));
remoteEnv.extensions = remove(remoteEnv.extensions, extension => this._isDisabled(extension));
// remove non-UI extensions from the local extensions
localExtensions = localExtensions.filter(extension => extension.isBuiltin || isUIExtension(extension, this._productService, this._configurationService));
localExtensions = remove(localExtensions, extension => !extension.isBuiltin && !isUIExtension(extension));
// in case of overlap, the remote wins
const isRemoteExtension = new Set<string>();
remoteEnv.extensions.forEach(extension => isRemoteExtension.add(ExtensionIdentifier.toKey(extension.identifier)));
localExtensions = localExtensions.filter(extension => !isRemoteExtension.has(ExtensionIdentifier.toKey(extension.identifier)));
// in case of UI extensions overlap, the local extension wins
remoteEnv.extensions = remove(remoteEnv.extensions, localExtensions.filter(extension => isUIExtension(extension)));
// in case of other extensions overlap, the remote extension wins
localExtensions = remove(localExtensions, remoteEnv.extensions);
// save for remote extension's init data
this._remoteExtensionsEnvironmentData.set(remoteAuthority, remoteEnv);
@@ -548,7 +552,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
public _onExtensionHostExit(code: number): void {
// Expected development extension termination: When the extension host goes down we also shutdown the window
if (!this._isExtensionDevTestFromCli) {
this._windowService.closeWindow();
this._electronService.closeWindow();
}
// When CLI testing make sure to exit with proper exit code
@@ -558,4 +562,23 @@ export class ExtensionService extends AbstractExtensionService implements IExten
}
}
function remove(arr: IExtensionDescription[], predicate: (item: IExtensionDescription) => boolean): IExtensionDescription[];
function remove(arr: IExtensionDescription[], toRemove: IExtensionDescription[]): IExtensionDescription[];
function remove(arr: IExtensionDescription[], arg2: ((item: IExtensionDescription) => boolean) | IExtensionDescription[]): IExtensionDescription[] {
if (typeof arg2 === 'function') {
return _removePredicate(arr, arg2);
}
return _removeSet(arr, arg2);
}
function _removePredicate(arr: IExtensionDescription[], predicate: (item: IExtensionDescription) => boolean): IExtensionDescription[] {
return arr.filter(extension => !predicate(extension));
}
function _removeSet(arr: IExtensionDescription[], toRemove: IExtensionDescription[]): IExtensionDescription[] {
const toRemoveSet = new Set<string>();
toRemove.forEach(extension => toRemoveSet.add(ExtensionIdentifier.toKey(extension.identifier)));
return arr.filter(extension => !toRemoveSet.has(ExtensionIdentifier.toKey(extension.identifier)));
}
registerSingleton(IExtensionService, ExtensionService);

View File

@@ -13,7 +13,7 @@ import { PersistentProtocol, ProtocolConstants, BufferedEmitter } from 'vs/base/
import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import product from 'vs/platform/product/common/product';
import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage, IExtHostReduceGraceTimeMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { ExtensionHostMain, IExitFn } from 'vs/workbench/services/extensions/common/extensionHostMain';
import { VSBuffer } from 'vs/base/common/buffer';
import { IURITransformer, URITransformer, IRawURITransformer } from 'vs/base/common/uriIpc';
@@ -21,6 +21,7 @@ import { exists } from 'vs/base/node/pfs';
import { realpath } from 'vs/base/node/extpath';
import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService';
import 'vs/workbench/api/node/extHost.services';
import { RunOnceScheduler } from 'vs/base/common/async';
interface ParsedExtHostArgs {
uriTransformerPath?: string;
@@ -91,9 +92,12 @@ function _createExtHostProtocol(): Promise<IMessagePassingProtocol> {
reject(new Error('VSCODE_EXTHOST_IPC_SOCKET timeout'));
}, 60000);
let disconnectWaitTimer: NodeJS.Timeout | null = null;
const reconnectionGraceTime = ProtocolConstants.ReconnectionGraceTime;
const reconnectionShortGraceTime = ProtocolConstants.ReconnectionShortGraceTime;
const disconnectRunner1 = new RunOnceScheduler(() => onTerminate(), reconnectionGraceTime);
const disconnectRunner2 = new RunOnceScheduler(() => onTerminate(), reconnectionShortGraceTime);
process.on('message', (msg: IExtHostSocketMessage, handle: net.Socket) => {
process.on('message', (msg: IExtHostSocketMessage | IExtHostReduceGraceTimeMessage, handle: net.Socket) => {
if (msg && msg.type === 'VSCODE_EXTHOST_IPC_SOCKET') {
const initialDataChunk = VSBuffer.wrap(Buffer.from(msg.initialDataChunk, 'base64'));
let socket: NodeSocket | WebSocketNodeSocket;
@@ -104,10 +108,8 @@ function _createExtHostProtocol(): Promise<IMessagePassingProtocol> {
}
if (protocol) {
// reconnection case
if (disconnectWaitTimer) {
clearTimeout(disconnectWaitTimer);
disconnectWaitTimer = null;
}
disconnectRunner1.cancel();
disconnectRunner2.cancel();
protocol.beginAcceptReconnection(socket, initialDataChunk);
protocol.endAcceptReconnection();
} else {
@@ -116,21 +118,21 @@ function _createExtHostProtocol(): Promise<IMessagePassingProtocol> {
protocol.onClose(() => onTerminate());
resolve(protocol);
if (msg.skipWebSocketFrames) {
// Wait for rich client to reconnect
protocol.onSocketClose(() => {
// The socket has closed, let's give the renderer a certain amount of time to reconnect
disconnectWaitTimer = setTimeout(() => {
disconnectWaitTimer = null;
onTerminate();
}, ProtocolConstants.ReconnectionGraceTime);
});
} else {
// Do not wait for web companion to reconnect
protocol.onSocketClose(() => {
onTerminate();
});
}
// Wait for rich client to reconnect
protocol.onSocketClose(() => {
// The socket has closed, let's give the renderer a certain amount of time to reconnect
disconnectRunner1.schedule();
});
}
}
if (msg && msg.type === 'VSCODE_EXTHOST_IPC_REDUCE_GRACE_TIME') {
if (disconnectRunner2.isScheduled()) {
// we are disconnected and already running the short reconnection timer
return;
}
if (disconnectRunner1.isScheduled()) {
// we are disconnected and running the long reconnection timer
disconnectRunner2.schedule();
}
}
});

View File

@@ -5,16 +5,75 @@
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
export class BrowserHostService implements IHostService {
_serviceBrand: undefined;
constructor(@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService) { }
//#region Window
readonly windowCount = Promise.resolve(1);
async openEmptyWindow(options?: { reuse?: boolean, remoteAuthority?: string }): Promise<void> {
// TODO@Ben delegate to embedder
const targetHref = `${document.location.origin}${document.location.pathname}?ew=true`;
if (options && options.reuse) {
window.location.href = targetHref;
} else {
window.open(targetHref);
}
}
async toggleFullScreen(): Promise<void> {
const target = this.layoutService.getWorkbenchElement();
// Chromium
if (document.fullscreen !== undefined) {
if (!document.fullscreen) {
try {
return await target.requestFullscreen();
} catch (error) {
console.warn('Toggle Full Screen failed'); // https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen
}
} else {
try {
return await document.exitFullscreen();
} catch (error) {
console.warn('Exit Full Screen failed');
}
}
}
// Safari and Edge 14 are all using webkit prefix
if ((<any>document).webkitIsFullScreen !== undefined) {
try {
if (!(<any>document).webkitIsFullScreen) {
(<any>target).webkitRequestFullscreen(); // it's async, but doesn't return a real promise.
} else {
(<any>document).webkitExitFullscreen(); // it's async, but doesn't return a real promise.
}
} catch {
console.warn('Enter/Exit Full Screen failed');
}
}
}
//#endregion
async restart(): Promise<void> {
this.reload();
}
async reload(): Promise<void> {
window.location.reload();
}
async closeWorkspace(): Promise<void> {
return this.openEmptyWindow({ reuse: true });
}
}
registerSingleton(IHostService, BrowserHostService, true);

View File

@@ -18,5 +18,36 @@ export interface IHostService {
*/
readonly windowCount: Promise<number>;
/**
* Opens an empty window. The optional parameter allows to define if
* a new window should open or the existing one change to an empty.
*/
openEmptyWindow(options?: { reuse?: boolean, remoteAuthority?: string }): Promise<void>;
/**
* Switch between fullscreen and normal window.
*/
toggleFullScreen(): Promise<void>;
//#endregion
//#region Lifecycle
/**
* Restart the entire application.
*/
restart(): Promise<void>;
/**
* Reload the currently active window.
*/
reload(): Promise<void>;
/**
* Closes the currently opened folder/workspace and returns to an empty
* window.
*/
closeWorkspace(): Promise<void>;
//#endregion
}

View File

@@ -17,7 +17,27 @@ export class DesktopHostService implements IHostService {
get windowCount() { return this.electronService.windowCount(); }
openEmptyWindow(options?: { reuse?: boolean, remoteAuthority?: string }): Promise<void> {
return this.electronService.openEmptyWindow(options);
}
toggleFullScreen(): Promise<void> {
return this.electronService.toggleFullScreen();
}
//#endregion
restart(): Promise<void> {
return this.electronService.relaunch();
}
reload(): Promise<void> {
return this.electronService.reload();
}
closeWorkspace(): Promise<void> {
return this.electronService.closeWorkpsace();
}
}
registerSingleton(IHostService, DesktopHostService, true);

View File

@@ -603,7 +603,7 @@ let schema: IJSONSchema = {
id: schemaId,
type: 'array',
title: nls.localize('keybindings.json.title', "Keybindings configuration"),
allowsTrailingCommas: true,
allowTrailingCommas: true,
allowComments: true,
definitions: {
'editorGroupsSchema': {

View File

@@ -27,11 +27,6 @@ export const enum Position {
BOTTOM
}
export interface ILayoutOptions {
toggleMaximizedPanel?: boolean;
source?: Parts;
}
export interface IWorkbenchLayoutService extends ILayoutService {
_serviceBrand: undefined;

View File

@@ -44,8 +44,6 @@ class TestProgressBar {
fInfinite: boolean = false;
fDone: boolean = false;
constructor() { }
infinite() {
this.fDone = null!;
this.fInfinite = true;

View File

@@ -88,12 +88,24 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
export class TunnelService implements ITunnelService {
_serviceBrand: undefined;
private readonly _tunnels = new Map</* port */ number, { refcount: number, readonly value: Promise<RemoteTunnel> }>();
public constructor(
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@ISignService private readonly signService: ISignService,
@ILogService private readonly logService: ILogService
) {
@ILogService private readonly logService: ILogService,
) { }
public get tunnels(): Promise<readonly RemoteTunnel[]> {
return Promise.all(Array.from(this._tunnels.values()).map(x => x.value));
}
dispose(): void {
for (const { value } of this._tunnels.values()) {
value.then(tunnel => tunnel.dispose());
}
this._tunnels.clear();
}
openTunnel(remotePort: number): Promise<RemoteTunnel> | undefined {
@@ -102,6 +114,33 @@ export class TunnelService implements ITunnelService {
return undefined;
}
const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remotePort);
if (!resolvedTunnel) {
return resolvedTunnel;
}
return resolvedTunnel.then(tunnel => ({
tunnelRemotePort: tunnel.tunnelRemotePort,
tunnelLocalPort: tunnel.tunnelLocalPort,
dispose: () => {
const existing = this._tunnels.get(remotePort);
if (existing) {
if (--existing.refcount <= 0) {
existing.value.then(tunnel => tunnel.dispose());
this._tunnels.delete(remotePort);
}
}
}
}));
}
private retainOrCreateTunnel(remoteAuthority: string, remotePort: number): Promise<RemoteTunnel> | undefined {
const existing = this._tunnels.get(remotePort);
if (existing) {
++existing.refcount;
return existing.value;
}
const options: IConnectionOptions = {
commit: product.commit,
socketFactory: nodeSocketFactory,
@@ -114,7 +153,10 @@ export class TunnelService implements ITunnelService {
signService: this.signService,
logService: this.logService
};
return createRemoteTunnel(options, remotePort);
const tunnel = createRemoteTunnel(options, remotePort);
this._tunnels.set(remotePort, { refcount: 1, value: tunnel });
return tunnel;
}
}

View File

@@ -9,9 +9,11 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { ILogService } from 'vs/platform/log/common/log';
import { RequestChannelClient } from 'vs/platform/request/common/requestIpc';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { RequestService as BrowserRequestService } from 'vs/platform/request/browser/requestService';
import { RequestService } from 'vs/platform/request/browser/requestService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IRequestService } from 'vs/platform/request/common/request';
export class RequestService extends BrowserRequestService {
export class BrowserRequestService extends RequestService {
private readonly remoteRequestChannel: RequestChannelClient | null;
@@ -40,5 +42,6 @@ export class RequestService extends BrowserRequestService {
throw error;
}
}
}
registerSingleton(IRequestService, BrowserRequestService, true);

View File

@@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { RequestService } from 'vs/platform/request/browser/requestService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IRequestService } from 'vs/platform/request/common/request';
import { IElectronService } from 'vs/platform/electron/node/electron';
export class NativeRequestService extends RequestService {
constructor(
@IConfigurationService configurationService: IConfigurationService,
@ILogService logService: ILogService,
@IElectronService private electronService: IElectronService
) {
super(configurationService, logService);
}
async resolveProxy(url: string): Promise<string | undefined> {
return this.electronService.resolveProxy(url);
}
}
registerSingleton(IRequestService, NativeRequestService, true);

View File

@@ -170,8 +170,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
}
private async backupBeforeShutdown(dirtyToBackup: URI[], reason: ShutdownReason): Promise<boolean> {
const windowCount = await this.hostService.windowCount;
// When quit is requested skip the confirm callback and attempt to backup all workspaces.
// When quit is not requested the confirm callback should be shown when the window being
// closed is the only VS Code window open, except for on Mac where hot exit is only
@@ -182,7 +180,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
case ShutdownReason.CLOSE:
if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured
} else if (windowCount > 1 || platform.isMacintosh) {
} else if (await this.hostService.windowCount > 1 || platform.isMacintosh) {
doBackup = false; // do not backup if a window is closed that does not cause quitting of the application
} else {
doBackup = true; // backup if last window is closed on win/linux where the application quits right after

View File

@@ -192,7 +192,7 @@ export const colorThemeSchemaId = 'vscode://schemas/color-theme';
const colorThemeSchema: IJSONSchema = {
type: 'object',
allowComments: true,
allowsTrailingCommas: true,
allowTrailingCommas: true,
properties: {
colors: {
description: nls.localize('schema.workbenchColors', 'Colors in the workbench'),

View File

@@ -12,7 +12,7 @@ const schemaId = 'vscode://schemas/icon-theme';
const schema: IJSONSchema = {
type: 'object',
allowComments: true,
allowsTrailingCommas: true,
allowTrailingCommas: true,
definitions: {
folderExpanded: {
type: 'string',

View File

@@ -7,7 +7,7 @@ import { Event, Emitter } from 'vs/base/common/event';
import { IUpdateService, State, UpdateType } from 'vs/platform/update/common/update';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { Disposable } from 'vs/base/common/lifecycle';
export interface IUpdate {
@@ -40,7 +40,7 @@ export class UpdateService extends Disposable implements IUpdateService {
constructor(
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IWindowService private readonly windowService: IWindowService
@IHostService private readonly hostService: IHostService
) {
super();
@@ -84,11 +84,11 @@ export class UpdateService extends Disposable implements IUpdateService {
}
async applyUpdate(): Promise<void> {
this.windowService.reloadWindow();
this.hostService.reload();
}
async quitAndInstall(): Promise<void> {
this.windowService.reloadWindow();
this.hostService.reload();
}
}

View File

@@ -15,6 +15,8 @@ import { IModelService } from 'vs/editor/common/services/modelService';
import { Position } from 'vs/editor/common/core/position';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ISettingsMergeService } from 'vs/platform/userDataSync/common/userDataSync';
import { values } from 'vs/base/common/map';
import { IStringDictionary } from 'vs/base/common/collections';
class SettingsMergeService implements ISettingsMergeService {
@@ -22,27 +24,27 @@ class SettingsMergeService implements ISettingsMergeService {
constructor(
@IModelService private readonly modelService: IModelService,
@IModeService private readonly modeService: IModeService
@IModeService private readonly modeService: IModeService,
) { }
async merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> {
async merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: IStringDictionary<boolean>): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> {
const local = parse(localContent);
const remote = parse(remoteContent);
const base = baseContent ? parse(baseContent) : null;
const localToRemote = this.compare(local, remote);
const localToRemote = this.compare(local, remote, ignoredSettings);
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
// No changes found between local and remote.
return { mergeContent: localContent, hasChanges: false, hasConflicts: false };
}
const conflicts: Set<string> = new Set<string>();
const baseToLocal = base ? this.compare(base, local) : { added: Object.keys(local).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
const baseToRemote = base ? this.compare(base, remote) : { added: Object.keys(remote).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
const baseToLocal = base ? this.compare(base, local, ignoredSettings) : { added: Object.keys(local).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
const baseToRemote = base ? this.compare(base, remote, ignoredSettings) : { added: Object.keys(remote).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
const settingsPreviewModel = this.modelService.createModel(localContent, this.modeService.create('jsonc'));
// Removed settings in Local
for (const key of baseToLocal.removed.keys()) {
for (const key of values(baseToLocal.removed)) {
// Got updated in remote
if (baseToRemote.updated.has(key)) {
conflicts.add(key);
@@ -50,7 +52,7 @@ class SettingsMergeService implements ISettingsMergeService {
}
// Removed settings in Remote
for (const key of baseToRemote.removed.keys()) {
for (const key of values(baseToRemote.removed)) {
if (conflicts.has(key)) {
continue;
}
@@ -63,7 +65,7 @@ class SettingsMergeService implements ISettingsMergeService {
}
// Added settings in Local
for (const key of baseToLocal.added.keys()) {
for (const key of values(baseToLocal.added)) {
if (conflicts.has(key)) {
continue;
}
@@ -77,7 +79,7 @@ class SettingsMergeService implements ISettingsMergeService {
}
// Added settings in remote
for (const key of baseToRemote.added.keys()) {
for (const key of values(baseToRemote.added)) {
if (conflicts.has(key)) {
continue;
}
@@ -93,7 +95,7 @@ class SettingsMergeService implements ISettingsMergeService {
}
// Updated settings in Local
for (const key of baseToLocal.updated.keys()) {
for (const key of values(baseToLocal.updated)) {
if (conflicts.has(key)) {
continue;
}
@@ -107,7 +109,7 @@ class SettingsMergeService implements ISettingsMergeService {
}
// Updated settings in Remote
for (const key of baseToRemote.updated.keys()) {
for (const key of values(baseToRemote.updated)) {
if (conflicts.has(key)) {
continue;
}
@@ -122,7 +124,7 @@ class SettingsMergeService implements ISettingsMergeService {
}
}
for (const key of conflicts.keys()) {
for (const key of values(conflicts)) {
const tree = parseTree(settingsPreviewModel.getValue());
const valueNode = findNodeAtLocation(tree, [key]);
const eol = settingsPreviewModel.getEOL();
@@ -149,6 +151,18 @@ class SettingsMergeService implements ISettingsMergeService {
return { mergeContent: settingsPreviewModel.getValue(), hasChanges: true, hasConflicts: conflicts.size > 0 };
}
async computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: IStringDictionary<boolean>): Promise<string> {
const remote = parse(remoteContent);
const remoteModel = this.modelService.createModel(localContent, this.modeService.create('jsonc'));
for (const key of Object.keys(ignoredSettings)) {
if (ignoredSettings[key]) {
this.editSetting(remoteModel, key, undefined);
this.editSetting(remoteModel, key, remote[key]);
}
}
return remoteModel.getValue();
}
private editSetting(model: ITextModel, key: string, value: any | undefined): void {
const insertSpaces = false;
const tabSize = 4;
@@ -166,9 +180,9 @@ class SettingsMergeService implements ISettingsMergeService {
}
}
private compare(from: { [key: string]: any }, to: { [key: string]: any }): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
const fromKeys = Object.keys(from);
const toKeys = Object.keys(to);
private compare(from: IStringDictionary<any>, to: IStringDictionary<any>, ignored: IStringDictionary<boolean>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
const fromKeys = Object.keys(from).filter(key => !ignored[key]);
const toKeys = Object.keys(to).filter(key => !ignored[key]);
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
const updated: Set<string> = new Set<string>();

View File

@@ -3,12 +3,13 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SyncStatus, SyncSource, IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync';
import { SyncStatus, SyncSource, IUserDataSyncService, ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
export class UserDataSyncService extends Disposable implements IUserDataSyncService {
@@ -31,13 +32,24 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
) {
super();
this.channel = sharedProcessService.getChannel('userDataSync');
this._register(this.channel.listen<SyncStatus>('onDidChangeStatus')(status => this.updateStatus(status)));
this.channel.call<SyncStatus>('_getInitialStatus').then(status => {
this.updateStatus(status);
this._register(this.channel.listen<SyncStatus>('onDidChangeStatus')(status => this.updateStatus(status)));
});
}
sync(_continue?: boolean): Promise<boolean> {
return this.channel.call('sync', [_continue]);
}
getRemoteExtensions(): Promise<ISyncExtension[]> {
return this.channel.call('getRemoteExtensions');
}
removeExtension(identifier: IExtensionIdentifier): Promise<void> {
return this.channel.call('removeExtension', [identifier]);
}
private async updateStatus(status: SyncStatus): Promise<void> {
this._conflictsSource = await this.channel.call<SyncSource>('getConflictsSource');
this._status = status;

View File

@@ -4,10 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { IWindowService, IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IDevToolsOptions, IOpenSettings, IURIToOpen, isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/windows/common/windows';
import { IWindowService, IWindowsService, IOpenSettings, IURIToOpen, isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/windows/common/windows';
import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { ParsedArgs } from 'vs/platform/environment/common/environment';
import { URI } from 'vs/base/common/uri';
import { Disposable } from 'vs/base/common/lifecycle';
import { ILabelService } from 'vs/platform/label/common/label';
@@ -53,50 +51,6 @@ export class WindowService extends Disposable implements IWindowService {
return this._windowId;
}
pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise<void> {
options.windowId = this.windowId;
return this.windowsService.pickFileFolderAndOpen(options);
}
pickFileAndOpen(options: INativeOpenDialogOptions): Promise<void> {
options.windowId = this.windowId;
return this.windowsService.pickFileAndOpen(options);
}
pickFolderAndOpen(options: INativeOpenDialogOptions): Promise<void> {
options.windowId = this.windowId;
return this.windowsService.pickFolderAndOpen(options);
}
pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise<void> {
options.windowId = this.windowId;
return this.windowsService.pickWorkspaceAndOpen(options);
}
reloadWindow(args?: ParsedArgs): Promise<void> {
return this.windowsService.reloadWindow(this.windowId, args);
}
openDevTools(options?: IDevToolsOptions): Promise<void> {
return this.windowsService.openDevTools(this.windowId, options);
}
toggleDevTools(): Promise<void> {
return this.windowsService.toggleDevTools(this.windowId);
}
closeWorkspace(): Promise<void> {
return this.windowsService.closeWorkspace(this.windowId);
}
enterWorkspace(path: URI): Promise<IEnterWorkspaceResult | undefined> {
return this.windowsService.enterWorkspace(this.windowId, path);
}
openWindow(uris: IURIToOpen[], options: IOpenSettings = {}): Promise<void> {
if (!!this.remoteAuthority) {
uris.forEach(u => u.label = u.label || this.getRecentLabel(u));
@@ -105,18 +59,6 @@ export class WindowService extends Disposable implements IWindowService {
return this.windowsService.openWindow(this.windowId, uris, options);
}
closeWindow(): Promise<void> {
return this.windowsService.closeWindow(this.windowId);
}
toggleFullScreen(target?: HTMLElement): Promise<void> {
return this.windowsService.toggleFullScreen(this.windowId);
}
setRepresentedFilename(fileName: string): Promise<void> {
return this.windowsService.setRepresentedFilename(this.windowId, fileName);
}
getRecentlyOpened(): Promise<IRecentlyOpened> {
return this.windowsService.getRecentlyOpened(this.windowId);
}
@@ -137,50 +79,6 @@ export class WindowService extends Disposable implements IWindowService {
return this.windowsService.isFocused(this.windowId);
}
isMaximized(): Promise<boolean> {
return this.windowsService.isMaximized(this.windowId);
}
maximizeWindow(): Promise<void> {
return this.windowsService.maximizeWindow(this.windowId);
}
unmaximizeWindow(): Promise<void> {
return this.windowsService.unmaximizeWindow(this.windowId);
}
minimizeWindow(): Promise<void> {
return this.windowsService.minimizeWindow(this.windowId);
}
onWindowTitleDoubleClick(): Promise<void> {
return this.windowsService.onWindowTitleDoubleClick(this.windowId);
}
setDocumentEdited(flag: boolean): Promise<void> {
return this.windowsService.setDocumentEdited(this.windowId, flag);
}
showMessageBox(options: Electron.MessageBoxOptions): Promise<IMessageBoxResult> {
return this.windowsService.showMessageBox(this.windowId, options);
}
showSaveDialog(options: Electron.SaveDialogOptions): Promise<string> {
return this.windowsService.showSaveDialog(this.windowId, options);
}
showOpenDialog(options: Electron.OpenDialogOptions): Promise<string[]> {
return this.windowsService.showOpenDialog(this.windowId, options);
}
updateTouchBar(items: ISerializableCommandAction[][]): Promise<void> {
return this.windowsService.updateTouchBar(this.windowId, items);
}
resolveProxy(url: string): Promise<string | undefined> {
return this.windowsService.resolveProxy(this.windowId, url);
}
private getRecentLabel(u: IURIToOpen): string {
if (isFolderToOpen(u)) {
return this.labelService.getWorkspaceLabel(u.folderUri, { verbose: true });
@@ -192,4 +90,4 @@ export class WindowService extends Disposable implements IWindowService {
}
}
registerSingleton(IWindowService, WindowService);
registerSingleton(IWindowService, WindowService);

View File

@@ -7,7 +7,7 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common
import { URI } from 'vs/base/common/uri';
import * as nls from 'vs/nls';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWindowService, MessageBoxOptions, IWindowsService } from 'vs/platform/windows/common/windows';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { IJSONEditingService, JSONEditingError, JSONEditingErrorCode } from 'vs/workbench/services/configuration/common/jsonEditing';
import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService, rewriteWorkspaceFileForNewLocation, WORKSPACE_FILTER } from 'vs/platform/workspaces/common/workspaces';
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
@@ -50,7 +50,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
@IFileService private readonly fileService: IFileService,
@ITextFileService private readonly textFileService: ITextFileService,
@IWindowsService private readonly windowsService: IWindowsService,
@IWorkspacesService private readonly workspaceService: IWorkspacesService,
@IWorkspacesService private readonly workspacesService: IWorkspacesService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IFileDialogService private readonly fileDialogService: IFileDialogService,
@IDialogService private readonly dialogService: IDialogService,
@@ -123,7 +123,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
// Don't Save: delete workspace
case ConfirmResult.DONT_SAVE:
this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier);
this.workspacesService.deleteUntitledWorkspace(workspaceIdentifier);
return false;
// Save: save workspace, but do not veto unload if path provided
@@ -136,12 +136,12 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
try {
await this.saveWorkspaceAs(workspaceIdentifier, newWorkspacePath);
const newWorkspaceIdentifier = await this.workspaceService.getWorkspaceIdentifier(newWorkspacePath);
const newWorkspaceIdentifier = await this.workspacesService.getWorkspaceIdentifier(newWorkspacePath);
const label = this.labelService.getWorkspaceLabel(newWorkspaceIdentifier, { verbose: true });
this.windowService.addRecentlyOpened([{ label, workspace: newWorkspaceIdentifier }]);
this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier);
this.workspacesService.deleteUntitledWorkspace(workspaceIdentifier);
} catch (error) {
// ignore
}
@@ -293,7 +293,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
}
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
const untitledWorkspace = await this.workspaceService.createUntitledWorkspace(folders, remoteAuthority);
const untitledWorkspace = await this.workspacesService.createUntitledWorkspace(folders, remoteAuthority);
if (path) {
await this.saveWorkspaceAs(untitledWorkspace, path);
} else {
@@ -323,14 +323,14 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
// Prevent overwriting a workspace that is currently opened in another window
if (windows.some(window => !!window.workspace && isEqual(window.workspace.configPath, path))) {
const options: MessageBoxOptions = {
type: 'info',
buttons: [nls.localize('ok', "OK")],
message: nls.localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)),
detail: nls.localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."),
noLink: true
};
await this.windowService.showMessageBox(options);
await this.dialogService.show(
Severity.Info,
nls.localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)),
[nls.localize('ok', "OK")],
{
detail: nls.localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again.")
}
);
return false;
}
@@ -389,7 +389,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
throw new Error('Entering a new workspace is not possible in tests.');
}
const workspace = await this.workspaceService.getWorkspaceIdentifier(path);
const workspace = await this.workspacesService.getWorkspaceIdentifier(path);
// Settings migration (only if we come from a folder workspace)
if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
@@ -399,7 +399,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
const workspaceImpl = this.contextService as WorkspaceService;
await workspaceImpl.initialize(workspace);
const result = await this.windowService.enterWorkspace(path);
const result = await this.workspacesService.enterWorkspace(path);
if (result) {
// Migrate storage to new workspace
@@ -415,7 +415,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
// TODO@aeschli: workaround until restarting works
if (this.environmentService.configuration.remoteAuthority) {
this.windowService.reloadWindow();
this.hostService.reload();
}
// Restart the extension host: entering a workspace means a new location for

View File

@@ -6,20 +6,25 @@
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IWorkspacesService, IWorkspaceFolderCreationData, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';
import { IEnterWorkspaceResult } from 'vs/platform/windows/common/windows';
export class WorkspacesService implements IWorkspacesService {
_serviceBrand: undefined;
async createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier> {
enterWorkspace(path: URI): Promise<IEnterWorkspaceResult | undefined> {
throw new Error('Untitled workspaces are currently unsupported in Web');
}
async deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise<void> {
createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier> {
throw new Error('Untitled workspaces are currently unsupported in Web');
}
async getWorkspaceIdentifier(workspacePath: URI): Promise<IWorkspaceIdentifier> {
deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise<void> {
throw new Error('Untitled workspaces are currently unsupported in Web');
}
getWorkspaceIdentifier(workspacePath: URI): Promise<IWorkspaceIdentifier> {
throw new Error('Untitled workspaces are currently unsupported in Web');
}
}

View File

@@ -8,6 +8,7 @@ import { IWorkspacesService, IWorkspaceIdentifier, IWorkspaceFolderCreationData,
import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
import { URI } from 'vs/base/common/uri';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IWindowService, IEnterWorkspaceResult } from 'vs/platform/windows/common/windows';
export class WorkspacesService implements IWorkspacesService {
@@ -15,10 +16,22 @@ export class WorkspacesService implements IWorkspacesService {
private channel: IChannel;
constructor(@IMainProcessService mainProcessService: IMainProcessService) {
constructor(
@IMainProcessService mainProcessService: IMainProcessService,
@IWindowService private readonly windowService: IWindowService
) {
this.channel = mainProcessService.getChannel('workspaces');
}
async enterWorkspace(path: URI): Promise<IEnterWorkspaceResult | undefined> {
const result: IEnterWorkspaceResult = await this.channel.call('enterWorkspace', [this.windowService.windowId, path]);
if (result) {
result.workspace = reviveWorkspaceIdentifier(result.workspace);
}
return result;
}
createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier> {
return this.channel.call('createUntitledWorkspace', [folders, remoteAuthority]).then(reviveWorkspaceIdentifier);
}