Merge from vscode e3c4990c67c40213af168300d1cfeb71d680f877 (#16569)

This commit is contained in:
Cory Rivera
2021-08-25 16:28:29 -07:00
committed by GitHub
parent ab1112bfb3
commit cb7b7da0a4
1752 changed files with 59525 additions and 33878 deletions

View File

@@ -46,7 +46,7 @@ export interface IBadge {
class BaseBadge implements IBadge {
constructor(public readonly descriptorFn: (arg: any) => string) {
constructor(readonly descriptorFn: (arg: any) => string) {
this.descriptorFn = descriptorFn;
}
@@ -57,7 +57,7 @@ class BaseBadge implements IBadge {
export class NumberBadge extends BaseBadge {
constructor(public readonly number: number, descriptorFn: (num: number) => string) {
constructor(readonly number: number, descriptorFn: (num: number) => string) {
super(descriptorFn);
this.number = number;
@@ -70,13 +70,13 @@ export class NumberBadge extends BaseBadge {
export class TextBadge extends BaseBadge {
constructor(public readonly text: string, descriptorFn: () => string) {
constructor(readonly text: string, descriptorFn: () => string) {
super(descriptorFn);
}
}
export class IconBadge extends BaseBadge {
constructor(public readonly icon: ThemeIcon, descriptorFn: () => string) {
constructor(readonly icon: ThemeIcon, descriptorFn: () => string) {
super(descriptorFn);
}
}

View File

@@ -37,7 +37,7 @@ export interface IAccountUsage {
lastUsed: number;
}
const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'vscode.github-browser', 'ms-vscode.github-browser', 'ms-vscode.remotehub', 'ms-vscode.remotehub-insiders', 'github.codespaces'];
const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'ms-vscode.remotehub', 'ms-vscode.remotehub-insiders', 'github.remotehub', 'github.remotehub-insiders', 'github.codespaces'];
export function readAccountUsages(storageService: IStorageService, providerId: string, accountName: string,): IAccountUsage[] {
const accountKey = `${providerId}-${accountName}-usages`;
@@ -338,7 +338,7 @@ export class AuthenticationService extends Disposable implements IAuthentication
}
Object.keys(existingRequestsForProvider).forEach(requestedScopes => {
if (addedSessions.some(session => session.scopes.slice().sort().join('') === requestedScopes)) {
if (addedSessions.some(session => session.scopes.slice().join('') === requestedScopes)) {
const sessionRequest = existingRequestsForProvider[requestedScopes];
sessionRequest?.disposables.forEach(item => item.dispose());
@@ -565,9 +565,11 @@ export class AuthenticationService extends Disposable implements IAuthentication
id: `${providerId}${extensionId}Access`,
title: nls.localize({
key: 'accessRequest',
comment: ['The placeholder {0} will be replaced with an extension name. (1) is to indicate that this menu item contributes to a badge count']
comment: [`The placeholder {0} will be replaced with an authentication provider''s label. {1} will be replaced with an extension name. (1) is to indicate that this menu item contributes to a badge count`]
},
"Grant access to {0}... (1)", extensionName)
"Grant access to {0} for {1}... (1)",
this.getLabel(providerId),
extensionName)
}
});
@@ -602,7 +604,7 @@ export class AuthenticationService extends Disposable implements IAuthentication
if (provider) {
const providerRequests = this._signInRequestItems.get(providerId);
const scopesList = scopes.sort().join('');
const scopesList = scopes.join('');
const extensionHasExistingRequest = providerRequests
&& providerRequests[scopesList]
&& providerRequests[scopesList].requestingExtensionIds.includes(extensionId);
@@ -615,12 +617,12 @@ export class AuthenticationService extends Disposable implements IAuthentication
group: '2_signInRequests',
command: {
id: `${extensionId}signIn`,
title: nls.localize(
{
key: 'signInRequest',
comment: ['The placeholder {0} will be replaced with an extension name. (1) is to indicate that this menu item contributes to a badge count.']
},
"Sign in to use {0} (1)",
title: nls.localize({
key: 'signInRequest',
comment: [`The placeholder {0} will be replaced with an authentication provider's label. {1} will be replaced with an extension name. (1) is to indicate that this menu item contributes to a badge count.`]
},
"Sign in with {0} to use {1} (1)",
provider.label,
extensionName)
}
});

View File

@@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Codicon } from 'vs/base/common/codicons';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILinkDescriptor } from 'vs/platform/opener/browser/link';
export interface IBannerItem {
readonly id: string;
readonly icon: Codicon;
readonly message: string | MarkdownString;
readonly actions?: ILinkDescriptor[];
readonly ariaLabel?: string;
readonly onClose?: () => void;
}
export const IBannerService = createDecorator<IBannerService>('bannerService');
export interface IBannerService {
readonly _serviceBrand: undefined;
focus(): void;
focusNextAction(): void;
focusPreviousAction(): void;
hide(id: string): void;
show(item: IBannerItem): void;
}

View File

@@ -12,13 +12,16 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import { once } from 'vs/base/common/functional';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { isSafari } from 'vs/base/browser/browser';
import { ILogService } from 'vs/platform/log/common/log';
export class BrowserClipboardService extends BaseBrowserClipboardService {
constructor(
@INotificationService private readonly notificationService: INotificationService,
@IOpenerService private readonly openerService: IOpenerService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@ILogService private readonly logService: ILogService
) {
super();
}
@@ -35,6 +38,12 @@ export class BrowserClipboardService extends BaseBrowserClipboardService {
return ''; // do not ask for input in tests (https://github.com/microsoft/vscode/issues/112264)
}
if (isSafari) {
this.logService.error(error);
return ''; // Safari does not seem to provide anyway to enable cipboard access (https://github.com/microsoft/vscode-internalbacklog/issues/2162#issuecomment-852042867)
}
return new Promise<string>(resolve => {
// Inform user about permissions problem (https://github.com/microsoft/vscode/issues/112089)

View File

@@ -3,13 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
import { ResourceMap } from 'vs/base/common/map';
import { Registry } from 'vs/platform/registry/common/platform';
// {{SQL CARBON EDIT}}
export const FOLDER_CONFIG_FOLDER_NAME = '.azuredatastudio';
@@ -50,14 +49,6 @@ export interface IConfigurationCache {
}
export function filterSettingsRequireWorkspaceTrust(settings: ReadonlyArray<string>): ReadonlyArray<string> {
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
return settings.filter(key => {
const property = configurationRegistry.getConfigurationProperties()[key];
return property.restricted && property.scope !== ConfigurationScope.APPLICATION && property.scope !== ConfigurationScope.MACHINE;
});
}
export type RestrictedSettings = {
default: ReadonlyArray<string>;
userLocal?: ReadonlyArray<string>;

View File

@@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';

View File

@@ -19,6 +19,7 @@ import { IQuickInputService, IInputOptions, IQuickPickItem, IPickOptions } from
import { ConfiguredInput } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { ILabelService } from 'vs/platform/label/common/label';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
export abstract class BaseConfigurationResolverService extends AbstractVariableResolverService {
@@ -35,7 +36,8 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR
private readonly commandService: ICommandService,
private readonly workspaceContextService: IWorkspaceContextService,
private readonly quickInputService: IQuickInputService,
private readonly labelService: ILabelService
private readonly labelService: ILabelService,
private readonly pathService: IPathService
) {
super({
getFolderUri: (folderName: string): uri | undefined => {
@@ -57,7 +59,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR
getFilePath: (): string | undefined => {
const fileResource = EditorResourceAccessor.getOriginalUri(editorService.activeEditor, {
supportSideBySide: SideBySideEditor.PRIMARY,
filterByScheme: [Schemas.file, Schemas.userData, Schemas.vscodeRemote]
filterByScheme: [Schemas.file, Schemas.userData, this.pathService.defaultUriScheme]
});
if (!fileResource) {
return undefined;
@@ -67,7 +69,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR
getWorkspaceFolderPathForFile: (): string | undefined => {
const fileResource = EditorResourceAccessor.getOriginalUri(editorService.activeEditor, {
supportSideBySide: SideBySideEditor.PRIMARY,
filterByScheme: [Schemas.file, Schemas.userData, Schemas.vscodeRemote]
filterByScheme: [Schemas.file, Schemas.userData, this.pathService.defaultUriScheme]
});
if (!fileResource) {
return undefined;
@@ -366,9 +368,10 @@ export class ConfigurationResolverService extends BaseConfigurationResolverServi
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
@IQuickInputService quickInputService: IQuickInputService,
@ILabelService labelService: ILabelService,
@IPathService pathService: IPathService
) {
super({ getAppRoot: () => undefined, getExecPath: () => undefined },
Promise.resolve(Object.create(null)), editorService, configurationService,
commandService, workspaceContextService, quickInputService, labelService);
commandService, workspaceContextService, quickInputService, labelService, pathService);
}
}

View File

@@ -9,4 +9,4 @@ export function applyDeprecatedVariableMessage(schema: IJSONSchema) {
schema.pattern = schema.pattern || '^(?!.*\\$\\{(env|config|command)\\.)';
schema.patternErrorMessage = schema.patternErrorMessage ||
nls.localize('deprecatedVariables', "'env.', 'config.' and 'command.' are deprecated, use 'env:', 'config:' and 'command:' instead.");
}
}

View File

@@ -141,7 +141,7 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe
// loop through all variables occurrences in 'value'
const replaced = value.replace(AbstractVariableResolverService.VARIABLE_REGEXP, (match: string, variable: string) => {
// disallow attempted nesting, see #77289
// disallow attempted nesting, see #77289. This doesn't exclude variables that resolve to other variables.
if (variable.includes(AbstractVariableResolverService.VARIABLE_LHS)) {
return match;
}
@@ -152,6 +152,10 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe
resolvedVariables.set(variable, resolvedValue);
}
if ((resolvedValue !== match) && types.isString(resolvedValue) && resolvedValue.match(AbstractVariableResolverService.VARIABLE_REGEXP)) {
resolvedValue = this.resolveString(environment, folderUri, resolvedValue, commandValueMapping, resolvedVariables);
}
return resolvedValue;
});

View File

@@ -14,6 +14,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService';
import { ILabelService } from 'vs/platform/label/common/label';
import { IShellEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/shellEnvironmentService';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
export class ConfigurationResolverService extends BaseConfigurationResolverService {
@@ -25,7 +26,8 @@ export class ConfigurationResolverService extends BaseConfigurationResolverServi
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
@IQuickInputService quickInputService: IQuickInputService,
@ILabelService labelService: ILabelService,
@IShellEnvironmentService shellEnvironmentService: IShellEnvironmentService
@IShellEnvironmentService shellEnvironmentService: IShellEnvironmentService,
@IPathService pathService: IPathService
) {
super({
getAppRoot: (): string | undefined => {
@@ -34,7 +36,8 @@ export class ConfigurationResolverService extends BaseConfigurationResolverServi
getExecPath: (): string | undefined => {
return environmentService.execPath;
}
}, shellEnvironmentService.getShellEnv(), editorService, configurationService, commandService, workspaceContextService, quickInputService, labelService);
}, shellEnvironmentService.getShellEnv(), editorService, configurationService, commandService,
workspaceContextService, quickInputService, labelService, pathService);
}
}

View File

@@ -6,7 +6,8 @@
import * as assert from 'assert';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { normalize } from 'vs/base/common/path';
import { Schemas } from 'vs/base/common/network';
import { IPath, normalize } from 'vs/base/common/path';
import * as platform from 'vs/base/common/platform';
import { isObject } from 'vs/base/common/types';
import { URI as uri } from 'vs/base/common/uri';
@@ -22,6 +23,7 @@ import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { TestEditorService, TestProductService, TestQuickInputService } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
import { TestWorkbenchConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices';
@@ -66,6 +68,7 @@ suite('Configuration Resolver Service', () => {
let workspace: IWorkspaceFolder;
let quickInputService: TestQuickInputService;
let labelService: MockLabelService;
let pathService: MockPathService;
setup(() => {
mockCommandService = new MockCommandService();
@@ -73,9 +76,10 @@ suite('Configuration Resolver Service', () => {
quickInputService = new TestQuickInputService();
environmentService = new MockWorkbenchEnvironmentService(envVariables);
labelService = new MockLabelService();
pathService = new MockPathService();
containingWorkspace = testWorkspace(uri.parse('file:///VSCode/workspaceLocation'));
workspace = containingWorkspace.folders[0];
configurationResolverService = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), editorService, new MockInputsConfigurationService(), mockCommandService, new TestContextService(containingWorkspace), quickInputService, labelService);
configurationResolverService = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), editorService, new MockInputsConfigurationService(), mockCommandService, new TestContextService(containingWorkspace), quickInputService, labelService, pathService);
});
teardown(() => {
@@ -214,7 +218,7 @@ suite('Configuration Resolver Service', () => {
}
});
let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService);
let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService);
assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} xyz'), 'abc foo xyz');
});
@@ -225,7 +229,7 @@ suite('Configuration Resolver Service', () => {
}
});
let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService);
let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService);
assert.strictEqual(await service.resolveAsync(undefined, 'abc ${config:editor.fontFamily} xyz'), 'abc foo xyz');
});
@@ -242,7 +246,7 @@ suite('Configuration Resolver Service', () => {
}
});
let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService);
let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService);
assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} xyz'), 'abc foo bar xyz');
});
@@ -259,7 +263,7 @@ suite('Configuration Resolver Service', () => {
}
});
let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService);
let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService);
if (platform.isWindows) {
assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} ${workspaceFolder} ${env:key1} xyz'), 'abc foo \\VSCode\\workspaceLocation Value for key1 xyz');
} else {
@@ -280,7 +284,7 @@ suite('Configuration Resolver Service', () => {
}
});
let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService);
let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService);
if (platform.isWindows) {
assert.strictEqual(await service.resolveAsync(workspace, '${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} ${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), 'foo bar \\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for key1 - Value for key2');
} else {
@@ -314,7 +318,7 @@ suite('Configuration Resolver Service', () => {
}
});
let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService);
let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService);
assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} ${config:editor.lineNumbers} ${config:editor.insertSpaces} xyz'), 'abc foo 123 false xyz');
});
@@ -324,7 +328,7 @@ suite('Configuration Resolver Service', () => {
editor: {}
});
let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService);
let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService);
assert.strictEqual(await service.resolveAsync(workspace, 'abc ${unknownVariable} xyz'), 'abc ${unknownVariable} xyz');
assert.strictEqual(await service.resolveAsync(workspace, 'abc ${env:unknownVariable} xyz'), 'abc xyz');
});
@@ -337,7 +341,7 @@ suite('Configuration Resolver Service', () => {
}
});
let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService);
let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService);
assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${env} xyz'));
assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${env:} xyz'));
@@ -675,6 +679,21 @@ class MockLabelService implements ILabelService {
onDidChangeFormatters: Event<IFormatterChangeEvent> = new Emitter<IFormatterChangeEvent>().event;
}
class MockPathService implements IPathService {
_serviceBrand: undefined;
get path(): Promise<IPath> {
throw new Error('Property not implemented');
}
defaultUriScheme: string = Schemas.file;
fileURI(path: string): Promise<uri> {
throw new Error('Method not implemented.');
}
userHome(options?: { preferLocal: boolean; }): Promise<uri> {
throw new Error('Method not implemented.');
}
resolvedUserHome: uri | undefined;
}
class MockInputsConfigurationService extends TestConfigurationService {
public override getValue(arg1?: any, arg2?: any): any {
let configuration;

View File

@@ -25,6 +25,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { stripIcons } from 'vs/base/common/iconLabels';
import { coalesce } from 'vs/base/common/arrays';
import { Emitter } from 'vs/base/common/event';
export class ContextMenuService extends Disposable implements IContextMenuService {
@@ -32,6 +33,9 @@ export class ContextMenuService extends Disposable implements IContextMenuServic
private impl: IContextMenuService;
private readonly _onDidShowContextMenu = this._register(new Emitter<void>());
readonly onDidShowContextMenu = this._onDidShowContextMenu.event;
constructor(
@INotificationService notificationService: INotificationService,
@ITelemetryService telemetryService: ITelemetryService,
@@ -56,6 +60,7 @@ export class ContextMenuService extends Disposable implements IContextMenuServic
showContextMenu(delegate: IContextMenuDelegate): void {
this.impl.showContextMenu(delegate);
this._onDidShowContextMenu.fire();
}
}
@@ -63,6 +68,8 @@ class NativeContextMenuService extends Disposable implements IContextMenuService
declare readonly _serviceBrand: undefined;
readonly onDidShowContextMenu = new Emitter<void>().event;
constructor(
@INotificationService private readonly notificationService: INotificationService,
@ITelemetryService private readonly telemetryService: ITelemetryService,

View File

@@ -38,9 +38,11 @@ export class BrowserCredentialsService extends Disposable implements ICredential
this._onDidChangePassword.fire({ service, account });
}
deletePassword(service: string, account: string): Promise<boolean> {
const didDelete = this.credentialsProvider.deletePassword(service, account);
this._onDidChangePassword.fire({ service, account });
async deletePassword(service: string, account: string): Promise<boolean> {
const didDelete = await this.credentialsProvider.deletePassword(service, account);
if (didDelete) {
this._onDidChangePassword.fire({ service, account });
}
return didDelete;
}

View File

@@ -75,12 +75,9 @@ class DecorationRule {
const { color, letter } = data;
// label
createCSSRule(`.${this.itemColorClassName}`, `color: ${getColor(theme, color)};`, element);
// icon
if (ThemeIcon.isThemeIcon(letter)) {
this._createIconCSSRule(letter, color, element, theme);
}
// letter
else if (letter) {
} else if (letter) {
createCSSRule(`.${this.itemBadgeClassName}::after`, `content: "${letter}"; color: ${getColor(theme, color)};`, element);
}
}
@@ -93,6 +90,7 @@ class DecorationRule {
// icon (only show first)
const icon = data.find(d => ThemeIcon.isThemeIcon(d.letter))?.letter as ThemeIcon | undefined;
if (icon) {
// todo@jrieken this is fishy. icons should be just like letter and not mute bubble badge
this._createIconCSSRule(icon, color, element, theme);
} else {
// badge
@@ -112,14 +110,26 @@ class DecorationRule {
}
private _createIconCSSRule(icon: ThemeIcon, color: string | undefined, element: HTMLStyleElement, theme: IColorTheme) {
const codicon = iconRegistry.get(icon.id);
const index = icon.id.lastIndexOf('~');
const id = index < 0 ? icon.id : icon.id.substr(0, index);
const modifier = index < 0 ? '' : icon.id.substr(index + 1);
const codicon = iconRegistry.get(id);
if (!codicon || !('fontCharacter' in codicon.definition)) {
return;
}
const charCode = parseInt(codicon.definition.fontCharacter.substr(1), 16);
createCSSRule(
`.${this.iconBadgeClassName}::after`,
`content: "${String.fromCharCode(charCode)}"; color: ${getColor(theme, color)}; font-family: codicon; font-size: 16px; padding-right: 14px; font-weight: normal`,
`content: "${String.fromCharCode(charCode)}";
color: ${getColor(theme, color)};
font-family: codicon;
font-size: 16px;
padding-right: 14px;
font-weight: normal;
${modifier === 'spin' ? 'animation: codicon-spin 1.5s steps(30) infinite' : ''};
`,
element
);
}

View File

@@ -450,8 +450,12 @@ export class SimpleFileDialog {
private constructFullUserPath(): string {
const currentFolderPath = this.pathFromUri(this.currentFolder);
if (equalsIgnoreCase(this.filePickBox.value.substr(0, this.userEnteredPathSegment.length), this.userEnteredPathSegment) && equalsIgnoreCase(this.filePickBox.value.substr(0, currentFolderPath.length), currentFolderPath)) {
return currentFolderPath;
if (equalsIgnoreCase(this.filePickBox.value.substr(0, this.userEnteredPathSegment.length), this.userEnteredPathSegment)) {
if (equalsIgnoreCase(this.filePickBox.value.substr(0, currentFolderPath.length), currentFolderPath)) {
return currentFolderPath;
} else {
return this.userEnteredPathSegment;
}
} else {
return this.pathAppend(this.currentFolder, this.userEnteredPathSegment);
}

View File

@@ -6,26 +6,30 @@
import Severity from 'vs/base/common/severity';
import { Disposable } from 'vs/base/common/lifecycle';
import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IInput, IInputResult, IShowResult } from 'vs/platform/dialogs/common/dialogs';
import { DialogsModel, IDialogsModel } from 'vs/workbench/common/dialogs';
import { DialogsModel } from 'vs/workbench/common/dialogs';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
export class DialogService extends Disposable implements IDialogService {
_serviceBrand: undefined;
readonly model: IDialogsModel = this._register(new DialogsModel());
declare readonly _serviceBrand: undefined;
readonly model = this._register(new DialogsModel());
async confirm(confirmation: IConfirmation): Promise<IConfirmationResult> {
const handle = this.model.show({ confirmArgs: { confirmation } });
return await handle.result as IConfirmationResult;
}
async show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise<IShowResult> {
async show(severity: Severity, message: string, buttons?: string[], options?: IDialogOptions): Promise<IShowResult> {
const handle = this.model.show({ showArgs: { severity, message, buttons, options } });
return await handle.result as IShowResult;
}
async input(severity: Severity, message: string, buttons: string[], inputs: IInput[], options?: IDialogOptions): Promise<IInputResult> {
const handle = this.model.show({ inputArgs: { severity, message, buttons, inputs, options } });
return await handle.result as IInputResult;
}

View File

@@ -8,12 +8,13 @@ import { CodeEditorServiceImpl } from 'vs/editor/browser/services/codeEditorServ
import { ScrollType } from 'vs/editor/common/editorCommon';
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IWorkbenchEditorConfiguration, TextEditorOptions } from 'vs/workbench/common/editor';
import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { isEqual } from 'vs/base/common/resources';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { applyTextEditorOptions } from 'vs/workbench/common/editor/editorOptions';
export class CodeEditorService extends CodeEditorServiceImpl {
@@ -60,8 +61,7 @@ export class CodeEditorService extends CodeEditorServiceImpl {
) {
const targetEditor = activeTextEditorControl.getModifiedEditor();
const textOptions = TextEditorOptions.create(input.options);
textOptions.apply(targetEditor, ScrollType.Smooth);
applyTextEditorOptions(input.options, targetEditor, ScrollType.Smooth);
return targetEditor;
}

View File

@@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as glob from 'vs/base/common/glob';
@@ -9,15 +9,14 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'
import { basename, extname, isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { EditorActivation, EditorOverride, IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { EditorResourceAccessor, IEditorInput, IEditorInputWithOptions, IEditorInputWithOptionsAndGroup } from 'vs/workbench/common/editor';
import { EditorActivation, EditorOverride, IEditorOptions } from 'vs/platform/editor/common/editor';
import { EditorResourceAccessor, IEditorInput, IEditorInputWithOptions, IEditorInputWithOptionsAndGroup, SideBySideEditor } from 'vs/workbench/common/editor';
import { IEditorGroup, IEditorGroupsService, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService';
import { Schemas } from 'vs/base/common/network';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { ContributedEditorInfo, ContributedEditorPriority, ContributionPointOptions, DEFAULT_EDITOR_ASSOCIATION, DiffEditorInputFactoryFunction, EditorAssociation, EditorAssociations, EditorInputFactoryFunction, editorsAssociationsSettingId, globMatchesResource, IEditorOverrideService, priorityToRank } from 'vs/workbench/services/editor/common/editorOverrideService';
import { ContributedEditorInfo, ContributedEditorPriority, RegisteredEditorOptions, DEFAULT_EDITOR_ASSOCIATION, DiffEditorInputFactoryFunction, EditorAssociation, EditorAssociations, EditorInputFactoryFunction, editorsAssociationsSettingId, globMatchesResource, IEditorOverrideService, priorityToRank } from 'vs/workbench/services/editor/common/editorOverrideService';
import { IKeyMods, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { localize } from 'vs/nls';
import { Codicon } from 'vs/base/common/codicons';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
@@ -28,21 +27,26 @@ interface IContributedEditorInput extends IEditorInput {
viewType?: string;
}
interface ContributionPoint {
interface RegisteredEditor {
globPattern: string | glob.IRelativePattern,
editorInfo: ContributedEditorInfo,
options?: ContributionPointOptions,
options?: RegisteredEditorOptions,
createEditorInput: EditorInputFactoryFunction
createDiffEditorInput?: DiffEditorInputFactoryFunction
}
type ContributionPoints = Array<ContributionPoint>;
type RegisteredEditors = Array<RegisteredEditor>;
export class EditorOverrideService extends Disposable implements IEditorOverrideService {
readonly _serviceBrand: undefined;
private _contributionPoints: Map<string | glob.IRelativePattern, ContributionPoints> = new Map<string | glob.IRelativePattern, ContributionPoints>();
// Constants
private static readonly configureDefaultID = 'promptOpenWith.configureDefault';
private static readonly overrideCacheStorageID = 'editorOverrideService.cache';
private static readonly conflictingDefaultsStorageID = 'editorOverrideService.conflictingDefaults';
// Data Stores
private _editors: Map<string | glob.IRelativePattern, RegisteredEditors> = new Map<string | glob.IRelativePattern, RegisteredEditors>();
// private cache: Set<string> | undefined; {{SQL CARBON EDIT}} Remove unused
constructor(
@@ -58,10 +62,11 @@ export class EditorOverrideService extends Disposable implements IEditorOverride
// Read in the cache on statup
// this.cache = new Set<string>(JSON.parse(this.storageService.get(EditorOverrideService.overrideCacheStorageID, StorageScope.GLOBAL, JSON.stringify([])))); {{SQL CARBON EDIT}} Remove unused
this.storageService.remove(EditorOverrideService.overrideCacheStorageID, StorageScope.GLOBAL);
this.convertOldAssociationFormat();
this._register(this.storageService.onWillSaveState(() => {
// We want to store the glob patterns we would activate on, this allows us to know if we need to await the ext host on startup for opening a resource
this.cacheContributionPoints();
this.cacheEditors();
}));
// When extensions have registered we no longer need the cache
@@ -70,9 +75,14 @@ export class EditorOverrideService extends Disposable implements IEditorOverride
this.cache = undefined;
});
*/
// When the setting changes we want to ensure that it is properly converted
this._register(this.configurationService.onDidChangeConfiguration(() => {
this.convertOldAssociationFormat();
}));
}
async resolveEditorOverride(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): Promise<IEditorInputWithOptionsAndGroup | undefined> {
async resolveEditorOverride(editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup): Promise<IEditorInputWithOptionsAndGroup | undefined> {
// If it was an override before we await for the extensions to activate and then proceed with overriding or else they won't be registered
//if (this.cache && editor.resource && this.resourceMatchesCache(editor.resource)) { // {{SQL CARBON EDIT}} Always wait for extensions so that our language-based overrides (SQL/Notebooks) will always have those registered
await this.extensionService.whenInstalledExtensionsRegistered();
@@ -83,11 +93,8 @@ export class EditorOverrideService extends Disposable implements IEditorOverride
}
// Always ensure inputs have populated resource fields
if (editor instanceof DiffEditorInput) {
if ((!editor.modifiedInput.resource || !editor.originalInput.resource)) {
return { editor, options, group };
}
} else if (!editor.resource) {
const resource = EditorResourceAccessor.getCanonicalUri(editor, { supportSideBySide: SideBySideEditor.PRIMARY });
if (!resource) {
return { editor, options, group };
}
@@ -107,29 +114,28 @@ export class EditorOverrideService extends Disposable implements IEditorOverride
group = picked[1] ?? group;
}
// Resolved the override as much as possible, now find a given contribution
const { contributionPoint, conflictingDefault } = this.getContributionPoint(editor instanceof DiffEditorInput ? editor.modifiedInput.resource! : editor.resource!, override);
const selectedContribution = contributionPoint;
if (!selectedContribution) {
// Resolved the override as much as possible, now find a given editor
const { editor: matchededEditor, conflictingDefault } = this.getEditor(resource, override);
const selectedEditor = matchededEditor;
if (!selectedEditor) {
return { editor, options, group };
}
const handlesDiff = typeof selectedContribution.options?.canHandleDiff === 'function' ? selectedContribution.options.canHandleDiff() : selectedContribution.options?.canHandleDiff;
const handlesDiff = typeof selectedEditor.options?.canHandleDiff === 'function' ? selectedEditor.options.canHandleDiff() : selectedEditor.options?.canHandleDiff;
if (editor instanceof DiffEditorInput && handlesDiff === false) {
return { editor, options, group };
}
// If it's the currently active editor we shouldn't do anything
if (selectedContribution.editorInfo.describes(editor)) {
if (selectedEditor.editorInfo.describes(editor)) {
return undefined; // {{SQL CARBON EDIT}} Strict nulls
}
const input = await this.doOverrideEditorInput(editor, options, group, selectedContribution);
const input = await this.doOverrideEditorInput(resource, editor, options, group, selectedEditor);
if (conflictingDefault && input) {
// Wait one second to give the user ample time to see the current editor then ask them to configure a default
setTimeout(() => {
this.doHandleConflictingDefaults(selectedContribution.editorInfo.label, input.editor, input.options ?? options, group);
}, 1200);
// Show the conflicting default dialog
await this.doHandleConflictingDefaults(resource, selectedEditor.editorInfo.label, input.editor, input.options ?? options, group);
}
// Add the group as we might've changed it with the quickpick
if (input) {
this.sendOverrideTelemetry(input.editor);
@@ -138,17 +144,19 @@ export class EditorOverrideService extends Disposable implements IEditorOverride
return input;
}
registerContributionPoint(
registerEditor(
globPattern: string | glob.IRelativePattern,
editorInfo: ContributedEditorInfo,
options: ContributionPointOptions,
options: RegisteredEditorOptions,
createEditorInput: EditorInputFactoryFunction,
createDiffEditorInput?: DiffEditorInputFactoryFunction
): IDisposable {
if (this._contributionPoints.get(globPattern) === undefined) {
this._contributionPoints.set(globPattern, []);
let registeredEditor = this._editors.get(globPattern);
if (registeredEditor === undefined) {
registeredEditor = [];
this._editors.set(globPattern, registeredEditor);
}
const remove = insert(this._contributionPoints.get(globPattern)!, {
const remove = insert(registeredEditor, {
globPattern,
editorInfo,
options,
@@ -158,91 +166,129 @@ export class EditorOverrideService extends Disposable implements IEditorOverride
return toDisposable(() => remove());
}
hasContributionPoint(schemeOrGlob: string): boolean {
return this._contributionPoints.has(schemeOrGlob);
getAssociationsForResource(resource: URI): EditorAssociations {
const associations = this.getAllUserAssociations();
const matchingAssociations = associations.filter(association => association.filenamePattern && globMatchesResource(association.filenamePattern, resource));
const allEditors: RegisteredEditors = this._registeredEditors;
// Ensure that the settings are valid editors
return matchingAssociations.filter(association => allEditors.find(c => c.editorInfo.id === association.viewType));
}
getAssociationsForResource(resource: URI): EditorAssociations {
const rawAssociations = this.configurationService.getValue<EditorAssociations>(editorsAssociationsSettingId) || [];
return rawAssociations.filter(association => association.filenamePattern && globMatchesResource(association.filenamePattern, resource));
private convertOldAssociationFormat(): void {
const rawAssociations = this.configurationService.getValue<EditorAssociations | { [fileNamePattern: string]: string }>(editorsAssociationsSettingId) || [];
// If it's not an array, then it's the new format
if (!Array.isArray(rawAssociations)) {
return;
}
let newSettingObject = Object.create(null);
// Make the correctly formatted object from the array and then set that object
for (const association of rawAssociations) {
if (association.filenamePattern) {
newSettingObject[association.filenamePattern] = association.viewType;
}
}
this.configurationService.updateValue(editorsAssociationsSettingId, newSettingObject);
}
private getAllUserAssociations(): EditorAssociations {
const rawAssociations = this.configurationService.getValue<{ [fileNamePattern: string]: string }>(editorsAssociationsSettingId) || [];
let associations = [];
for (const [key, value] of Object.entries(rawAssociations)) {
const association: EditorAssociation = {
filenamePattern: key,
viewType: value
};
associations.push(association);
}
return associations;
}
/**
* Returns all editors as an array. Possible to contain duplicates
*/
private get _registeredEditors(): RegisteredEditors {
return flatten(Array.from(this._editors.values()));
}
updateUserAssociations(globPattern: string, editorID: string): void {
const newAssociation: EditorAssociation = { viewType: editorID, filenamePattern: globPattern };
const currentAssociations = [...this.configurationService.getValue<EditorAssociations>(editorsAssociationsSettingId)];
// First try updating existing association
for (let i = 0; i < currentAssociations.length; ++i) {
const existing = currentAssociations[i];
if (existing.filenamePattern === newAssociation.filenamePattern) {
currentAssociations.splice(i, 1, newAssociation);
this.configurationService.updateValue(editorsAssociationsSettingId, currentAssociations);
return;
const currentAssociations = this.getAllUserAssociations();
const newSettingObject = Object.create(null);
// Form the new setting object including the newest associations
for (const association of [...currentAssociations, newAssociation]) {
if (association.filenamePattern) {
newSettingObject[association.filenamePattern] = association.viewType;
}
}
// Otherwise, create a new one
currentAssociations.unshift(newAssociation);
this.configurationService.updateValue(editorsAssociationsSettingId, currentAssociations);
this.configurationService.updateValue(editorsAssociationsSettingId, newSettingObject);
}
private findMatchingContributions(resource: URI): ContributionPoint[] {
let contributions: ContributionPoint[] = [];
private findMatchingEditors(resource: URI): RegisteredEditor[] {
// The user setting should be respected even if the editor doesn't specify that resource in package.json
const userSettings = this.getAssociationsForResource(resource);
let matchingEditors: RegisteredEditor[] = [];
// Then all glob patterns
for (const key of this._contributionPoints.keys()) {
const contributionPoints = this._contributionPoints.get(key)!;
for (const contributionPoint of contributionPoints) {
if (globMatchesResource(key, resource)) {
contributions.push(contributionPoint);
for (const [key, editors] of this._editors) {
for (const editor of editors) {
const foundInSettings = userSettings.find(setting => setting.viewType === editor.editorInfo.id);
if (foundInSettings || globMatchesResource(key, resource)) {
matchingEditors.push(editor);
}
}
}
// Return the contributions sorted by their priority
return contributions.sort((a, b) => priorityToRank(b.editorInfo.priority) - priorityToRank(a.editorInfo.priority));
// Return the editors sorted by their priority
return matchingEditors.sort((a, b) => priorityToRank(b.editorInfo.priority) - priorityToRank(a.editorInfo.priority));
}
public getEditorIds(resource: URI): string[] {
const editors = this.findMatchingEditors(resource);
return editors.map(editor => editor.editorInfo.id);
}
/**
* Given a resource and an override selects the best possible contribution point
* @returns The contribution point and whether there was another default which conflicted with it
* Given a resource and an override selects the best possible editor
* @returns The editor and whether there was another default which conflicted with it
*/
private getContributionPoint(resource: URI, override: string | undefined): { contributionPoint: ContributionPoint | undefined, conflictingDefault: boolean } {
const findMatchingContribPoint = (contributionPoints: ContributionPoints, viewType: string) => {
return contributionPoints.find((contribPoint) => {
if (contribPoint.options && contribPoint.options.canSupportResource !== undefined) {
return contribPoint.editorInfo.id === viewType && contribPoint.options.canSupportResource(resource);
private getEditor(resource: URI, override: string | undefined): { editor: RegisteredEditor | undefined, conflictingDefault: boolean } {
const findMatchingEditor = (editors: RegisteredEditors, viewType: string) => {
return editors.find((editor) => {
if (editor.options && editor.options.canSupportResource !== undefined) {
return editor.editorInfo.id === viewType && editor.options.canSupportResource(resource);
}
return contribPoint.editorInfo.id === viewType;
return editor.editorInfo.id === viewType;
});
};
if (override) {
// Specific overried passed in doesn't have to match the reosurce, it can be anything
const contributionPoints = flatten(Array.from(this._contributionPoints.values()));
// Specific overried passed in doesn't have to match the resource, it can be anything
const registeredEditors = this._registeredEditors;
return {
contributionPoint: findMatchingContribPoint(contributionPoints, override),
editor: findMatchingEditor(registeredEditors, override),
conflictingDefault: false
};
}
let contributionPoints = this.findMatchingContributions(resource);
let editors = this.findMatchingEditors(resource);
const associationsFromSetting = this.getAssociationsForResource(resource);
// We only want built-in+ if no user defined setting is found, else we won't override
const possibleContributionPoints = contributionPoints.filter(contribPoint => priorityToRank(contribPoint.editorInfo.priority) >= priorityToRank(ContributedEditorPriority.builtin) && contribPoint.editorInfo.id !== DEFAULT_EDITOR_ASSOCIATION.id);
// If the user has a setting we use that, else choose the highest priority editor that is built-in+
const selectedViewType = associationsFromSetting[0]?.viewType || possibleContributionPoints[0]?.editorInfo.id;
const possibleEditors = editors.filter(editor => priorityToRank(editor.editorInfo.priority) >= priorityToRank(ContributedEditorPriority.builtin) && editor.editorInfo.id !== DEFAULT_EDITOR_ASSOCIATION.id);
// If the editor is exclusive we use that, else use the user setting, else use the built-in+ editor
const selectedViewType = possibleEditors[0]?.editorInfo.priority === ContributedEditorPriority.exclusive ?
possibleEditors[0]?.editorInfo.id :
associationsFromSetting[0]?.viewType || possibleEditors[0]?.editorInfo.id;
let conflictingDefault = false;
if (associationsFromSetting.length === 0 && possibleContributionPoints.length > 1) {
if (associationsFromSetting.length === 0 && possibleEditors.length > 1) {
conflictingDefault = true;
}
return {
contributionPoint: findMatchingContribPoint(contributionPoints, selectedViewType),
editor: findMatchingEditor(editors, selectedViewType),
conflictingDefault
};
}
private async doOverrideEditorInput(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup, selectedContribution: ContributionPoint): Promise<IEditorInputWithOptions | undefined> {
private async doOverrideEditorInput(resource: URI, editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup, selectedEditor: RegisteredEditor): Promise<IEditorInputWithOptions | undefined> {
// If no activation option is provided, populate it.
if (options && typeof options.activation === 'undefined') {
@@ -251,25 +297,22 @@ export class EditorOverrideService extends Disposable implements IEditorOverride
// If it's a diff editor we trigger the create diff editor input
if (editor instanceof DiffEditorInput) {
if (!selectedContribution.createDiffEditorInput) {
if (!selectedEditor.createDiffEditorInput) {
return undefined; // {{SQL CARBON EDIT}} Strict nulls
}
const inputWithOptions = selectedContribution.createDiffEditorInput(editor, options, group);
const inputWithOptions = selectedEditor.createDiffEditorInput(editor, options, group);
return inputWithOptions;
}
// We only call this function from one place and there we do the check to ensure editor.resource is not undefined
const resource = editor.resource!;
// Respect options passed back
const inputWithOptions = selectedContribution.createEditorInput(resource, options, group);
const inputWithOptions = selectedEditor.createEditorInput(resource, options, group);
options = inputWithOptions.options ?? options;
const input = inputWithOptions.editor;
// If the editor states it can only be opened once per resource we must close all existing ones first
const singleEditorPerResource = typeof selectedContribution.options?.singlePerResource === 'function' ? selectedContribution.options.singlePerResource() : selectedContribution.options?.singlePerResource;
const singleEditorPerResource = typeof selectedEditor.options?.singlePerResource === 'function' ? selectedEditor.options.singlePerResource() : selectedEditor.options?.singlePerResource;
if (singleEditorPerResource) {
this.closeExistingEditorsForResource(resource, selectedContribution.editorInfo.id, group);
this.closeExistingEditorsForResource(resource, selectedEditor.editorInfo.id, group);
}
return { editor: input, options };
@@ -325,16 +368,27 @@ export class EditorOverrideService extends Disposable implements IEditorOverride
return out;
}
private async doHandleConflictingDefaults(editorName: string, currentEditor: IContributedEditorInput, options: IEditorOptions | undefined, group: IEditorGroup) {
const makeCurrentEditorDefault = () => {
const viewType = currentEditor.viewType;
if (viewType) {
this.updateUserAssociations(`*${extname(currentEditor.resource!)}`, viewType);
}
private async doHandleConflictingDefaults(resource: URI, editorName: string, currentEditor: IContributedEditorInput, options: IEditorOptions | undefined, group: IEditorGroup) {
type StoredChoice = {
[key: string]: string[];
};
const editors = this.findMatchingEditors(resource);
const storedChoices: StoredChoice = JSON.parse(this.storageService.get(EditorOverrideService.conflictingDefaultsStorageID, StorageScope.GLOBAL, '{}'));
const globForResource = `*${extname(resource)}`;
// Writes to the storage service that a choice has been made for the currently installed editors
const writeCurrentEditorsToStorage = () => {
storedChoices[globForResource] = [];
editors.forEach(editor => storedChoices[globForResource].push(editor.editorInfo.id));
this.storageService.store(EditorOverrideService.conflictingDefaultsStorageID, JSON.stringify(storedChoices), StorageScope.GLOBAL, StorageTarget.MACHINE);
};
// If the user has already made a choice for this editor we don't want to ask them again
if (storedChoices[globForResource] && storedChoices[globForResource].find(editorID => editorID === currentEditor.viewType)) {
return;
}
const handle = this.notificationService.prompt(Severity.Warning,
localize('editorOverride.conflictingDefaults', 'Multiple editors want to be your default editor for this resource.'),
localize('editorOverride.conflictingDefaults', 'There are multiple default editors available for the resource.'),
[{
label: localize('editorOverride.configureDefault', 'Configure Default'),
run: async () => {
@@ -359,23 +413,25 @@ export class EditorOverrideService extends Disposable implements IEditorOverride
},
{
label: localize('editorOverride.keepDefault', 'Keep {0}', editorName),
run: makeCurrentEditorDefault
run: writeCurrentEditorsToStorage
}
]);
// If the user pressed X we assume they want to keep the current editor as default
const onCloseListener = handle.onDidClose(() => {
makeCurrentEditorDefault();
writeCurrentEditorsToStorage();
onCloseListener.dispose();
});
}
private mapContributionsToQuickPickEntry(resource: URI, group: IEditorGroup, alwaysUpdateSetting?: boolean) {
private mapEditorsToQuickPickEntry(resource: URI, group: IEditorGroup, showDefaultPicker?: boolean) {
const currentEditor = firstOrDefault(group.findEditors(resource));
// If untitled, we want all contribution points
let contributionPoints = resource.scheme === Schemas.untitled ? distinct(flatten(Array.from(this._contributionPoints.values())), (contrib) => contrib.editorInfo.id) : this.findMatchingContributions(resource);
// If untitled, we want all registered editors
let registeredEditors = resource.scheme === Schemas.untitled ? this._registeredEditors : this.findMatchingEditors(resource);
// We don't want duplicate Id entries
registeredEditors = distinct(registeredEditors, c => c.editorInfo.id);
const defaultSetting = this.getAssociationsForResource(resource)[0]?.viewType;
// Not the most efficient way to do this, but we want to ensure the text editor is at the top of the quickpick
contributionPoints = contributionPoints.sort((a, b) => {
registeredEditors = registeredEditors.sort((a, b) => {
if (a.editorInfo.id === DEFAULT_EDITOR_ASSOCIATION.id) {
return -1;
} else if (b.editorInfo.id === DEFAULT_EDITOR_ASSOCIATION.id) {
@@ -384,37 +440,43 @@ export class EditorOverrideService extends Disposable implements IEditorOverride
return priorityToRank(b.editorInfo.priority) - priorityToRank(a.editorInfo.priority);
}
});
const contribGroups: { defaults: Array<IQuickPickSeparator | IQuickPickItem>, optional: Array<IQuickPickSeparator | IQuickPickItem> } = {
defaults: [
{ type: 'separator', label: localize('editorOverride.picker.default', 'Defaults') }
],
optional: [
{ type: 'separator', label: localize('editorOverride.picker.optional', 'Optional') }
],
};
// Get the matching contribtuions and call resolve whether they're active for the picker
contributionPoints.forEach(contribPoint => {
const isActive = currentEditor ? contribPoint.editorInfo.describes(currentEditor) : false;
const quickPickEntry = {
id: contribPoint.editorInfo.id,
label: contribPoint.editorInfo.label,
description: isActive ? localize('promptOpenWith.currentlyActive', "Currently Active") : undefined,
detail: contribPoint.editorInfo.detail ?? contribPoint.editorInfo.priority,
buttons: alwaysUpdateSetting ? [] : [{
iconClass: Codicon.gear.classNames,
tooltip: localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", extname(resource))
}],
const quickPickEntries: Array<IQuickPickItem | IQuickPickSeparator> = [];
const currentlyActiveLabel = localize('promptOpenWith.currentlyActive', "Active");
const currentDefaultLabel = localize('promptOpenWith.currentDefault', "Default");
const currentDefaultAndActiveLabel = localize('promptOpenWith.currentDefaultAndActive', "Active and Default");
// Default order = setting -> highest priority -> text
let defaultViewType = defaultSetting;
if (!defaultViewType && registeredEditors.length > 2 && registeredEditors[1]?.editorInfo.priority !== ContributedEditorPriority.option) {
defaultViewType = registeredEditors[1]?.editorInfo.id;
}
if (!defaultViewType) {
defaultViewType = DEFAULT_EDITOR_ASSOCIATION.id;
}
// Map the editors to quickpick entries
registeredEditors.forEach(editor => {
const isActive = currentEditor ? editor.editorInfo.describes(currentEditor) : false;
const isDefault = editor.editorInfo.id === defaultViewType;
const quickPickEntry: IQuickPickItem = {
id: editor.editorInfo.id,
label: editor.editorInfo.label,
description: isActive && isDefault ? currentDefaultAndActiveLabel : isActive ? currentlyActiveLabel : isDefault ? currentDefaultLabel : undefined,
detail: editor.editorInfo.detail ?? editor.editorInfo.priority,
};
if (contribPoint.editorInfo.priority === ContributedEditorPriority.option) {
contribGroups.optional.push(quickPickEntry);
} else {
contribGroups.defaults.push(quickPickEntry);
}
quickPickEntries.push(quickPickEntry);
});
return [...contribGroups.defaults, ...contribGroups.optional];
if (!showDefaultPicker) {
const separator: IQuickPickSeparator = { type: 'separator' };
quickPickEntries.push(separator);
const configureDefaultEntry = {
id: EditorOverrideService.configureDefaultID,
label: localize('promptOpenWith.configureDefault', "Configure default editor for '{0}'...", `*${extname(resource)}`),
};
quickPickEntries.push(configureDefaultEntry);
}
return quickPickEntries;
}
private async doPickEditorOverride(editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup, alwaysUpdateSetting?: boolean): Promise<[IEditorOptions, IEditorGroup | undefined] | undefined> {
private async doPickEditorOverride(editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup, showDefaultPicker?: boolean): Promise<[IEditorOptions, IEditorGroup | undefined] | undefined> {
type EditorOverridePick = {
readonly item: IQuickPickItem;
@@ -429,12 +491,12 @@ export class EditorOverrideService extends Disposable implements IEditorOverride
}
// Text editor has the lowest priority because we
const editorOverridePicks = this.mapContributionsToQuickPickEntry(resource, group, alwaysUpdateSetting);
const editorOverridePicks = this.mapEditorsToQuickPickEntry(resource, group, showDefaultPicker);
// Create editor override picker
const editorOverridePicker = this.quickInputService.createQuickPick<IQuickPickItem>();
const placeHolderMessage = alwaysUpdateSetting ?
localize('prompOpenWith.updateDefaultPlaceHolder', "Select new default editor for '{0}'", basename(resource)) :
const placeHolderMessage = showDefaultPicker ?
localize('prompOpenWith.updateDefaultPlaceHolder', "Select new default editor for '{0}'", `*${extname(resource)}`) :
localize('promptOpenWith.placeHolder', "Select editor for '{0}'", basename(resource));
editorOverridePicker.placeholder = placeHolderMessage;
editorOverridePicker.canAcceptInBackground = true;
@@ -458,7 +520,7 @@ export class EditorOverrideService extends Disposable implements IEditorOverride
}
// If asked to always update the setting then update it even if the gear isn't clicked
if (alwaysUpdateSetting && result?.item.id) {
if (showDefaultPicker && result?.item.id) {
this.updateUserAssociations(`*${extname(resource)}`, result.item.id,);
}
@@ -487,6 +549,11 @@ export class EditorOverrideService extends Disposable implements IEditorOverride
// options and group to use accordingly
if (picked) {
// If the user selected to configure default we trigger this picker again and tell it to show the default picker
if (picked.item.id === EditorOverrideService.configureDefaultID) {
return this.doPickEditorOverride(editor, options, group, true);
}
// Figure out target group
let targetGroup: IEditorGroup | undefined;
if (picked.keyMods?.alt || picked.keyMods?.ctrlCmd) {
@@ -520,13 +587,12 @@ export class EditorOverrideService extends Disposable implements IEditorOverride
}
}
private cacheContributionPoints() {
// Create a set to store contributed glob patterns
private cacheEditors() {
// Create a set to store glob patterns
const cacheStorage: Set<string> = new Set<string>();
// Store just the relative pattern pieces without any path info
for (const globPattern of this._contributionPoints.keys()) {
const contribPoint = this._contributionPoints.get(globPattern)!;
for (const [globPattern, contribPoint] of this._editors) {
const nonOptional = !!contribPoint.find(c => c.editorInfo.priority !== ContributedEditorPriority.option && c.editorInfo.id !== DEFAULT_EDITOR_ASSOCIATION.id);
// Don't keep a cache of the optional ones as those wouldn't be opened on start anyways
if (!nonOptional) {
@@ -540,7 +606,7 @@ export class EditorOverrideService extends Disposable implements IEditorOverride
}
// Also store the users settings as those would have to activate on startup as well
const userAssociations = this.configurationService.getValue<EditorAssociations>(editorsAssociationsSettingId) || [];
const userAssociations = this.getAllUserAssociations();
for (const association of userAssociations) {
if (association.filenamePattern) {
cacheStorage.add(association.filenamePattern);

View File

@@ -4,9 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IResourceEditorInput, ITextEditorOptions, IEditorOptions, EditorActivation, EditorOverride, IResourceEditorInputIdentifier } from 'vs/platform/editor/common/editor';
import { SideBySideEditor, IEditorInput, IEditorPane, GroupIdentifier, IFileEditorInput, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorInputFactoryRegistry, EditorExtensions, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditorPane, ITextDiffEditorPane, IRevertOptions, SaveReason, EditorsOrder, isTextEditorPane, IWorkbenchEditorConfiguration, EditorResourceAccessor, IVisibleEditorPane, IEditorInputWithOptionsAndGroup } from 'vs/workbench/common/editor';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { IResourceEditorInput, IEditorOptions, EditorActivation, EditorOverride, IResourceEditorInputIdentifier, ITextEditorOptions, ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
import { SideBySideEditor, IEditorInput, IEditorPane, GroupIdentifier, IFileEditorInput, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorInputFactoryRegistry, EditorExtensions, IEditorInputWithOptions, isEditorInputWithOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditorPane, ITextDiffEditorPane, IRevertOptions, SaveReason, EditorsOrder, isTextEditorPane, IWorkbenchEditorConfiguration, EditorResourceAccessor, IVisibleEditorPane, IEditorInputWithOptionsAndGroup, EditorInputCapabilities } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput';
import { TextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
import { Registry } from 'vs/platform/registry/common/platform';
import { ResourceMap } from 'vs/base/common/map';
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
@@ -14,30 +16,30 @@ import { IFileService, FileOperationEvent, FileOperation, FileChangesEvent, File
import { Schemas } from 'vs/base/common/network';
import { Event, Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { basename, joinPath, isEqual } from 'vs/base/common/resources';
import { basename, joinPath } from 'vs/base/common/resources';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IResourceEditorInputType, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions, IOpenEditorOverrideEntry } from 'vs/workbench/services/editor/common/editorService';
import { IResourceEditorInputType, SIDE_GROUP, IResourceEditorReplacement, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions, IOpenEditorsOptions } from 'vs/workbench/services/editor/common/editorService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { coalesce, distinct, firstOrDefault, insert } from 'vs/base/common/arrays';
import { Disposable, IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
import { coalesce, distinct } from 'vs/base/common/arrays';
import { isCodeEditor, isDiffEditor, ICodeEditor, IDiffEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorGroupView, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { isUndefined, withNullAsUndefined } from 'vs/base/common/types';
import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver';
import { IEditorViewState } from 'vs/editor/common/editorCommon';
import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel';
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
import { Promises, timeout } from 'vs/base/common/async';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { indexOfPath } from 'vs/base/common/extpath';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ILogService } from 'vs/platform/log/common/log';
import { ContributedEditorPriority, DEFAULT_EDITOR_ASSOCIATION, IEditorOverrideService } from 'vs/workbench/services/editor/common/editorOverrideService';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { IWorkspaceTrustRequestService, WorkspaceTrustUriResponse } from 'vs/platform/workspace/common/workspaceTrust';
import { IHostService } from 'vs/workbench/services/host/browser/host';
type CachedEditorInput = ResourceEditorInput | IFileEditorInput | UntitledTextEditorInput;
type CachedEditorInput = TextResourceEditorInput | IFileEditorInput | UntitledTextEditorInput;
type OpenInEditorGroup = IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE;
export class EditorService extends Disposable implements EditorServiceImpl {
@@ -73,8 +75,10 @@ export class EditorService extends Disposable implements EditorServiceImpl {
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@ILogService private readonly logService: ILogService,
@IEditorOverrideService private readonly editorOverrideService: IEditorOverrideService,
@IWorkingCopyService private readonly workingCopyService: IWorkingCopyService,
@IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService,
@IHostService private readonly hostService: IHostService,
) {
super();
@@ -110,6 +114,22 @@ export class EditorService extends Disposable implements EditorServiceImpl {
this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue<IWorkbenchEditorConfiguration>())));
}
private registerDefaultOverride(): void {
this._register(this.editorOverrideService.registerEditor(
'*',
{
id: DEFAULT_EDITOR_ASSOCIATION.id,
label: DEFAULT_EDITOR_ASSOCIATION.displayName,
detail: DEFAULT_EDITOR_ASSOCIATION.providerDisplayName,
describes: (currentEditor) => this.fileEditorInputFactory.isFileEditorInput(currentEditor) && currentEditor.matches(this.activeEditor),
priority: ContributedEditorPriority.builtin
},
{},
resource => ({ editor: this.createEditorInput({ resource }) }),
diffEditor => ({ editor: diffEditor })
));
}
//#region Editor & group event handlers
private lastActiveEditor: IEditorInput | undefined = undefined;
@@ -284,7 +304,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
replacement: {
...moveResult.editor,
options: {
...moveResult.editor.options,
...(moveResult.editor as IResourceEditorInputType).options,
...optionOverrides
}
}
@@ -318,10 +338,11 @@ export class EditorService extends Disposable implements EditorServiceImpl {
}
// Handle deletes in opened editors depending on:
// - the user has not disabled the setting closeOnFileDelete
// - the file change is local
// - the input is a file that is not resolved (we need to dispose because we cannot restore otherwise since we do not have the contents)
if (this.closeOnFileDelete || !isExternal || (this.fileEditorInputFactory.isFileEditorInput(editor) && !editor.isResolved())) {
// - we close any editor when `closeOnFileDelete: true`
// - we close any editor when the delete occurred from within VSCode
// - we close any editor without resolved working copy assuming that
// this editor could not be opened after the file is gone
if (this.closeOnFileDelete || !isExternal || !this.workingCopyService.has(resource)) {
// Do NOT close any opened editor that matches the resource path (either equal or being parent) of the
// resource we move to (movedTo). Otherwise we would close a resource that has been renamed to the same
@@ -365,7 +386,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
const editors: IEditorInput[] = [];
function conditionallyAddEditor(editor: IEditorInput): void {
if (editor.isUntitled() && !options.includeUntitled) {
if (editor.hasCapability(EditorInputCapabilities.Untitled) && !options.includeUntitled) {
return;
}
@@ -485,164 +506,48 @@ export class EditorService extends Disposable implements EditorServiceImpl {
//#endregion
//#region editor overrides
private readonly openEditorOverrides: IOpenEditorOverrideHandler[] = [];
overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable {
const remove = insert(this.openEditorOverrides, handler);
return toDisposable(() => remove());
}
getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] {
const overrides: [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] = [];
// Collect contributed editor open overrides
for (const openEditorOverride of this.openEditorOverrides) {
if (typeof openEditorOverride.getEditorOverrides === 'function') {
try {
overrides.push(...openEditorOverride.getEditorOverrides(resource, options, group).map(val => [openEditorOverride, val] as [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry]));
} catch (error) {
this.logService.error(`Unexpected error getting editor overrides: ${error}`);
}
}
}
// Ensure the default one is always present
if (!overrides.some(([, entry]) => entry.id === DEFAULT_EDITOR_ASSOCIATION.id)) {
overrides.unshift(this.getDefaultEditorOverride(resource));
}
return overrides;
}
private registerDefaultOverride(): void {
this._register(this.editorOverrideService.registerContributionPoint(
'*',
{
id: DEFAULT_EDITOR_ASSOCIATION.id,
label: DEFAULT_EDITOR_ASSOCIATION.displayName,
detail: DEFAULT_EDITOR_ASSOCIATION.providerDisplayName,
describes: (currentEditor) => currentEditor.matches(this.activeEditor),
priority: ContributedEditorPriority.builtin
},
{},
resource => ({ editor: this.createEditorInput({ resource }) }),
diffEditor => ({ editor: diffEditor })
));
}
private getDefaultEditorOverride(resource: URI): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry] {
return [
{
open: (editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup) => {
const resource = EditorResourceAccessor.getOriginalUri(editor);
if (!resource) {
return undefined; // {{SQL CARBON EDIT}} Strict null
}
const fileEditorInput = this.createEditorInput({ resource, forceFile: true });
const textOptions: IEditorOptions | ITextEditorOptions = { ...options, override: EditorOverride.DISABLED };
return {
override: (async () => {
// Try to replace existing editors for resource
const existingEditor = firstOrDefault(this.findEditors(resource, group));
if (existingEditor && !fileEditorInput.matches(existingEditor)) {
await this.replaceEditors([{
editor: existingEditor,
replacement: fileEditorInput,
forceReplaceDirty: existingEditor.resource?.scheme === Schemas.untitled,
options: options ? EditorOptions.create(options) : undefined,
}], group);
}
return this.openEditor(fileEditorInput, textOptions, group);
})()
};
}
},
{
id: DEFAULT_EDITOR_ASSOCIATION.id,
label: DEFAULT_EDITOR_ASSOCIATION.displayName,
detail: DEFAULT_EDITOR_ASSOCIATION.providerDisplayName,
active: this.fileEditorInputFactory.isFileEditorInput(this.activeEditor) && isEqual(this.activeEditor.resource, resource),
}
];
}
private doOverrideOpenEditor(editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup): Promise<IEditorPane | undefined> | undefined {
for (const openEditorOverride of this.openEditorOverrides) {
const result = openEditorOverride.open(editor, options, group);
const override = result?.override;
if (override) {
return override;
}
}
return undefined; // {{SQL CARBON EDIT}} Strict null
}
//#endregion
//#region openEditor()
openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise<IEditorPane | undefined>;
openEditor(editor: IEditorInput, options?: IEditorOptions, group?: OpenInEditorGroup): Promise<IEditorPane | undefined>;
openEditor(editor: IResourceEditorInput | IUntitledTextResourceEditorInput, group?: OpenInEditorGroup): Promise<ITextEditorPane | undefined>;
openEditor(editor: IResourceDiffEditorInput, group?: OpenInEditorGroup): Promise<ITextDiffEditorPane | undefined>;
async openEditor(editor: IEditorInput | IResourceEditorInputType, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): Promise<IEditorPane | undefined> {
async openEditor(editor: IEditorInput | IResourceEditorInputType, optionsOrGroup?: IEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): Promise<IEditorPane | undefined> {
const result = this.doResolveEditorOpenRequest(editor, optionsOrGroup, group);
if (result) {
const [resolvedGroup, resolvedEditor, resolvedOptions] = result;
// Override handling: pick editor or open specific
if (resolvedOptions?.override === EditorOverride.PICK || typeof resolvedOptions?.override === 'string') {
const resolvedInputWithOptionsAndGroup = await this.editorOverrideService.resolveEditorOverride(resolvedEditor, resolvedOptions, resolvedGroup);
if (!resolvedInputWithOptionsAndGroup) {
return undefined; // no editor was picked or registered for the identifier
}
return (resolvedInputWithOptionsAndGroup.group ?? resolvedGroup).openEditor(resolvedInputWithOptionsAndGroup.editor, resolvedInputWithOptionsAndGroup.options ?? resolvedOptions);
}
// Override handling: ask providers to override
// Override handling: request override from override service
if (resolvedOptions?.override !== EditorOverride.DISABLED) {
// TODO@lramos15 this will get cleaned up soon, but since the override
// service no longer uses the override flow we must check that
const resolvedInputWithOptionsAndGroup = await this.editorOverrideService.resolveEditorOverride(resolvedEditor, resolvedOptions, resolvedGroup);
// If we didn't override try the legacy overrides
if (!resolvedInputWithOptionsAndGroup || resolvedEditor.matches(resolvedInputWithOptionsAndGroup.editor)) {
const override = this.doOverrideOpenEditor(resolvedEditor, resolvedOptions, resolvedGroup);
if (override) {
return override;
}
} else {
return (resolvedInputWithOptionsAndGroup.group ?? resolvedGroup).openEditor(resolvedInputWithOptionsAndGroup.editor, resolvedInputWithOptionsAndGroup.options ?? resolvedOptions);
if (resolvedInputWithOptionsAndGroup) {
return (resolvedInputWithOptionsAndGroup.group ?? resolvedGroup).openEditor(
resolvedInputWithOptionsAndGroup.editor,
resolvedInputWithOptionsAndGroup.options ?? resolvedOptions
);
}
}
// Override handling: disabled
// Override handling: disabled or no override found
return resolvedGroup.openEditor(resolvedEditor, resolvedOptions);
}
return undefined;
}
doResolveEditorOpenRequest(editor: IEditorInput | IResourceEditorInputType, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): [IEditorGroup, EditorInput, EditorOptions | undefined] | undefined {
doResolveEditorOpenRequest(editor: IEditorInput | IResourceEditorInputType, optionsOrGroup?: IEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): [IEditorGroup, EditorInput, IEditorOptions | undefined] | undefined {
let resolvedGroup: IEditorGroup | undefined;
let candidateGroup: OpenInEditorGroup | undefined;
let typedEditor: EditorInput | undefined;
let typedOptions: EditorOptions | undefined;
let options: IEditorOptions | undefined;
// Typed Editor Support
if (editor instanceof EditorInput) {
typedEditor = editor;
typedOptions = this.toOptions(optionsOrGroup as IEditorOptions);
options = optionsOrGroup as IEditorOptions;
candidateGroup = group;
resolvedGroup = this.findTargetGroup(typedEditor, typedOptions, candidateGroup);
resolvedGroup = this.findTargetGroup(typedEditor, options, candidateGroup);
}
// Untyped Text Editor Support
@@ -650,19 +555,19 @@ export class EditorService extends Disposable implements EditorServiceImpl {
const textInput = editor as IResourceEditorInputType;
typedEditor = this.createEditorInput(textInput);
if (typedEditor) {
typedOptions = TextEditorOptions.from(textInput);
options = textInput.options;
candidateGroup = optionsOrGroup as OpenInEditorGroup;
resolvedGroup = this.findTargetGroup(typedEditor, typedOptions, candidateGroup);
resolvedGroup = this.findTargetGroup(typedEditor, options, candidateGroup);
}
}
if (typedEditor && resolvedGroup) {
if (
this.editorGroupService.activeGroup !== resolvedGroup && // only if target group is not already active
typedOptions && !typedOptions.inactive && // never for inactive editors
typedOptions.preserveFocus && // only if preserveFocus
typeof typedOptions.activation !== 'number' && // only if activation is not already defined (either true or false)
options && !options.inactive && // never for inactive editors
options.preserveFocus && // only if preserveFocus
typeof options.activation !== 'number' && // only if activation is not already defined (either true or false)
candidateGroup !== SIDE_GROUP // never for the SIDE_GROUP
) {
// If the resolved group is not the active one, we typically
@@ -674,10 +579,10 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// group is it is opened as `SIDE_GROUP` with `preserveFocus:true`.
// repeated Alt-clicking of files in the explorer always open
// into the same side group and not cause a group to be created each time.
typedOptions.overwrite({ activation: EditorActivation.ACTIVATE });
options.activation = EditorActivation.ACTIVATE;
}
return [resolvedGroup, typedEditor, typedOptions];
return [resolvedGroup, typedEditor, options];
}
return undefined;
@@ -763,26 +668,23 @@ export class EditorService extends Disposable implements EditorServiceImpl {
return neighbourGroup;
}
private toOptions(options?: IEditorOptions | ITextEditorOptions | EditorOptions): EditorOptions {
if (!options || options instanceof EditorOptions) {
return options as EditorOptions;
}
const textOptions: ITextEditorOptions = options;
if (textOptions.selection || textOptions.viewState) {
return TextEditorOptions.create(options);
}
return EditorOptions.create(options);
}
//#endregion
//#region openEditors()
openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise<IEditorPane[]>;
openEditors(editors: IResourceEditorInputType[], group?: OpenInEditorGroup): Promise<IEditorPane[]>;
async openEditors(editors: Array<IEditorInputWithOptions | IResourceEditorInputType>, group?: OpenInEditorGroup): Promise<IEditorPane[]> {
openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup, options?: IOpenEditorsOptions): Promise<IEditorPane[]>;
openEditors(editors: IResourceEditorInputType[], group?: OpenInEditorGroup, options?: IOpenEditorsOptions): Promise<IEditorPane[]>;
async openEditors(editors: Array<IEditorInputWithOptions | IResourceEditorInputType>, group?: OpenInEditorGroup, options?: IOpenEditorsOptions): Promise<IEditorPane[]> {
// Pass all editors to trust service to determine if
// we should proceed with opening the editors if we
// are asked to validate trust.
if (options?.validateTrust) {
const editorsTrusted = await this.handleWorkspaceTrust(editors);
if (!editorsTrusted) {
return [];
}
}
// Convert to typed editors and options
const typedEditors: IEditorInputWithOptions[] = editors.map(editor => {
@@ -792,7 +694,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
return {
editor: this.createEditorInput(editor),
options: TextEditorOptions.from(editor)
options: editor.options
};
});
@@ -830,7 +732,10 @@ export class EditorService extends Disposable implements EditorServiceImpl {
mapGroupToEditors.set(targetGroup, targetGroupEditors);
}
targetGroupEditors.push(editorOverride ?? { editor, options });
targetGroupEditors.push(editorOverride ?
{ editor: editorOverride.editor, options: editorOverride.options } :
{ editor, options }
);
}
}
@@ -843,13 +748,82 @@ export class EditorService extends Disposable implements EditorServiceImpl {
return coalesce(await Promises.settled(result));
}
private async handleWorkspaceTrust(editors: Array<IEditorInputWithOptions | IResourceEditorInputType>): Promise<boolean> {
const { resources, diffMode } = this.extractEditorResources(editors);
const trustResult = await this.workspaceTrustRequestService.requestOpenUris(resources);
switch (trustResult) {
case WorkspaceTrustUriResponse.Open:
return true;
case WorkspaceTrustUriResponse.OpenInNewWindow:
await this.hostService.openWindow(resources.map(resource => ({ fileUri: resource })), { forceNewWindow: true, diffMode });
return false;
case WorkspaceTrustUriResponse.Cancel:
return false;
}
}
private extractEditorResources(editors: Array<IEditorInputWithOptions | IResourceEditorInputType>): { resources: URI[], diffMode?: boolean } {
const resources = new ResourceMap<boolean>();
let diffMode = false;
for (const editor of editors) {
// Typed Editor
if (isEditorInputWithOptions(editor)) {
const resource = EditorResourceAccessor.getOriginalUri(editor.editor, { supportSideBySide: SideBySideEditor.BOTH });
if (URI.isUri(resource)) {
resources.set(resource, true);
} else if (resource) {
if (resource.primary) {
resources.set(resource.primary, true);
}
if (resource.secondary) {
resources.set(resource.secondary, true);
}
diffMode = editor.editor instanceof DiffEditorInput;
}
}
// Untyped editor
else {
const resourceDiffEditor = editor as IResourceDiffEditorInput;
if (resourceDiffEditor.originalInput && resourceDiffEditor.modifiedInput) {
const originalResourceEditor = resourceDiffEditor.originalInput as IResourceEditorInput;
if (URI.isUri(originalResourceEditor.resource)) {
resources.set(originalResourceEditor.resource, true);
}
const modifiedResourceEditor = resourceDiffEditor.modifiedInput as IResourceEditorInput;
if (URI.isUri(modifiedResourceEditor.resource)) {
resources.set(modifiedResourceEditor.resource, true);
}
diffMode = true;
}
const resourceEditor = editor as IResourceEditorInput;
if (URI.isUri(resourceEditor.resource)) {
resources.set(resourceEditor.resource, true);
}
}
}
return {
resources: Array.from(resources.keys()),
diffMode
};
}
//#endregion
//#region isOpened()
isOpened(editor: IResourceEditorInputIdentifier): boolean {
return this.editorsObserver.hasEditor({
resource: this.asCanonicalEditorResource(editor.resource),
resource: this.uriIdentityService.asCanonicalUri(editor.resource),
typeId: editor.typeId
});
}
@@ -956,7 +930,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
editor: replacementArg.editor,
replacement: replacementArg.replacement,
forceReplaceDirty: replacementArg.forceReplaceDirty,
options: this.toOptions(replacementArg.options)
options: replacementArg.options
});
} else {
const replacementArg = replaceEditorArg as IResourceEditorReplacement;
@@ -964,7 +938,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
typedEditors.push({
editor: this.createEditorInput(replacementArg.editor),
replacement: this.createEditorInput(replacementArg.replacement),
options: this.toOptions(replacementArg.replacement.options)
options: replacementArg.replacement.options
});
}
}
@@ -995,22 +969,22 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// Diff Editor Support
const resourceDiffInput = input as IResourceDiffEditorInput;
if (resourceDiffInput.leftResource && resourceDiffInput.rightResource) {
const leftInput = this.createEditorInput({ resource: resourceDiffInput.leftResource, forceFile: resourceDiffInput.forceFile });
const rightInput = this.createEditorInput({ resource: resourceDiffInput.rightResource, forceFile: resourceDiffInput.forceFile });
if (resourceDiffInput.originalInput && resourceDiffInput.modifiedInput) {
const originalInput = this.createEditorInput({ ...resourceDiffInput.originalInput, forceFile: resourceDiffInput.forceFile });
const modifiedInput = this.createEditorInput({ ...resourceDiffInput.modifiedInput, forceFile: resourceDiffInput.forceFile });
return this.instantiationService.createInstance(DiffEditorInput,
resourceDiffInput.label,
resourceDiffInput.description,
leftInput,
rightInput,
originalInput,
modifiedInput,
undefined
);
}
// Untitled file support
const untitledInput = input as IUntitledTextResourceEditorInput;
if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource && untitledInput.resource.scheme === Schemas.untitled)) {
if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource.scheme === Schemas.untitled)) {
const untitledOptions = {
mode: untitledInput.mode,
initialValue: untitledInput.contents,
@@ -1044,31 +1018,31 @@ export class EditorService extends Disposable implements EditorServiceImpl {
}) as EditorInput;
}
// Resource Editor Support
const resourceEditorInput = input as IResourceEditorInput;
if (resourceEditorInput.resource instanceof URI) {
// Text Resource Editor Support
const textResourceEditorInput = input as ITextResourceEditorInput;
if (textResourceEditorInput.resource instanceof URI) {
// Derive the label from the path if not provided explicitly
const label = resourceEditorInput.label || basename(resourceEditorInput.resource);
const label = textResourceEditorInput.label || basename(textResourceEditorInput.resource);
// We keep track of the preferred resource this input is to be created
// with but it may be different from the canonical resource (see below)
const preferredResource = resourceEditorInput.resource;
const preferredResource = textResourceEditorInput.resource;
// From this moment on, only operate on the canonical resource
// to ensure we reduce the chance of opening the same resource
// with different resource forms (e.g. path casing on Windows)
const canonicalResource = this.asCanonicalEditorResource(preferredResource);
const canonicalResource = this.uriIdentityService.asCanonicalUri(preferredResource);
return this.createOrGetCached(canonicalResource, () => {
// File
if (resourceEditorInput.forceFile || this.fileService.canHandleResource(canonicalResource)) {
return this.fileEditorInputFactory.createFileEditorInput(canonicalResource, preferredResource, resourceEditorInput.label, resourceEditorInput.description, resourceEditorInput.encoding, resourceEditorInput.mode, this.instantiationService);
if (textResourceEditorInput.forceFile || this.fileService.canHandleResource(canonicalResource)) {
return this.fileEditorInputFactory.createFileEditorInput(canonicalResource, preferredResource, textResourceEditorInput.label, textResourceEditorInput.description, textResourceEditorInput.encoding, textResourceEditorInput.mode, textResourceEditorInput.contents, this.instantiationService);
}
// Resource
return this.instantiationService.createInstance(ResourceEditorInput, canonicalResource, resourceEditorInput.label, resourceEditorInput.description, resourceEditorInput.mode);
return this.instantiationService.createInstance(TextResourceEditorInput, canonicalResource, textResourceEditorInput.label, textResourceEditorInput.description, textResourceEditorInput.mode, textResourceEditorInput.contents);
}, cachedInput => {
// Untitled
@@ -1077,23 +1051,27 @@ export class EditorService extends Disposable implements EditorServiceImpl {
}
// Files
else if (!(cachedInput instanceof ResourceEditorInput)) {
else if (!(cachedInput instanceof TextResourceEditorInput)) {
cachedInput.setPreferredResource(preferredResource);
if (resourceEditorInput.label) {
cachedInput.setPreferredName(resourceEditorInput.label);
if (textResourceEditorInput.label) {
cachedInput.setPreferredName(textResourceEditorInput.label);
}
if (resourceEditorInput.description) {
cachedInput.setPreferredDescription(resourceEditorInput.description);
if (textResourceEditorInput.description) {
cachedInput.setPreferredDescription(textResourceEditorInput.description);
}
if (resourceEditorInput.encoding) {
cachedInput.setPreferredEncoding(resourceEditorInput.encoding);
if (textResourceEditorInput.encoding) {
cachedInput.setPreferredEncoding(textResourceEditorInput.encoding);
}
if (resourceEditorInput.mode) {
cachedInput.setPreferredMode(resourceEditorInput.mode);
if (textResourceEditorInput.mode) {
cachedInput.setPreferredMode(textResourceEditorInput.mode);
}
if (typeof textResourceEditorInput.contents === 'string') {
cachedInput.setPreferredContents(textResourceEditorInput.contents);
}
}
@@ -1103,12 +1081,16 @@ export class EditorService extends Disposable implements EditorServiceImpl {
cachedInput.setName(label);
}
if (resourceEditorInput.description) {
cachedInput.setDescription(resourceEditorInput.description);
if (textResourceEditorInput.description) {
cachedInput.setDescription(textResourceEditorInput.description);
}
if (resourceEditorInput.mode) {
cachedInput.setPreferredMode(resourceEditorInput.mode);
if (textResourceEditorInput.mode) {
cachedInput.setPreferredMode(textResourceEditorInput.mode);
}
if (typeof textResourceEditorInput.contents === 'string') {
cachedInput.setPreferredContents(textResourceEditorInput.contents);
}
}
}) as EditorInput;
@@ -1117,28 +1099,6 @@ export class EditorService extends Disposable implements EditorServiceImpl {
throw new Error('Unknown input type');
}
private _modelService: IModelService | undefined = undefined;
private get modelService(): IModelService | undefined {
if (!this._modelService) {
this._modelService = this.instantiationService.invokeFunction(accessor => accessor.get(IModelService));
}
return this._modelService;
}
private asCanonicalEditorResource(resource: URI): URI {
const canonicalResource: URI = this.uriIdentityService.asCanonicalUri(resource);
// In the unlikely case that a model exists for the original resource but
// differs from the canonical resource, we print a warning as this means
// the model will not be able to be opened as editor.
if (!isEqual(resource, canonicalResource) && this.modelService?.getModel(resource)) {
this.logService.warn(`EditorService: a model exists for a resource that is not canonical: ${resource.toString(true)}`);
}
return canonicalResource;
}
private createOrGetCached(resource: URI, factoryFn: () => CachedEditorInput, cachedFn?: (input: CachedEditorInput) => void): CachedEditorInput {
// Return early if already cached
@@ -1185,7 +1145,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
editorsToSaveSequentially.push(...uniqueEditors);
} else {
for (const { groupId, editor } of uniqueEditors) {
if (editor.isUntitled()) {
if (editor.hasCapability(EditorInputCapabilities.Untitled)) {
editorsToSaveSequentially.push({ groupId, editor });
} else {
editorsToSaveParallel.push({ groupId, editor });
@@ -1214,11 +1174,11 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// Preserve view state by opening the editor first if the editor
// is untitled or we "Save As". This also allows the user to review
// the contents of the editor before making a decision.
let viewState: IEditorViewState | undefined = undefined;
const editorPane = await this.openEditor(editor, undefined, groupId);
if (isTextEditorPane(editorPane)) {
viewState = editorPane.getViewState();
}
const editorOptions: ITextEditorOptions = {
pinned: true,
viewState: isTextEditorPane(editorPane) ? editorPane.getViewState() : undefined
};
const result = options?.saveAs ? await editor.saveAs(groupId, options) : await editor.save(groupId, options);
saveResults.push(result);
@@ -1231,9 +1191,9 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// only selected group) if the resulting editor is different from the
// current one.
if (!result.matches(editor)) {
const targetGroups = editor.isUntitled() ? this.editorGroupService.groups.map(group => group.id) /* untitled replaces across all groups */ : [groupId];
const targetGroups = editor.hasCapability(EditorInputCapabilities.Untitled) ? this.editorGroupService.groups.map(group => group.id) /* untitled replaces across all groups */ : [groupId];
for (const group of targetGroups) {
await this.replaceEditors([{ editor, replacement: result, options: { pinned: true, viewState } }], group);
await this.replaceEditors([{ editor, replacement: result, options: editorOptions }], group);
}
}
}
@@ -1280,7 +1240,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
continue;
}
if (!options?.includeUntitled && editor.isUntitled()) {
if (!options?.includeUntitled && editor.hasCapability(EditorInputCapabilities.Untitled)) {
continue;
}
@@ -1340,10 +1300,10 @@ export class DelegatingEditorService implements IEditorService {
@IEditorService private editorService: EditorService
) { }
openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise<IEditorPane | undefined>;
openEditor(editor: IEditorInput, options?: IEditorOptions, group?: OpenInEditorGroup): Promise<IEditorPane | undefined>;
openEditor(editor: IResourceEditorInput | IUntitledTextResourceEditorInput, group?: OpenInEditorGroup): Promise<ITextEditorPane | undefined>;
openEditor(editor: IResourceDiffEditorInput, group?: OpenInEditorGroup): Promise<ITextDiffEditorPane | undefined>;
async openEditor(editor: IEditorInput | IResourceEditorInputType, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): Promise<IEditorPane | undefined> {
async openEditor(editor: IEditorInput | IResourceEditorInputType, optionsOrGroup?: IEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): Promise<IEditorPane | undefined> {
const result = this.editorService.doResolveEditorOpenRequest(editor, optionsOrGroup, group);
if (result) {
const [resolvedGroup, resolvedEditor, resolvedOptions] = result;
@@ -1372,10 +1332,10 @@ export class DelegatingEditorService implements IEditorService {
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): readonly IEditorIdentifier[] { return this.editorService.getEditors(order, options); }
openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise<IEditorPane[]>;
openEditors(editors: IResourceEditorInputType[], group?: OpenInEditorGroup): Promise<IEditorPane[]>;
openEditors(editors: Array<IEditorInputWithOptions | IResourceEditorInputType>, group?: OpenInEditorGroup): Promise<IEditorPane[]> {
return this.editorService.openEditors(editors, group);
openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup, options?: IOpenEditorsOptions): Promise<IEditorPane[]>;
openEditors(editors: IResourceEditorInputType[], group?: OpenInEditorGroup, options?: IOpenEditorsOptions): Promise<IEditorPane[]>;
openEditors(editors: Array<IEditorInputWithOptions | IResourceEditorInputType>, group?: OpenInEditorGroup, options?: IOpenEditorsOptions): Promise<IEditorPane[]> {
return this.editorService.openEditors(editors, group, options);
}
replaceEditors(editors: IResourceEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise<void>;
@@ -1392,9 +1352,6 @@ export class DelegatingEditorService implements IEditorService {
findEditors(resource: IResourceEditorInputIdentifier, group: IEditorGroup | GroupIdentifier): IEditorInput | undefined;
findEditors(arg1: URI | IResourceEditorInputIdentifier, arg2?: IEditorGroup | GroupIdentifier): readonly IEditorIdentifier[] | readonly IEditorInput[] | IEditorInput | undefined { return this.editorService.findEditors(arg1, arg2); }
overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable { return this.editorService.overrideOpenEditor(handler); }
getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined) { return this.editorService.getEditorOverrides(resource, options, group); }
createEditorInput(input: IResourceEditorInputType): IEditorInput { return this.editorService.createEditorInput(input); }
save(editors: IEditorIdentifier | IEditorIdentifier[], options?: ISaveEditorsOptions): Promise<boolean> { return this.editorService.save(editors, options); }

View File

@@ -5,8 +5,8 @@
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IEditorInput, IEditorPane, GroupIdentifier, IEditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent, IEditorMoveEvent } from 'vs/workbench/common/editor';
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditorInput, IEditorPane, GroupIdentifier, IEditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent, IEditorMoveEvent, IEditorOpenEvent } from 'vs/workbench/common/editor';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IDimension } from 'vs/editor/common/editorCommon';
import { IDisposable } from 'vs/base/common/lifecycle';
@@ -101,7 +101,7 @@ export interface ICloseAllEditorsOptions {
export interface IEditorReplacement {
editor: IEditorInput;
replacement: IEditorInput;
options?: IEditorOptions | ITextEditorOptions;
options?: IEditorOptions;
/**
* Skips asking the user for confirmation and doesn't
@@ -217,11 +217,6 @@ export interface IEditorGroupsService {
*/
readonly whenRestored: Promise<void>;
/**
* Will return `true` as soon as `whenRestored` is resolved.
*/
isRestored(): boolean;
/**
* Find out if the editor group service has UI state to restore
* from a previous session.
@@ -377,6 +372,7 @@ export const enum GroupChangeKind {
EDITOR_MOVE,
EDITOR_ACTIVE,
EDITOR_LABEL,
EDITOR_CAPABILITIES,
EDITOR_PIN,
EDITOR_STICKY,
EDITOR_DIRTY
@@ -417,6 +413,12 @@ export interface IEditorGroup {
*/
readonly onWillMoveEditor: Event<IEditorMoveEvent>;
/**
* An event that is fired when an editor is about to be opened
* in the group.
*/
readonly onWillOpenEditor: Event<IEditorOpenEvent>;
/**
* A unique identifier of this group that remains identical even if the
* group is moved to different locations.
@@ -519,7 +521,7 @@ export interface IEditorGroup {
* @returns a promise that resolves around an IEditor instance unless
* the call failed, or the editor was not opened as active editor.
*/
openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions): Promise<IEditorPane | undefined>;
openEditor(editor: IEditorInput, options?: IEditorOptions): Promise<IEditorPane | undefined>;
/**
* Opens editors in this group.
@@ -556,14 +558,14 @@ export interface IEditorGroup {
/**
* Move an editor from this group either within this group or to another group.
*/
moveEditor(editor: IEditorInput, target: IEditorGroup, options?: IEditorOptions | ITextEditorOptions): void;
moveEditor(editor: IEditorInput, target: IEditorGroup, options?: IEditorOptions): void;
/**
* Copy an editor from this group to another group.
*
* Note: It is currently not supported to show the same editor more than once in the same group.
*/
copyEditor(editor: IEditorInput, target: IEditorGroup, options?: IEditorOptions | ITextEditorOptions): void;
copyEditor(editor: IEditorInput, target: IEditorGroup, options?: IEditorOptions): void;
/**
* Close an editor from the group. This may trigger a confirmation dialog if

View File

@@ -1,11 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as glob from 'vs/base/common/glob';
import { Event } from 'vs/base/common/event';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { posix } from 'vs/base/common/path';
@@ -14,10 +12,10 @@ import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
import { Extensions as ConfigurationExtensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorExtensions, IEditorInput, IEditorInputWithOptions, IEditorInputWithOptionsAndGroup } from 'vs/workbench/common/editor';
import { IEditorInput, IEditorInputWithOptions, IEditorInputWithOptionsAndGroup } from 'vs/workbench/common/editor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
@@ -25,7 +23,7 @@ export const IEditorOverrideService = createDecorator<IEditorOverrideService>('e
//#region Editor Associations
// Static values for editor contributions
// Static values for registered editors
export type EditorAssociation = {
readonly viewType: string;
@@ -44,40 +42,14 @@ export const DEFAULT_EDITOR_ASSOCIATION: IEditorType = {
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
const editorTypeSchemaAddition: IJSONSchema = {
type: 'string',
enum: []
};
const editorAssociationsConfigurationNode: IConfigurationNode = {
...workbenchConfigurationNodeBase,
properties: {
'workbench.editorAssociations': {
type: 'array',
markdownDescription: localize('editor.editorAssociations', "Configure which editor to use for specific file types."),
items: {
type: 'object',
defaultSnippets: [{
body: {
'viewType': '$1',
'filenamePattern': '$2'
}
}],
properties: {
'viewType': {
anyOf: [
{
type: 'string',
description: localize('editor.editorAssociations.viewType', "The unique id of the editor to use."),
},
editorTypeSchemaAddition
]
},
'filenamePattern': {
type: 'string',
description: localize('editor.editorAssociations.filenamePattern', "Glob pattern specifying which files the editor should be used for."),
}
}
type: 'object',
markdownDescription: localize('editor.editorAssociations', "Configure glob patterns to editors (e.g. `\"*.hex\": \"hexEditor.hexEdit\"`). These have precedence over the default behavior."),
additionalProperties: {
type: 'string'
}
}
}
@@ -89,68 +61,6 @@ export interface IEditorType {
readonly providerDisplayName: string;
}
export interface IEditorTypesHandler {
readonly onDidChangeEditorTypes: Event<void>;
getEditorTypes(): IEditorType[];
}
export interface IEditorAssociationsRegistry {
/**
* Register handlers for editor types
*/
registerEditorTypesHandler(id: string, handler: IEditorTypesHandler): IDisposable;
}
class EditorAssociationsRegistry implements IEditorAssociationsRegistry {
private readonly editorTypesHandlers = new Map<string, IEditorTypesHandler>();
registerEditorTypesHandler(id: string, handler: IEditorTypesHandler): IDisposable {
if (this.editorTypesHandlers.has(id)) {
throw new Error(`An editor type handler with ${id} was already registered.`);
}
this.editorTypesHandlers.set(id, handler);
this.updateEditorAssociationsSchema();
const editorTypeChangeEvent = handler.onDidChangeEditorTypes(() => {
this.updateEditorAssociationsSchema();
});
return {
dispose: () => {
editorTypeChangeEvent.dispose();
this.editorTypesHandlers.delete(id);
this.updateEditorAssociationsSchema();
}
};
}
private updateEditorAssociationsSchema() {
const enumValues: string[] = [];
const enumDescriptions: string[] = [];
const editorTypes: IEditorType[] = [DEFAULT_EDITOR_ASSOCIATION];
for (const [, handler] of this.editorTypesHandlers) {
editorTypes.push(...handler.getEditorTypes());
}
for (const { id, providerDisplayName } of editorTypes) {
enumValues.push(id);
enumDescriptions.push(localize('editorAssociations.viewType.sourceDescription', "Source: {0}", providerDisplayName));
}
editorTypeSchemaAddition.enum = enumValues;
editorTypeSchemaAddition.enumDescriptions = enumDescriptions;
configurationRegistry.notifyConfigurationSchemaUpdated(editorAssociationsConfigurationNode);
}
}
Registry.add(EditorExtensions.Associations, new EditorAssociationsRegistry());
configurationRegistry.registerConfiguration(editorAssociationsConfigurationNode);
//#endregion
@@ -162,7 +72,7 @@ export enum ContributedEditorPriority {
default = 'default'
}
export type ContributionPointOptions = {
export type RegisteredEditorOptions = {
/**
* If your editor cannot be opened in multiple groups for the same resource
*/
@@ -187,9 +97,9 @@ export type ContributedEditorInfo = {
priority: ContributedEditorPriority;
};
export type EditorInputFactoryFunction = (resource: URI, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup) => IEditorInputWithOptions;
export type EditorInputFactoryFunction = (resource: URI, options: IEditorOptions | undefined, group: IEditorGroup) => IEditorInputWithOptions;
export type DiffEditorInputFactoryFunction = (diffEditorInput: DiffEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup) => IEditorInputWithOptions;
export type DiffEditorInputFactoryFunction = (diffEditorInput: DiffEditorInput, options: IEditorOptions | undefined, group: IEditorGroup) => IEditorInputWithOptions;
export interface IEditorOverrideService {
readonly _serviceBrand: undefined;
@@ -208,16 +118,16 @@ export interface IEditorOverrideService {
updateUserAssociations(globPattern: string, editorID: string): void;
/**
* Registers a specific editor contribution.
* @param globPattern The glob pattern for this contribution point
* @param editorInfo Information about the contribution point
* @param options Specific options which apply to this contribution
* Registers a specific editor.
* @param globPattern The glob pattern for this registration
* @param editorInfo Information about the registration
* @param options Specific options which apply to this registration
* @param createEditorInput The factory method for creating inputs
*/
registerContributionPoint(
registerEditor(
globPattern: string | glob.IRelativePattern,
editorInfo: ContributedEditorInfo,
options: ContributionPointOptions,
options: RegisteredEditorOptions,
createEditorInput: EditorInputFactoryFunction,
createDiffEditorInput?: DiffEditorInputFactoryFunction
): IDisposable;
@@ -229,7 +139,14 @@ export interface IEditorOverrideService {
* @param group The current group
* @returns An IEditorInputWithOptionsAndGroup if there is an available override or undefined if there is not
*/
resolveEditorOverride(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): Promise<IEditorInputWithOptionsAndGroup | undefined>;
resolveEditorOverride(editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup): Promise<IEditorInputWithOptionsAndGroup | undefined>;
/**
* Given a resource returns all the editor ids that match that resource
* @param resource The resource
* @returns A list of editor ids
*/
getEditorIds(resource: URI): string[];
}
//#endregion
@@ -260,7 +177,7 @@ export function globMatchesResource(globPattern: string | glob.IRelativePattern,
return false;
}
const matchOnPath = typeof globPattern === 'string' && globPattern.indexOf(posix.sep) >= 0;
const target = matchOnPath ? resource.path : basename(resource);
return glob.match(globPattern, target.toLowerCase());
const target = matchOnPath ? `${resource.scheme}:${resource.path}` : basename(resource);
return glob.match(typeof globPattern === 'string' ? globPattern.toLowerCase() : globPattern, target.toLowerCase());
}
//#endregion

View File

@@ -4,17 +4,16 @@
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IResourceEditorInput, IEditorOptions, ITextEditorOptions, IResourceEditorInputIdentifier } from 'vs/platform/editor/common/editor';
import { IResourceEditorInput, IEditorOptions, IResourceEditorInputIdentifier, ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
import { IEditorInput, IEditorPane, GroupIdentifier, IEditorInputWithOptions, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, ITextEditorPane, ITextDiffEditorPane, IEditorIdentifier, ISaveOptions, IRevertOptions, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent } from 'vs/workbench/common/editor';
import { Event } from 'vs/base/common/event';
import { IEditor, IDiffEditor } from 'vs/editor/common/editorCommon';
import { IEditorGroup, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
export const IEditorService = createDecorator<IEditorService>('editorService');
export type IResourceEditorInputType = IResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput;
export type IResourceEditorInputType = IResourceEditorInput | ITextResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput;
export interface IResourceEditorReplacement {
readonly editor: IResourceEditorInputType;
@@ -27,27 +26,6 @@ export type ACTIVE_GROUP_TYPE = typeof ACTIVE_GROUP;
export const SIDE_GROUP = -2;
export type SIDE_GROUP_TYPE = typeof SIDE_GROUP;
export interface IOpenEditorOverrideEntry {
readonly id: string;
readonly label: string;
readonly active: boolean;
readonly detail?: string;
}
export interface IOpenEditorOverrideHandler {
open(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined;
getEditorOverrides?(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): IOpenEditorOverrideEntry[];
}
export interface IOpenEditorOverride {
/**
* If defined, will prevent the opening of an editor and replace the resulting
* promise with the provided promise for the openEditor() call.
*/
override?: Promise<IEditorPane | undefined>;
}
export interface ISaveEditorsOptions extends ISaveOptions {
/**
@@ -73,6 +51,15 @@ export interface ISaveAllEditorsOptions extends ISaveEditorsOptions, IBaseSaveRe
export interface IRevertAllEditorsOptions extends IRevertOptions, IBaseSaveRevertAllEditorOptions { }
export interface IOpenEditorsOptions {
/**
* Whether to validate trust when opening editors
* that are potentially not inside the workspace.
*/
readonly validateTrust?: boolean;
}
export interface IEditorService {
readonly _serviceBrand: undefined;
@@ -80,14 +67,14 @@ export interface IEditorService {
/**
* Emitted when the currently active editor changes.
*
* @see `IEditorService.activeEditorPane`
* @see {@link IEditorService.activeEditorPane}
*/
readonly onDidActiveEditorChange: Event<void>;
/**
* Emitted when any of the current visible editors changes.
*
* @see `IEditorService.visibleEditorPanes`
* @see {@link IEditorService.visibleEditorPanes}
*/
readonly onDidVisibleEditorsChange: Event<void>;
@@ -100,7 +87,7 @@ export interface IEditorService {
* The currently active editor pane or `undefined` if none. The editor pane is
* the workbench container for editors of any kind.
*
* @see `IEditorService.activeEditor` for access to the active editor input
* @see {@link IEditorService.activeEditor} for access to the active editor input
*/
readonly activeEditorPane: IVisibleEditorPane | undefined;
@@ -115,7 +102,7 @@ export interface IEditorService {
* The currently active text editor control or `undefined` if there is currently no active
* editor or the active editor widget is neither a text nor a diff editor.
*
* @see `IEditorService.activeEditor`
* @see {@link IEditorService.activeEditor}
*/
readonly activeTextEditorControl: IEditor | IDiffEditor | undefined;
@@ -129,7 +116,7 @@ export interface IEditorService {
/**
* All editor panes that are currently visible across all editor groups.
*
* @see `IEditorService.visibleEditors` for access to the visible editor inputs
* @see {@link IEditorService.visibleEditors} for access to the visible editor inputs
*/
readonly visibleEditorPanes: readonly IVisibleEditorPane[];
@@ -179,8 +166,9 @@ export interface IEditorService {
* @returns the editor that opened or `undefined` if the operation failed or the editor was not
* opened to be active.
*/
openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<IEditorPane | undefined>;
openEditor(editor: IResourceEditorInput | IUntitledTextResourceEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<ITextEditorPane | undefined>;
openEditor(editor: IEditorInput, options?: IEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<IEditorPane | undefined>;
openEditor(editor: IResourceEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<IEditorPane | undefined>;
openEditor(editor: ITextResourceEditorInput | IUntitledTextResourceEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<ITextEditorPane | undefined>;
openEditor(editor: IResourceDiffEditorInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<ITextDiffEditorPane | undefined>;
/**
@@ -194,8 +182,8 @@ export interface IEditorService {
* @returns the editors that opened. The array can be empty or have less elements for editors
* that failed to open or were instructed to open as inactive.
*/
openEditors(editors: IEditorInputWithOptions[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<readonly IEditorPane[]>;
openEditors(editors: IResourceEditorInputType[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise<readonly IEditorPane[]>;
openEditors(editors: IEditorInputWithOptions[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE, options?: IOpenEditorsOptions): Promise<readonly IEditorPane[]>;
openEditors(editors: IResourceEditorInputType[], group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE, options?: IOpenEditorsOptions): Promise<readonly IEditorPane[]>;
/**
* Replaces editors in an editor group with the provided replacement.
@@ -233,18 +221,6 @@ export interface IEditorService {
findEditors(resource: URI, group: IEditorGroup | GroupIdentifier): readonly IEditorInput[];
findEditors(resource: IResourceEditorInputIdentifier, group: IEditorGroup | GroupIdentifier): IEditorInput | undefined;
/**
* Get all available editor overrides for the editor input.
*/
getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][];
/**
* Allows to override the opening of editors by installing a handler that will
* be called each time an editor is about to open allowing to override the
* operation to open a different editor.
*/
overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable;
/**
* Converts a lightweight input to a workbench editor input.
*/

View File

@@ -6,14 +6,14 @@
import * as assert from 'assert';
import { workbenchInstantiationService, registerTestEditor, TestFileEditorInput, TestEditorPart, ITestInstantiationService, TestServiceAccessor, createEditorPart } from 'vs/workbench/test/browser/workbenchTestServices';
import { GroupDirection, GroupsOrder, MergeGroupMode, GroupOrientation, GroupChangeKind, GroupLocation } from 'vs/workbench/services/editor/common/editorGroupsService';
import { EditorOptions, CloseDirection, IEditorPartOptions, EditorsOrder } from 'vs/workbench/common/editor';
import { CloseDirection, IEditorPartOptions, EditorsOrder, EditorInputCapabilities } from 'vs/workbench/common/editor';
import { URI } from 'vs/base/common/uri';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { MockScopableContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { ConfirmResult } from 'vs/platform/dialogs/common/dialogs';
suite.skip('EditorGroupsService', () => {
suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} Skip suite
const TEST_EDITOR_ID = 'MyFileEditorForEditorGroupService';
const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorGroupService';
@@ -196,10 +196,10 @@ suite.skip('EditorGroupsService', () => {
const downGroup = part.addGroup(rightGroup, GroupDirection.DOWN);
const rootGroupInput = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID);
await rootGroup.openEditor(rootGroupInput, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(rootGroupInput, { pinned: true });
const rightGroupInput = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID);
await rightGroup.openEditor(rightGroupInput, EditorOptions.create({ pinned: true }));
await rightGroup.openEditor(rightGroupInput, { pinned: true });
assert.strictEqual(part.groups.length, 3);
@@ -293,7 +293,7 @@ suite.skip('EditorGroupsService', () => {
const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID);
await rootGroup.openEditor(input, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input, { pinned: true });
const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT, { activate: true });
const downGroup = part.copyGroup(rootGroup, rightGroup, GroupDirection.DOWN);
assert.strictEqual(groupAddedCounter, 2);
@@ -323,13 +323,13 @@ suite.skip('EditorGroupsService', () => {
const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID);
const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID);
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input1, { pinned: true });
const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT);
await rightGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
await rightGroup.openEditor(input2, { pinned: true });
const downGroup = part.copyGroup(rootGroup, rightGroup, GroupDirection.DOWN);
await downGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
await downGroup.openEditor(input3, { pinned: true });
part.activateGroup(rootGroup);
@@ -347,8 +347,6 @@ suite.skip('EditorGroupsService', () => {
await part.whenReady;
await part.whenRestored;
assert.strictEqual(part.isRestored(), true);
});
test('options', async () => {
@@ -380,6 +378,7 @@ suite.skip('EditorGroupsService', () => {
let editorCloseCounter = 0;
let editorPinCounter = 0;
let editorStickyCounter = 0;
let editorCapabilitiesCounter = 0;
const editorGroupChangeListener = group.onDidGroupChange(e => {
if (e.kind === GroupChangeKind.EDITOR_OPEN) {
assert.ok(e.editor);
@@ -393,6 +392,9 @@ suite.skip('EditorGroupsService', () => {
} else if (e.kind === GroupChangeKind.EDITOR_PIN) {
assert.ok(e.editor);
editorPinCounter++;
} else if (e.kind === GroupChangeKind.EDITOR_CAPABILITIES) {
assert.ok(e.editor);
editorCapabilitiesCounter++;
} else if (e.kind === GroupChangeKind.EDITOR_STICKY) {
assert.ok(e.editor);
editorStickyCounter++;
@@ -412,8 +414,8 @@ suite.skip('EditorGroupsService', () => {
const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID);
const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID);
await group.openEditor(input, EditorOptions.create({ pinned: true }));
await group.openEditor(inputInactive, EditorOptions.create({ inactive: true }));
await group.openEditor(input, { pinned: true });
await group.openEditor(inputInactive, { inactive: true });
assert.strictEqual(group.isActive(input), true);
assert.strictEqual(group.isActive(inputInactive), false);
@@ -421,6 +423,7 @@ suite.skip('EditorGroupsService', () => {
assert.strictEqual(group.contains(inputInactive), true);
assert.strictEqual(group.isEmpty, false);
assert.strictEqual(group.count, 2);
assert.strictEqual(editorCapabilitiesCounter, 0);
assert.strictEqual(editorDidOpenCounter, 2);
assert.strictEqual(activeEditorChangeCounter, 1);
assert.strictEqual(group.getEditorByIndex(0), input);
@@ -428,6 +431,12 @@ suite.skip('EditorGroupsService', () => {
assert.strictEqual(group.getIndexOfEditor(input), 0);
assert.strictEqual(group.getIndexOfEditor(inputInactive), 1);
input.capabilities = EditorInputCapabilities.RequiresTrust;
assert.strictEqual(editorCapabilitiesCounter, 1);
inputInactive.capabilities = EditorInputCapabilities.Singleton;
assert.strictEqual(editorCapabilitiesCounter, 2);
assert.strictEqual(group.previewEditor, inputInactive);
assert.strictEqual(group.isPinned(inputInactive), false);
group.pinEditor(inputInactive);
@@ -1146,8 +1155,8 @@ suite.skip('EditorGroupsService', () => {
const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID);
const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID);
await group.openEditor(input, EditorOptions.create({ pinned: true }));
await group.openEditor(inputInactive, EditorOptions.create({ inactive: true }));
await group.openEditor(input, { pinned: true });
await group.openEditor(inputInactive, { inactive: true });
assert.strictEqual(group.stickyCount, 0);
assert.strictEqual(group.isSticky(input), false);
@@ -1208,7 +1217,7 @@ suite.skip('EditorGroupsService', () => {
const inputSticky = new TestFileEditorInput(URI.file('foo/bar/sticky'), TEST_EDITOR_INPUT_ID);
await group.openEditor(inputSticky, EditorOptions.create({ sticky: true }));
await group.openEditor(inputSticky, { sticky: true });
assert.strictEqual(group.stickyCount, 2);
assert.strictEqual(group.isSticky(input), false);
@@ -1219,7 +1228,7 @@ suite.skip('EditorGroupsService', () => {
assert.strictEqual(group.getIndexOfEditor(inputSticky), 1);
assert.strictEqual(group.getIndexOfEditor(input), 2);
await group.openEditor(input, EditorOptions.create({ sticky: true }));
await group.openEditor(input, { sticky: true });
assert.strictEqual(group.stickyCount, 3);
assert.strictEqual(group.isSticky(input), true);
@@ -1274,6 +1283,48 @@ suite.skip('EditorGroupsService', () => {
rightGroupListener.dispose();
});
test('onWillOpenEditor', async () => {
const [part] = await createPart();
const group = part.activeGroup;
assert.strictEqual(group.isEmpty, true);
const rightGroup = part.addGroup(group, GroupDirection.RIGHT);
const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID);
const secondInput = new TestFileEditorInput(URI.file('foo/bar/second'), TEST_EDITOR_INPUT_ID);
const thirdInput = new TestFileEditorInput(URI.file('foo/bar/third'), TEST_EDITOR_INPUT_ID);
let leftFiredCount = 0;
const leftGroupListener = group.onWillOpenEditor(() => {
leftFiredCount++;
});
let rightFiredCount = 0;
const rightGroupListener = rightGroup.onWillOpenEditor(() => {
rightFiredCount++;
});
await group.openEditor(input);
assert.strictEqual(leftFiredCount, 1);
assert.strictEqual(rightFiredCount, 0);
rightGroup.openEditor(secondInput);
assert.strictEqual(leftFiredCount, 1);
assert.strictEqual(rightFiredCount, 1);
group.openEditor(thirdInput);
assert.strictEqual(leftFiredCount, 2);
assert.strictEqual(rightFiredCount, 1);
// Ensure move fires the open event too
rightGroup.moveEditor(secondInput, group);
assert.strictEqual(leftFiredCount, 3);
assert.strictEqual(rightFiredCount, 1);
leftGroupListener.dispose();
rightGroupListener.dispose();
});
test('copyEditor with context (across groups)', async () => {
const [part] = await createPart();
const group = part.activeGroup;

View File

@@ -1,23 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { EditorActivation } from 'vs/platform/editor/common/editor';
import { EditorActivation, EditorOverride } from 'vs/platform/editor/common/editor';
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { EditorInput, EditorsOrder, SideBySideEditorInput } from 'vs/workbench/common/editor';
import { EditorsOrder, IResourceDiffEditorInput } from 'vs/workbench/common/editor';
import { workbenchInstantiationService, TestServiceAccessor, registerTestEditor, TestFileEditorInput, ITestInstantiationService, registerTestResourceEditor, registerTestSideBySideEditor, createEditorPart } from 'vs/workbench/test/browser/workbenchTestServices';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { TextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { EditorService, DelegatingEditorService } from 'vs/workbench/services/editor/browser/editorService';
import { IEditorGroup, IEditorGroupsService, GroupDirection, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService';
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { FileEditorInput } from 'vs/workbench/contrib/files/browser/editors/fileEditorInput';
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
import { timeout } from 'vs/base/common/async';
import { toResource } from 'vs/base/test/common/utils';
@@ -30,6 +30,12 @@ import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
import { isLinux } from 'vs/base/common/platform';
import { MockScopableContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { ContributedEditorPriority } from 'vs/workbench/services/editor/common/editorOverrideService';
import { IWorkspaceTrustRequestService, WorkspaceTrustUriResponse } from 'vs/platform/workspace/common/workspaceTrust';
import { TestWorkspaceTrustRequestService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService';
import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} Skip suite
@@ -60,6 +66,7 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} Skip suite
const part = await createEditorPart(instantiationService, disposables);
instantiationService.stub(IEditorGroupsService, part);
instantiationService.stub(IWorkspaceTrustRequestService, new TestWorkspaceTrustRequestService(false));
const editorService = instantiationService.createInstance(EditorService);
instantiationService.stub(IEditorService, editorService);
@@ -226,6 +233,104 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} Skip suite
assert.strictEqual(part.activeGroup.getIndexOfEditor(replaceInput), 0);
});
test('openEditors() handles workspace trust (typed editors)', async () => {
const [part, service, accessor] = await createEditorService();
const input1 = new TestFileEditorInput(URI.parse('my://resource1-openEditors'), TEST_EDITOR_INPUT_ID);
const input2 = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID);
const input3 = new TestFileEditorInput(URI.parse('my://resource3-openEditors'), TEST_EDITOR_INPUT_ID);
const input4 = new TestFileEditorInput(URI.parse('my://resource4-openEditors'), TEST_EDITOR_INPUT_ID);
const sideBySideInput = new SideBySideEditorInput('side by side', undefined, input3, input4);
const oldHandler = accessor.workspaceTrustRequestService.requestOpenUrisHandler;
try {
// Trust: cancel
let trustEditorUris: URI[] = [];
accessor.workspaceTrustRequestService.requestOpenUrisHandler = async uris => {
trustEditorUris = uris;
return WorkspaceTrustUriResponse.Cancel;
};
await service.openEditors([{ editor: input1 }, { editor: input2 }, { editor: sideBySideInput }], undefined, { validateTrust: true });
assert.strictEqual(part.activeGroup.count, 0);
assert.strictEqual(trustEditorUris.length, 4);
assert.strictEqual(trustEditorUris.some(uri => uri.toString() === input1.resource.toString()), true);
assert.strictEqual(trustEditorUris.some(uri => uri.toString() === input2.resource.toString()), true);
assert.strictEqual(trustEditorUris.some(uri => uri.toString() === input3.resource.toString()), true);
assert.strictEqual(trustEditorUris.some(uri => uri.toString() === input4.resource.toString()), true);
// Trust: open in new window
accessor.workspaceTrustRequestService.requestOpenUrisHandler = async uris => WorkspaceTrustUriResponse.OpenInNewWindow;
await service.openEditors([{ editor: input1 }, { editor: input2 }, { editor: sideBySideInput }], undefined, { validateTrust: true });
assert.strictEqual(part.activeGroup.count, 0);
// Trust: allow
accessor.workspaceTrustRequestService.requestOpenUrisHandler = async uris => WorkspaceTrustUriResponse.Open;
await service.openEditors([{ editor: input1 }, { editor: input2 }, { editor: sideBySideInput }], undefined, { validateTrust: true });
assert.strictEqual(part.activeGroup.count, 3);
} finally {
accessor.workspaceTrustRequestService.requestOpenUrisHandler = oldHandler;
}
});
test('openEditors() ignores trust when `validateTrust: false', async () => {
const [part, service, accessor] = await createEditorService();
const input1 = new TestFileEditorInput(URI.parse('my://resource1-openEditors'), TEST_EDITOR_INPUT_ID);
const input2 = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID);
const input3 = new TestFileEditorInput(URI.parse('my://resource3-openEditors'), TEST_EDITOR_INPUT_ID);
const input4 = new TestFileEditorInput(URI.parse('my://resource4-openEditors'), TEST_EDITOR_INPUT_ID);
const sideBySideInput = new SideBySideEditorInput('side by side', undefined, input3, input4);
const oldHandler = accessor.workspaceTrustRequestService.requestOpenUrisHandler;
try {
// Trust: cancel
accessor.workspaceTrustRequestService.requestOpenUrisHandler = async uris => WorkspaceTrustUriResponse.Cancel;
await service.openEditors([{ editor: input1 }, { editor: input2 }, { editor: sideBySideInput }]);
assert.strictEqual(part.activeGroup.count, 3);
} finally {
accessor.workspaceTrustRequestService.requestOpenUrisHandler = oldHandler;
}
});
test('openEditors() extracts proper resources from untyped editors for workspace trust', async () => {
const [part, service, accessor] = await createEditorService();
const input = { resource: URI.parse('my://resource-openEditors') };
const otherInput: IResourceDiffEditorInput = {
originalInput: { resource: URI.parse('my://resource2-openEditors') },
modifiedInput: { resource: URI.parse('my://resource3-openEditors') }
};
const oldHandler = accessor.workspaceTrustRequestService.requestOpenUrisHandler;
try {
let trustEditorUris: URI[] = [];
accessor.workspaceTrustRequestService.requestOpenUrisHandler = async uris => {
trustEditorUris = uris;
return oldHandler(uris);
};
await service.openEditors([input, otherInput], undefined, { validateTrust: true });
assert.strictEqual(part.activeGroup.count, 0);
assert.strictEqual(trustEditorUris.length, 3);
assert.strictEqual(trustEditorUris.some(uri => uri.toString() === input.resource.toString()), true);
assert.strictEqual(trustEditorUris.some(uri => uri.toString() === otherInput.originalInput.resource?.toString()), true);
assert.strictEqual(trustEditorUris.some(uri => uri.toString() === otherInput.modifiedInput.resource?.toString()), true);
} finally {
accessor.workspaceTrustRequestService.requestOpenUrisHandler = oldHandler;
}
});
test('caching', function () {
const instantiationService = workbenchInstantiationService();
const service = instantiationService.createInstance(EditorService);
@@ -317,6 +422,16 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} Skip suite
assert(input instanceof FileEditorInput);
contentInput = <FileEditorInput>input;
assert.strictEqual(contentInput.getPreferredMode(), mode);
let fileModel = (await contentInput.resolve() as ITextFileEditorModel);
assert.strictEqual(fileModel.textEditorModel?.getModeId(), mode);
// Untyped Input (file, contents)
input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), contents: 'My contents' });
assert(input instanceof FileEditorInput);
contentInput = <FileEditorInput>input;
fileModel = (await contentInput.resolve() as ITextFileEditorModel);
assert.strictEqual(fileModel.textEditorModel?.getValue(), 'My contents');
assert.strictEqual(fileModel.isDirty(), true);
// Untyped Input (file, different mode)
input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), mode: 'text' });
@@ -361,14 +476,20 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} Skip suite
// Untyped Input (resource)
input = service.createEditorInput({ resource: URI.parse('custom:resource') });
assert(input instanceof ResourceEditorInput);
assert(input instanceof TextResourceEditorInput);
// Untyped Input (diff)
input = service.createEditorInput({
leftResource: toResource.call(this, '/primary.html'),
rightResource: toResource.call(this, '/secondary.html')
});
const resourceDiffInput = {
originalInput: { resource: toResource.call(this, '/primary.html') },
modifiedInput: { resource: toResource.call(this, '/secondary.html') }
};
input = service.createEditorInput(resourceDiffInput);
assert(input instanceof DiffEditorInput);
assert.strictEqual(input.originalInput.resource?.toString(), resourceDiffInput.originalInput.resource.toString());
assert.strictEqual(input.modifiedInput.resource?.toString(), resourceDiffInput.modifiedInput.resource.toString());
const untypedDiffInput = input.asResourceEditorInput(0) as IResourceDiffEditorInput;
assert.strictEqual(untypedDiffInput.originalInput.resource?.toString(), resourceDiffInput.originalInput.resource.toString());
assert.strictEqual(untypedDiffInput.modifiedInput.resource?.toString(), resourceDiffInput.modifiedInput.resource.toString());
});
test('delegate', function (done) {
@@ -389,18 +510,18 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} Skip suite
createEditor(): void { }
}
const ed = instantiationService.createInstance(MyEditor, 'my.editor');
const editor = instantiationService.createInstance(MyEditor, 'my.editor');
const inp = instantiationService.createInstance(ResourceEditorInput, URI.parse('my://resource-delegate'), 'name', 'description', undefined);
const input = instantiationService.createInstance(TextResourceEditorInput, URI.parse('my://resource-delegate'), 'name', 'description', undefined, undefined);
const delegate = instantiationService.createInstance(DelegatingEditorService, async (group, delegate) => {
assert.ok(group);
done();
return ed;
return editor;
});
delegate.openEditor(inp);
delegate.openEditor(input);
});
test('close editor does not dispose when editor opened in other group', async () => {
@@ -955,7 +1076,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} Skip suite
mtime: 0,
name: 'resource2',
size: 0,
isSymbolicLink: false
isSymbolicLink: false,
readonly: false
}));
await activeEditorChangePromise;
@@ -998,32 +1120,105 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} Skip suite
assert.strictEqual(editorContextKeyService, part.activeGroup.activeEditorPane?.scopedContextKeyService);
});
test('overrideOpenEditor', async function () {
const [, service] = await createEditorService();
test('editorOverrideService - openEditor', async function () {
const [, service, accessor] = await createEditorService();
const editorOverrideService = accessor.editorOverrideService;
let overrideCount = 0;
const registrationDisposable = editorOverrideService.registerEditor(
'*.md',
{
id: 'TestEditor',
label: 'Test Editor',
detail: 'Test Editor Provider',
describes: () => false,
priority: ContributedEditorPriority.builtin
},
{},
(resource) => {
overrideCount++;
return ({ editor: service.createEditorInput({ resource }) });
},
diffEditor => ({ editor: diffEditor })
);
assert.strictEqual(overrideCount, 0);
const input1 = new TestFileEditorInput(URI.parse('file://test/path/resource1.txt'), TEST_EDITOR_INPUT_ID);
const input2 = new TestFileEditorInput(URI.parse('file://test/path/resource2.md'), TEST_EDITOR_INPUT_ID);
// Open editor input 1 and it shouln't trigger override as the glob doesn't match
await service.openEditor(input1);
assert.strictEqual(overrideCount, 0);
// Open editor input 2 and it should trigger override as the glob doesn match
await service.openEditor(input2);
assert.strictEqual(overrideCount, 1);
// Because we specify an override we shouldn't see it triggered even if it matches
await service.openEditor(input2, { override: 'default' });
assert.strictEqual(overrideCount, 1);
const input1 = new TestFileEditorInput(URI.parse('file://resource1'), TEST_EDITOR_INPUT_ID);
const input2 = new TestFileEditorInput(URI.parse('file://resource2'), TEST_EDITOR_INPUT_ID);
registrationDisposable.dispose();
});
let overrideCalled = false;
test('editorOverrideService - openEditors', async function () {
const [, service, accessor] = await createEditorService();
const editorOverrideService = accessor.editorOverrideService;
let overrideCount = 0;
const registrationDisposable = editorOverrideService.registerEditor(
'*.md',
{
id: 'TestEditor',
label: 'Test Editor',
detail: 'Test Editor Provider',
describes: () => false,
priority: ContributedEditorPriority.builtin
},
{},
(resource) => {
overrideCount++;
return ({ editor: service.createEditorInput({ resource }) });
},
diffEditor => ({ editor: diffEditor })
);
assert.strictEqual(overrideCount, 0);
const input1 = new TestFileEditorInput(URI.parse('file://test/path/resource1.txt'), TEST_EDITOR_INPUT_ID);
const input2 = new TestFileEditorInput(URI.parse('file://test/path/resource2.txt'), TEST_EDITOR_INPUT_ID);
const input3 = new TestFileEditorInput(URI.parse('file://test/path/resource3.md'), TEST_EDITOR_INPUT_ID);
const input4 = new TestFileEditorInput(URI.parse('file://test/path/resource4.md'), TEST_EDITOR_INPUT_ID);
// Open editor input 1 and it shouln't trigger override as the glob doesn't match
await service.openEditors([{ editor: input1 }, { editor: input2 }, { editor: input3 }, { editor: input4 }]);
assert.strictEqual(overrideCount, 2);
const handler = service.overrideOpenEditor({
open: editor => {
if (editor === input1) {
overrideCalled = true;
registrationDisposable.dispose();
});
return { override: service.openEditor(input2, { pinned: true }) };
}
return undefined;
}
});
await service.openEditor(input1, { pinned: true });
assert.ok(overrideCalled);
assert.strictEqual(service.activeEditor, input2);
handler.dispose();
test('editorOverrideService - replaceEditors', async function () {
const [part, service, accessor] = await createEditorService();
const editorOverrideService = accessor.editorOverrideService;
let overrideCount = 0;
const registrationDisposable = editorOverrideService.registerEditor(
'*.md',
{
id: 'TestEditor',
label: 'Test Editor',
detail: 'Test Editor Provider',
describes: () => false,
priority: ContributedEditorPriority.builtin
},
{},
(resource) => {
overrideCount++;
return ({ editor: service.createEditorInput({ resource }) });
},
diffEditor => ({ editor: diffEditor })
);
assert.strictEqual(overrideCount, 0);
const input1 = new TestFileEditorInput(URI.parse('file://test/path/resource2.md'), TEST_EDITOR_INPUT_ID);
// Open editor input 1 and it shouldn't trigger because I've disabled the override logic
await service.openEditor(input1, { override: EditorOverride.DISABLED });
assert.strictEqual(overrideCount, 0);
await service.replaceEditors([{
editor: input1,
replacement: input1,
}], part.activeGroup);
assert.strictEqual(overrideCount, 1);
registrationDisposable.dispose();
});
test('findEditors (in group)', async () => {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { EditorOptions, IEditorInputFactoryRegistry, EditorExtensions, SideBySideEditorInput } from 'vs/workbench/common/editor';
import { IEditorInputFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor';
import { URI } from 'vs/base/common/uri';
import { workbenchInstantiationService, TestFileEditorInput, registerTestEditor, TestEditorPart, createEditorPart, registerTestSideBySideEditor } from 'vs/workbench/test/browser/workbenchTestServices';
import { Registry } from 'vs/platform/registry/common/platform';
@@ -17,8 +17,9 @@ import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver';
import { timeout } from 'vs/base/common/async';
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput';
suite.skip('EditorsObserver', function () {
suite.skip('EditorsObserver', function () { // {{SQL CARBON EDIT}} Skip suite
const TEST_EDITOR_ID = 'MyTestEditorForEditorsObserver';
const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorsObserver';
@@ -26,7 +27,6 @@ suite.skip('EditorsObserver', function () {
const disposables = new DisposableStore();
setup(() => {
disposables.add(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput)], TEST_SERIALIZABLE_EDITOR_INPUT_ID));
disposables.add(registerTestSideBySideEditor());
@@ -68,7 +68,7 @@ suite.skip('EditorsObserver', function () {
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input1, { pinned: true });
currentEditorsMRU = observer.editors;
assert.strictEqual(currentEditorsMRU.length, 1);
@@ -85,8 +85,8 @@ suite.skip('EditorsObserver', function () {
assert.strictEqual(observer.hasEditors(input2.resource), false);
assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), false);
await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input2, { pinned: true });
await part.activeGroup.openEditor(input3, { pinned: true });
currentEditorsMRU = observer.editors;
assert.strictEqual(currentEditorsMRU.length, 3);
@@ -99,7 +99,7 @@ suite.skip('EditorsObserver', function () {
assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), true);
assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), true);
await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input2, { pinned: true });
currentEditorsMRU = observer.editors;
assert.strictEqual(currentEditorsMRU.length, 3);
@@ -149,8 +149,8 @@ suite.skip('EditorsObserver', function () {
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE }));
await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE }));
await rootGroup.openEditor(input1, { pinned: true, activation: EditorActivation.ACTIVATE });
await sideGroup.openEditor(input1, { pinned: true, activation: EditorActivation.ACTIVATE });
currentEditorsMRU = observer.editors;
assert.strictEqual(currentEditorsMRU.length, 2);
@@ -161,7 +161,7 @@ suite.skip('EditorsObserver', function () {
assert.strictEqual(observer.hasEditors(input1.resource), true);
assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true);
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE }));
await rootGroup.openEditor(input1, { pinned: true, activation: EditorActivation.ACTIVATE });
currentEditorsMRU = observer.editors;
assert.strictEqual(currentEditorsMRU.length, 2);
@@ -176,7 +176,7 @@ suite.skip('EditorsObserver', function () {
// the most recent editor, but rather put it behind
const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
await rootGroup.openEditor(input2, EditorOptions.create({ inactive: true }));
await rootGroup.openEditor(input2, { inactive: true });
currentEditorsMRU = observer.editors;
assert.strictEqual(currentEditorsMRU.length, 3);
@@ -222,13 +222,13 @@ suite.skip('EditorsObserver', function () {
assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), false);
assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), false);
await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input1, { pinned: true });
assert.strictEqual(observer.hasEditors(input1.resource), true);
assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true);
assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId }), false);
await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input2, { pinned: true });
assert.strictEqual(observer.hasEditors(input1.resource), true);
assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId }), true);
@@ -259,13 +259,13 @@ suite.skip('EditorsObserver', function () {
assert.strictEqual(observer.hasEditor({ resource: primary.resource, typeId: primary.typeId }), false);
assert.strictEqual(observer.hasEditor({ resource: secondary.resource, typeId: secondary.typeId }), false);
await part.activeGroup.openEditor(input, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input, { pinned: true });
assert.strictEqual(observer.hasEditors(primary.resource), true);
assert.strictEqual(observer.hasEditor({ resource: primary.resource, typeId: primary.typeId }), true);
assert.strictEqual(observer.hasEditor({ resource: secondary.resource, typeId: secondary.typeId }), false);
await part.activeGroup.openEditor(primary, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(primary, { pinned: true });
assert.strictEqual(observer.hasEditors(primary.resource), true);
assert.strictEqual(observer.hasEditor({ resource: primary.resource, typeId: primary.typeId }), true);
@@ -293,9 +293,9 @@ suite.skip('EditorsObserver', function () {
const rootGroup = part.activeGroup;
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input1, { pinned: true });
await rootGroup.openEditor(input2, { pinned: true });
await rootGroup.openEditor(input3, { pinned: true });
let currentEditorsMRU = observer.editors;
assert.strictEqual(currentEditorsMRU.length, 3);
@@ -353,9 +353,9 @@ suite.skip('EditorsObserver', function () {
const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input1, { pinned: true });
await rootGroup.openEditor(input2, { pinned: true });
await rootGroup.openEditor(input3, { pinned: true });
const storage = new TestStorageService();
const observer = disposables.add(new EditorsObserver(part, storage));
@@ -400,11 +400,11 @@ suite.skip('EditorsObserver', function () {
const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_SERIALIZABLE_EDITOR_INPUT_ID);
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input1, { pinned: true });
await rootGroup.openEditor(input2, { pinned: true });
const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT);
await sideGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
await sideGroup.openEditor(input3, { pinned: true });
const storage = new TestStorageService();
const observer = disposables.add(new EditorsObserver(part, storage));
@@ -447,7 +447,7 @@ suite.skip('EditorsObserver', function () {
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID);
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input1, { pinned: true });
const storage = new TestStorageService();
const observer = disposables.add(new EditorsObserver(part, storage));
@@ -484,10 +484,10 @@ suite.skip('EditorsObserver', function () {
const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID);
const input4 = new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID);
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input4, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input1, { pinned: true });
await rootGroup.openEditor(input2, { pinned: true });
await rootGroup.openEditor(input3, { pinned: true });
await rootGroup.openEditor(input4, { pinned: true });
assert.strictEqual(rootGroup.count, 3);
assert.strictEqual(rootGroup.contains(input1), false);
@@ -515,7 +515,7 @@ suite.skip('EditorsObserver', function () {
assert.strictEqual(observer.hasEditor({ resource: input4.resource, typeId: input4.typeId }), true);
const input5 = new TestFileEditorInput(URI.parse('foo://bar5'), TEST_EDITOR_INPUT_ID);
await sideGroup.openEditor(input5, EditorOptions.create({ pinned: true }));
await sideGroup.openEditor(input5, { pinned: true });
assert.strictEqual(rootGroup.count, 1);
assert.strictEqual(rootGroup.contains(input1), false);
@@ -545,10 +545,10 @@ suite.skip('EditorsObserver', function () {
const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID);
const input4 = new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID);
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input4, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input1, { pinned: true });
await rootGroup.openEditor(input2, { pinned: true });
await rootGroup.openEditor(input3, { pinned: true });
await rootGroup.openEditor(input4, { pinned: true });
assert.strictEqual(rootGroup.count, 3); // 1 editor got closed due to our limit!
assert.strictEqual(rootGroup.contains(input1), false);
@@ -560,10 +560,10 @@ suite.skip('EditorsObserver', function () {
assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId }), true);
assert.strictEqual(observer.hasEditor({ resource: input4.resource, typeId: input4.typeId }), true);
await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
await sideGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
await sideGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
await sideGroup.openEditor(input4, EditorOptions.create({ pinned: true }));
await sideGroup.openEditor(input1, { pinned: true });
await sideGroup.openEditor(input2, { pinned: true });
await sideGroup.openEditor(input3, { pinned: true });
await sideGroup.openEditor(input4, { pinned: true });
assert.strictEqual(sideGroup.count, 3);
assert.strictEqual(sideGroup.contains(input1), false);
@@ -611,10 +611,10 @@ suite.skip('EditorsObserver', function () {
const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID);
const input4 = new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID);
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, sticky: true }));
await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input4, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input1, { pinned: true, sticky: true });
await rootGroup.openEditor(input2, { pinned: true });
await rootGroup.openEditor(input3, { pinned: true });
await rootGroup.openEditor(input4, { pinned: true });
assert.strictEqual(rootGroup.count, 3);
assert.strictEqual(rootGroup.contains(input1), true);

View File

@@ -226,25 +226,15 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
get disableExtensions() { return this.payload?.get('disableExtensions') === 'true'; }
private get webviewEndpoint(): string {
// TODO@matt: get fallback from product service
return this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com/{{commit}}';
}
@memoize
get webviewExternalEndpoint(): string {
return (this.webviewEndpoint).replace('{{commit}}', this.productService.commit || '23a2409675bc1bde94f3532bc7c5826a6e99e4b6');
}
const endpoint = this.options.webviewEndpoint
|| this.productService.webviewContentExternalBaseUrlTemplate
|| 'https://{{uuid}}.vscode-webview.net/{{quality}}/{{commit}}/out/vs/workbench/contrib/webview/browser/pre/';
@memoize
get webviewResourceRoot(): string {
return `${this.webviewExternalEndpoint}/vscode-resource/{{resource}}`;
}
@memoize
get webviewCspSource(): string {
const uri = URI.parse(this.webviewEndpoint.replace('{{uuid}}', '*'));
return `${uri.scheme}://${uri.authority}`;
return endpoint
.replace('{{commit}}', this.payload?.get('webviewExternalEndpointCommit') ?? this.productService.commit ?? '97740a7d253650f9f186c211de5247e2577ce9f7')
.replace('{{quality}}', this.productService.quality || 'insider');
}
@memoize
@@ -256,6 +246,9 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
get skipReleaseNotes(): boolean { return false; }
@memoize
get disableWorkspaceTrust(): boolean { return true; }
private payload: Map<string, string> | undefined;
constructor(

View File

@@ -36,8 +36,6 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService {
readonly extensionEnabledProposedApi?: string[];
readonly webviewExternalEndpoint: string;
readonly webviewResourceRoot: string;
readonly webviewCspSource: string;
readonly skipReleaseNotes: boolean;

View File

@@ -68,21 +68,6 @@ export class NativeWorkbenchEnvironmentService extends AbstractNativeEnvironment
@memoize
get webviewExternalEndpoint(): string { return `${Schemas.vscodeWebview}://{{uuid}}`; }
@memoize
get webviewResourceRoot(): string {
// On desktop, this endpoint is only used for the service worker to identify resource loads and
// should never actually be requested.
//
// Required due to https://github.com/electron/electron/issues/28528
return 'https://{{uuid}}.vscode-webview-test.com/vscode-resource/{{resource}}';
}
@memoize
get webviewCspSource(): string {
const uri = URI.parse(this.webviewResourceRoot.replace('{{uuid}}', '*'));
return `${uri.scheme}://${uri.authority}`;
}
@memoize
get skipReleaseNotes(): boolean { return !!this.args['skip-release-notes']; }

View File

@@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';

View File

@@ -291,7 +291,7 @@ registerAction2(class extends Action2 {
if (done.bad) {
// DONE but nothing found
await dialogService.show(Severity.Info, localize('done.msg', "Extension Bisect"), [], {
await dialogService.show(Severity.Info, localize('done.msg', "Extension Bisect"), undefined, {
detail: localize('done.detail2', "Extension Bisect is done but no extension has been identified. This might be a problem with {0}.", productService.nameShort)
});
@@ -299,7 +299,6 @@ registerAction2(class extends Action2 {
// DONE and identified extension
const res = await dialogService.show(Severity.Info, localize('done.msg', "Extension Bisect"),
[localize('report', "Report Issue & Continue"), localize('done', "Continue")],
// [],
{
detail: localize('done.detail', "Extension Bisect is done and has identified {0} as the extension causing the problem.", done.id),
checkbox: { label: localize('done.disbale', "Keep this extension disabled"), checked: true },

View File

@@ -26,7 +26,7 @@ import { IExtensionBisectService } from 'vs/workbench/services/extensionManageme
import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust';
import { Promises } from 'vs/base/common/async';
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
import { getVirtualWorkspaceScheme } from 'vs/platform/remote/common/remoteHosts';
import { isVirtualWorkspace } from 'vs/platform/remote/common/remoteHosts';
const SOURCE = 'IWorkbenchExtensionEnablementService';
@@ -95,7 +95,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
if (this._isDisabledByExtensionKind(extension)) {
return EnablementState.DisabledByExtensionKind;
}
if (this._isEnabled(extension) && this._isDisabledByTrustRequirement(extension)) {
if (this._isEnabled(extension) && this._isDisabledByWorkspaceTrust(extension)) {
return EnablementState.DisabledByTrustRequirement;
}
return this._getEnablementState(extension.identifier);
@@ -160,14 +160,10 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
}
const result = await Promises.settled(extensions.map(e => {
if (this._isDisabledByTrustRequirement(e)) {
return this.workspaceTrustRequestService.requestWorkspaceTrust({ modal: true })
if (this._isDisabledByWorkspaceTrust(e)) {
return this.workspaceTrustRequestService.requestWorkspaceTrust()
.then(trustState => {
if (trustState) {
return this._setEnablement(e, newState);
} else {
return Promise.resolve(false);
}
return Promise.resolve(trustState ?? false);
});
} else {
return this._setEnablement(e, newState);
@@ -233,8 +229,8 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
}
private _isDisabledByVirtualWorkspace(extension: IExtension): boolean {
if (getVirtualWorkspaceScheme(this.contextService.getWorkspace()) !== undefined) {
return !this.extensionManifestPropertiesService.canSupportVirtualWorkspace(extension.manifest);
if (isVirtualWorkspace(this.contextService.getWorkspace())) {
return this.extensionManifestPropertiesService.getExtensionVirtualWorkspaceSupportType(extension.manifest) === false;
}
return false;
}
@@ -272,12 +268,16 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
return false;
}
private _isDisabledByTrustRequirement(extension: IExtension): boolean {
isDisabledByWorkspaceTrust(extension: IExtension): boolean {
return this._isEnabled(extension) && this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(extension.manifest) === false;
}
private _isDisabledByWorkspaceTrust(extension: IExtension): boolean {
if (this.workspaceTrustManagementService.isWorkpaceTrusted()) {
return false;
}
return this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(extension.manifest) === false;
return this.isDisabledByWorkspaceTrust(extension);
}
private _getEnablementState(identifier: IExtensionIdentifier): EnablementState {
@@ -416,7 +416,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
}
private _onDidInstallExtension({ local, error }: DidInstallExtensionEvent): void {
if (local && !error && this._isDisabledByTrustRequirement(local)) {
if (local && !error && this._isDisabledByWorkspaceTrust(local)) {
this._onEnablementChanged.fire([local]);
}
}
@@ -427,15 +427,12 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
}
}
private async _getExtensionsByWorkspaceTrustRequirement(): Promise<IExtension[]> {
const extensions = await this.extensionManagementService.getInstalled();
return extensions.filter(e => this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(e.manifest) === false);
}
public async updateEnablementByWorkspaceTrustRequirement(): Promise<void> {
const extensions = await this._getExtensionsByWorkspaceTrustRequirement();
if (extensions.length) {
this._onEnablementChanged.fire(extensions);
const installedExtensions = await this.extensionManagementService.getInstalled();
const disabledExtensions = installedExtensions.filter(e => this.isDisabledByWorkspaceTrust(e));
if (disabledExtensions.length) {
this._onEnablementChanged.fire(disabledExtensions);
}
}

View File

@@ -9,7 +9,6 @@ import { IExtension, IScannedExtension, ExtensionType, ITranslatedScannedExtensi
import { IExtensionManagementService, IGalleryExtension, IExtensionIdentifier, ILocalExtension, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { URI } from 'vs/base/common/uri';
import { IWorkspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; // {{SQL CARBON EDIT}}
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
export interface IExtensionManagementServer {
id: string;
@@ -83,6 +82,12 @@ export interface IWorkbenchExtensionEnablementService {
*/
isDisabledGlobally(extension: IExtension): boolean;
/**
* Returns `true` if the given extension identifier is enabled by the user but it it
* disabled due to the fact that the current window/folder/workspace is not trusted.
*/
isDisabledByWorkspaceTrust(extension: IExtension): boolean;
/**
* Enable or disable the given extension.
* if `workspace` is `true` then enablement is done for workspace, otherwise globally.
@@ -101,16 +106,12 @@ export interface IWorkbenchExtensionEnablementService {
updateEnablementByWorkspaceTrustRequirement(): Promise<void>;
}
// {{SQL CARBON EDIT}}
export interface IExtensionsConfigContent {
recommendations: string[];
unwantedRecommendations: string[];
}
export type RecommendationChangeNotification = {
extensionId: string,
isRecommended: boolean
};
export type DynamicRecommendation = 'dynamic';
export type ConfigRecommendation = 'config';
export type ExecutableRecommendation = 'executable';
@@ -123,12 +124,7 @@ export interface IExtensionRecommendation {
extensionId: string;
sources: ExtensionRecommendationSource[];
}
export interface IExtensionRecommendationReason {
reasonId: ExtensionRecommendationReason;
reasonText: string;
}
// {{SQL CARBON EDIT}} - End
export const IWebExtensionsScannerService = createDecorator<IWebExtensionsScannerService>('IWebExtensionsScannerService');
export interface IWebExtensionsScannerService {
readonly _serviceBrand: undefined;

View File

@@ -5,7 +5,7 @@
import { Event, EventMultiplexer } from 'vs/base/common/event';
import {
ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata, IExtensionGalleryService, InstallOptions, UninstallOptions, INSTALL_ERROR_NOT_SUPPORTED
ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata, IExtensionGalleryService, InstallOptions, UninstallOptions, INSTALL_ERROR_NOT_SUPPORTED, InstallVSIXOptions
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementServer, IExtensionManagementServerService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ExtensionType, isLanguagePackExtension, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
@@ -171,35 +171,35 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
.map(({ extensionManagementService }) => extensionManagementService.unzip(zipLocation))).then(([extensionIdentifier]) => extensionIdentifier);
}
async install(vsix: URI): Promise<ILocalExtension> {
async install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension> {
if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {
const manifest = await this.getManifest(vsix);
if (isLanguagePackExtension(manifest)) {
// Install on both servers
const [local] = await Promises.settled([this.extensionManagementServerService.localExtensionManagementServer, this.extensionManagementServerService.remoteExtensionManagementServer].map(server => this.installVSIX(vsix, server)));
const [local] = await Promises.settled([this.extensionManagementServerService.localExtensionManagementServer, this.extensionManagementServerService.remoteExtensionManagementServer].map(server => this.installVSIX(vsix, server, options)));
return local;
}
if (this.extensionManifestPropertiesService.prefersExecuteOnUI(manifest)) {
// Install only on local server
return this.installVSIX(vsix, this.extensionManagementServerService.localExtensionManagementServer);
return this.installVSIX(vsix, this.extensionManagementServerService.localExtensionManagementServer, options);
}
// Install only on remote server
return this.installVSIX(vsix, this.extensionManagementServerService.remoteExtensionManagementServer);
return this.installVSIX(vsix, this.extensionManagementServerService.remoteExtensionManagementServer, options);
}
if (this.extensionManagementServerService.localExtensionManagementServer) {
return this.installVSIX(vsix, this.extensionManagementServerService.localExtensionManagementServer);
return this.installVSIX(vsix, this.extensionManagementServerService.localExtensionManagementServer, options);
}
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
return this.installVSIX(vsix, this.extensionManagementServerService.remoteExtensionManagementServer);
return this.installVSIX(vsix, this.extensionManagementServerService.remoteExtensionManagementServer, options);
}
return Promise.reject('No Servers to Install');
}
protected async installVSIX(vsix: URI, server: IExtensionManagementServer): Promise<ILocalExtension> {
protected async installVSIX(vsix: URI, server: IExtensionManagementServer, options: InstallVSIXOptions | undefined): Promise<ILocalExtension> {
const manifest = await this.getManifest(vsix);
if (manifest) {
await this.checkForWorkspaceTrust(manifest);
return server.extensionManagementService.install(vsix);
return server.extensionManagementService.install(vsix, options);
}
return Promise.reject('Unable to get the extension manifest.');
}
@@ -360,7 +360,6 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
protected async checkForWorkspaceTrust(manifest: IExtensionManifest): Promise<void> {
if (this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(manifest) === false) {
const trustState = await this.workspaceTrustRequestService.requestWorkspaceTrust({
modal: true,
message: localize('extensionInstallWorkspaceTrustMessage', "Enabling this extension requires a trusted workspace."),
buttons: [
{ label: localize('extensionInstallWorkspaceTrustButton', "Trust Workspace & Install"), type: 'ContinueWithTrust' },

View File

@@ -21,11 +21,10 @@ import { groupByExtension, areSameExtensions, getGalleryExtensionId } from 'vs/p
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import type { IStaticExtension } from 'vs/workbench/workbench.web.api';
import { Disposable } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls';
import { localize } from 'vs/nls';
import * as semver from 'vs/base/common/semver/semver';
import { isArray } from 'vs/base/common/types';
import { isArray, isFunction } from 'vs/base/common/types';
interface IUserExtension {
identifier: IExtensionIdentifier;
@@ -54,8 +53,6 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
private readonly extensionsResource: URI | undefined = undefined;
private readonly userExtensionsResourceLimiter: Queue<IUserExtension[]> = new Queue<IUserExtension[]>();
private userExtensionsPromise: Promise<IScannedExtension[]> | undefined;
constructor(
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IBuiltinExtensionsScannerService private readonly builtinExtensionsScannerService: IBuiltinExtensionsScannerService,
@@ -69,15 +66,20 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
this.extensionsResource = joinPath(environmentService.userRoamingDataHome, 'extensions.json');
this.systemExtensionsPromise = this.readSystemExtensions();
this.defaultExtensionsPromise = this.readDefaultExtensions();
if (this.extensionsResource) {
this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.extensionsResource!))(() => this.userExtensionsPromise = undefined));
}
}
}
private async readSystemExtensions(): Promise<IScannedExtension[]> {
const extensions = await this.builtinExtensionsScannerService.scanBuiltinExtensions();
return extensions.concat(this.getStaticExtensions(true));
let [builtinExtensions, staticExtensions] = await Promise.all([
this.builtinExtensionsScannerService.scanBuiltinExtensions(),
this.getStaticExtensions(true)
]);
if (isFunction(this.environmentService.options?.builtinExtensionsFilter)) {
builtinExtensions = builtinExtensions.filter(e => this.environmentService.options!.builtinExtensionsFilter!(e.identifier.id));
}
return [...builtinExtensions, ...staticExtensions];
}
/**
@@ -183,10 +185,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
if (type === undefined || type === ExtensionType.User) {
const staticExtensions = await this.defaultExtensionsPromise;
extensions.push(...staticExtensions);
if (!this.userExtensionsPromise) {
this.userExtensionsPromise = this.scanUserExtensions();
}
const userExtensions = await this.userExtensionsPromise;
const userExtensions = await this.scanUserExtensions();
extensions.push(...userExtensions);
}
return extensions;
@@ -259,6 +258,10 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
}
canAddExtension(galleryExtension: IGalleryExtension): boolean {
if (this.environmentService.options?.assumeGalleryExtensionsAreAddressable) {
return true;
}
return !!galleryExtension.properties.webExtension && !!galleryExtension.webResource;
}
@@ -267,7 +270,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
throw new Error(localize('cannot be installed', "Cannot install '{0}' because this extension is not a web extension.", galleryExtension.displayName || galleryExtension.name));
}
const extensionLocation = galleryExtension.webResource!;
const extensionLocation = joinPath(galleryExtension.assetUri, 'Microsoft.VisualStudio.Code.WebResources', 'extension');
const packageNLSUri = joinPath(extensionLocation, 'package.nls.json');
const context = await this.requestService.request({ type: 'GET', url: packageNLSUri.toString() }, CancellationToken.None);
const packageNLSExists = isSuccess(context);
@@ -371,7 +374,6 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
packageNLSUri: e.packageNLSUri?.toJSON(),
}));
await this.fileService.writeFile(this.extensionsResource!, VSBuffer.fromString(JSON.stringify(storedUserExtensions)));
this.userExtensionsPromise = undefined;
return userExtensions;
});
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { generateUuid } from 'vs/base/common/uuid';
import { ILocalExtension, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ILocalExtension, IExtensionGalleryService, InstallVSIXOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { URI } from 'vs/base/common/uri';
import { ExtensionManagementService as BaseExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
@@ -38,7 +38,7 @@ export class ExtensionManagementService extends BaseExtensionManagementService {
super(extensionManagementServerService, extensionGalleryService, configurationService, productService, downloadService, userDataAutoSyncEnablementService, userDataSyncResourceEnablementService, dialogService, workspaceTrustRequestService, extensionManifestPropertiesService);
}
protected override async installVSIX(vsix: URI, server: IExtensionManagementServer): Promise<ILocalExtension> {
protected override async installVSIX(vsix: URI, server: IExtensionManagementServer, options: InstallVSIXOptions | undefined): Promise<ILocalExtension> {
if (vsix.scheme === Schemas.vscodeRemote && server === this.extensionManagementServerService.localExtensionManagementServer) {
const downloadedLocation = joinPath(this.environmentService.tmpDir, generateUuid());
await this.downloadService.download(vsix, downloadedLocation);
@@ -47,7 +47,7 @@ export class ExtensionManagementService extends BaseExtensionManagementService {
const manifest = await this.getManifest(vsix);
if (manifest) {
await this.checkForWorkspaceTrust(manifest);
return server.extensionManagementService.install(vsix);
return server.extensionManagementService.install(vsix, options);
}
return Promise.reject('Unable to get the extension manifest.');

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionManagementService, ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementService, ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation, InstallOptions, InstallVSIXOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { URI } from 'vs/base/common/uri';
import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
@@ -41,8 +41,8 @@ export class NativeRemoteExtensionManagementService extends WebRemoteExtensionMa
this.localExtensionManagementService = localExtensionManagementServer.extensionManagementService;
}
override async install(vsix: URI): Promise<ILocalExtension> {
const local = await super.install(vsix);
override async install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension> {
const local = await super.install(vsix, options);
await this.installUIDependenciesAndPackedExtensions(local);
return local;
}

View File

@@ -55,6 +55,7 @@ export class TestExtensionEnablementService extends ExtensionEnablementService {
const storageService = createStorageService(instantiationService);
const extensionManagementService = instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService, { onDidInstallExtension: new Emitter<DidInstallExtensionEvent>().event, onDidUninstallExtension: new Emitter<DidUninstallExtensionEvent>().event } as IExtensionManagementService);
const extensionManagementServerService = instantiationService.get(IExtensionManagementServerService) || instantiationService.stub(IExtensionManagementServerService, <IExtensionManagementServerService>{ localExtensionManagementServer: { extensionManagementService } });
const workspaceTrustManagementService = instantiationService.get(IWorkspaceTrustManagementService) || instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService());
super(
storageService,
new GlobalExtensionEnablementService(storageService),
@@ -69,9 +70,9 @@ export class TestExtensionEnablementService extends ExtensionEnablementService {
instantiationService.get(INotificationService) || instantiationService.stub(INotificationService, new TestNotificationService()),
instantiationService.get(IHostService),
new class extends mock<IExtensionBisectService>() { override isDisabledByBisect() { return false; } },
instantiationService.get(IWorkspaceTrustManagementService) || instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService()),
workspaceTrustManagementService,
new class extends mock<IWorkspaceTrustRequestService>() { override requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise<boolean> { return Promise.resolve(true); } },
instantiationService.get(IExtensionManifestPropertiesService) || instantiationService.stub(IExtensionManifestPropertiesService, new ExtensionManifestPropertiesService(TestProductService, new TestConfigurationService()))
instantiationService.get(IExtensionManifestPropertiesService) || instantiationService.stub(IExtensionManifestPropertiesService, new ExtensionManifestPropertiesService(TestProductService, new TestConfigurationService(), workspaceTrustManagementService))
);
}

View File

@@ -48,6 +48,7 @@ export interface IExtensionRecommendationsService {
getRecommendedExtensionsByScenario(scenarioType: string): Promise<IExtensionRecommendation[]>; // {{SQL CARBON EDIT}}
promptRecommendedExtensionsByScenario(scenarioType: string): void; // {{SQL CARBON EDIT}}
getLanguageRecommendations(): string[];
}
export type IgnoredRecommendationChangeNotification = {

View File

@@ -185,8 +185,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
this._remoteAgentService.getEnvironment(),
this._remoteAgentService.scanExtensions()
]);
localExtensions = this._checkEnabledAndProposedAPI(localExtensions);
remoteExtensions = this._checkEnabledAndProposedAPI(remoteExtensions);
localExtensions = this._checkEnabledAndProposedAPI(localExtensions, false);
remoteExtensions = this._checkEnabledAndProposedAPI(remoteExtensions, false);
const remoteAgentConnection = this._remoteAgentService.getConnection();
this._runningLocation = this._runningLocationClassifier.determineRunningLocation(localExtensions, remoteExtensions);

View File

@@ -8,7 +8,7 @@ import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/li
import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
@@ -263,7 +263,13 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
// Extension is not installed
else {
const galleryExtension = await this.galleryService.getCompatibleExtension(extensionIdentifier);
let galleryExtension: IGalleryExtension | undefined;
try {
galleryExtension = await this.galleryService.getCompatibleExtension(extensionIdentifier) ?? undefined;
} catch (err) {
return;
}
if (!galleryExtension) {
return;
@@ -285,7 +291,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
await this.progressService.withProgress({
location: ProgressLocation.Notification,
title: localize('Installing', "Installing Extension '{0}'...", galleryExtension.displayName || galleryExtension.name)
}, () => this.extensionManagementService.installFromGallery(galleryExtension));
}, () => this.extensionManagementService.installFromGallery(galleryExtension!));
this.notificationService.prompt(
Severity.Info,

View File

@@ -361,8 +361,6 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
globalStorageHome: this._environmentService.globalStorageHome,
workspaceStorageHome: this._environmentService.workspaceStorageHome,
webviewResourceRoot: this._environmentService.webviewResourceRoot,
webviewCspSource: this._environmentService.webviewCspSource,
},
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : {
configuration: workspace.configuration || undefined,

View File

@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { Barrier } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import * as perf from 'vs/base/common/performance';
import { isEqualOrParent } from 'vs/base/common/resources';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@@ -51,7 +51,7 @@ export function parseScannedExtension(extension: ITranslatedScannedExtension): I
class DeltaExtensionsQueueItem {
constructor(
public readonly toAdd: IExtension[],
public readonly toRemove: string[]
public readonly toRemove: string[] | IExtension[]
) { }
}
@@ -68,6 +68,69 @@ export const enum ExtensionRunningPreference {
Remote
}
class LockCustomer {
public readonly promise: Promise<IDisposable>;
private _resolve!: (value: IDisposable) => void;
constructor(
public readonly name: string
) {
this.promise = new Promise<IDisposable>((resolve, reject) => {
this._resolve = resolve;
});
}
resolve(value: IDisposable): void {
this._resolve(value);
}
}
class Lock {
private readonly _pendingCustomers: LockCustomer[] = [];
private _isLocked = false;
public async acquire(customerName: string): Promise<IDisposable> {
const customer = new LockCustomer(customerName);
this._pendingCustomers.push(customer);
this._advance();
return customer.promise;
}
private _advance(): void {
if (this._isLocked) {
// cannot advance yet
return;
}
if (this._pendingCustomers.length === 0) {
// no more waiting customers
return;
}
const customer = this._pendingCustomers.shift()!;
this._isLocked = true;
let customerHoldsLock = true;
let logLongRunningCustomerTimeout = setTimeout(() => {
if (customerHoldsLock) {
console.warn(`The customer named ${customer.name} has been holding on to the lock for 30s. This might be a problem.`);
}
}, 30 * 1000 /* 30 seconds */);
const releaseLock = () => {
if (!customerHoldsLock) {
return;
}
clearTimeout(logLongRunningCustomerTimeout);
customerHoldsLock = false;
this._isLocked = false;
this._advance();
};
customer.resolve(toDisposable(releaseLock));
}
}
export abstract class AbstractExtensionService extends Disposable implements IExtensionService {
public _serviceBrand: undefined;
@@ -88,6 +151,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
public readonly onDidChangeResponsiveChange: Event<IResponsiveStateChangeEvent> = this._onDidChangeResponsiveChange.event;
protected readonly _registry: ExtensionDescriptionRegistry;
private readonly _registryLock: Lock;
private readonly _installedExtensionsReady: Barrier;
protected readonly _isDev: boolean;
private readonly _extensionsMessages: Map<string, IMessage[]>;
@@ -98,7 +163,6 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
private _deltaExtensionsQueue: DeltaExtensionsQueueItem[];
private _inHandleDeltaExtensions: boolean;
private readonly _onDidFinishHandleDeltaExtensions = this._register(new Emitter<void>());
protected _runningLocation: Map<string, ExtensionRunningLocation>;
@@ -130,6 +194,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
}));
this._registry = new ExtensionDescriptionRegistry([]);
this._registryLock = new Lock();
this._installedExtensionsReady = new Barrier();
this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;
this._extensionsMessages = new Map<string, IMessage[]>();
@@ -151,14 +217,14 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
this._register(this._extensionEnablementService.onEnablementChanged((extensions) => {
let toAdd: IExtension[] = [];
let toRemove: string[] = [];
let toRemove: IExtension[] = [];
for (const extension of extensions) {
if (this._safeInvokeIsEnabled(extension)) {
// an extension has been enabled
toAdd.push(extension);
} else {
// an extension has been disabled
toRemove.push(extension.identifier.id);
toRemove.push(extension);
}
}
this._handleDeltaExtensions(new DeltaExtensionsQueueItem(toAdd, toRemove));
@@ -207,20 +273,23 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
return;
}
while (this._deltaExtensionsQueue.length > 0) {
const item = this._deltaExtensionsQueue.shift()!;
try {
this._inHandleDeltaExtensions = true;
let lock: IDisposable | null = null;
try {
this._inHandleDeltaExtensions = true;
lock = await this._registryLock.acquire('handleDeltaExtensions');
while (this._deltaExtensionsQueue.length > 0) {
const item = this._deltaExtensionsQueue.shift()!;
await this._deltaExtensions(item.toAdd, item.toRemove);
} finally {
this._inHandleDeltaExtensions = false;
}
} finally {
this._inHandleDeltaExtensions = false;
if (lock) {
lock.dispose();
}
}
this._onDidFinishHandleDeltaExtensions.fire();
}
private async _deltaExtensions(_toAdd: IExtension[], _toRemove: string[]): Promise<void> {
private async _deltaExtensions(_toAdd: IExtension[], _toRemove: string[] | IExtension[]): Promise<void> {
let toAdd: IExtensionDescription[] = [];
for (let i = 0, len = _toAdd.length; i < len; i++) {
const extension = _toAdd[i];
@@ -240,13 +309,20 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
let toRemove: IExtensionDescription[] = [];
for (let i = 0, len = _toRemove.length; i < len; i++) {
const extensionId = _toRemove[i];
const extensionOrId = _toRemove[i];
const extensionId = (typeof extensionOrId === 'string' ? extensionOrId : extensionOrId.identifier.id);
const extension = (typeof extensionOrId === 'string' ? null : extensionOrId);
const extensionDescription = this._registry.getExtensionDescription(extensionId);
if (!extensionDescription) {
// ignore disabling/uninstalling an extension which is not running
continue;
}
if (extension && extensionDescription.extensionLocation.scheme !== extension.location.scheme) {
// this event is for a different extension than mine (maybe for the local extension, while I have the remote extension)
continue;
}
if (!this.canRemoveExtension(extensionDescription)) {
// uses non-dynamic extension point or is activated
continue;
@@ -559,11 +635,17 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
public async startExtensionHosts(): Promise<void> {
this.stopExtensionHosts();
if (this._inHandleDeltaExtensions) {
await Event.toPromise(this._onDidFinishHandleDeltaExtensions.event);
}
const lock = await this._registryLock.acquire('startExtensionHosts');
try {
this._startExtensionHosts(false, Array.from(this._allRequestedActivateEvents.keys()));
this._startExtensionHosts(false, Array.from(this._allRequestedActivateEvents.keys()));
const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess);
if (localProcessExtensionHost) {
await localProcessExtensionHost.ready();
}
} finally {
lock.dispose();
}
}
public async restartExtensionHost(): Promise<void> {
@@ -680,15 +762,23 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
}
}
protected _checkEnabledAndProposedAPI(extensions: IExtensionDescription[]): IExtensionDescription[] {
/**
* @argument extensions The extensions to be checked.
* @argument ignoreWorkspaceTrust Do not take workspace trust into account.
*/
protected _checkEnabledAndProposedAPI(extensions: IExtensionDescription[], ignoreWorkspaceTrust: boolean): IExtensionDescription[] {
// enable or disable proposed API per extension
this._checkEnableProposedApi(extensions);
// keep only enabled extensions
return extensions.filter(extension => this._isEnabled(extension));
return extensions.filter(extension => this._isEnabled(extension, ignoreWorkspaceTrust));
}
protected _isEnabled(extension: IExtensionDescription): boolean {
/**
* @argument extension The extension to be checked.
* @argument ignoreWorkspaceTrust Do not take workspace trust into account.
*/
protected _isEnabled(extension: IExtensionDescription, ignoreWorkspaceTrust: boolean): boolean {
if (extension.isUnderDevelopment) {
// Never disable extensions under development
return true;
@@ -699,7 +789,20 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
return false;
}
return this._safeInvokeIsEnabled(toExtension(extension));
const ext = toExtension(extension);
const isEnabled = this._safeInvokeIsEnabled(ext);
if (isEnabled) {
return true;
}
if (ignoreWorkspaceTrust && this._safeInvokeIsDisabledByWorkspaceTrust(ext)) {
// This extension is disabled, but the reason for it being disabled
// is workspace trust, so we will consider it enabled
return true;
}
return false;
}
protected _safeInvokeIsEnabled(extension: IExtension): boolean {
@@ -710,6 +813,14 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
}
}
protected _safeInvokeIsDisabledByWorkspaceTrust(extension: IExtension): boolean {
try {
return this._extensionEnablementService.isDisabledByWorkspaceTrust(extension);
} catch (err) {
return false;
}
}
protected _doHandleExtensionPoints(affectedExtensions: IExtensionDescription[]): void {
const affectedExtensionPoints: { [extPointName: string]: boolean; } = Object.create(null);
for (let extensionDescription of affectedExtensions) {

View File

@@ -36,6 +36,7 @@ export class ExtensionHostMain {
private _isTerminating: boolean;
private readonly _hostUtils: IHostUtils;
private readonly _rpcProtocol: RPCProtocol;
private readonly _extensionService: IExtHostExtensionService;
private readonly _logService: ILogService;
private readonly _disposables = new DisposableStore();
@@ -48,15 +49,15 @@ export class ExtensionHostMain {
) {
this._isTerminating = false;
this._hostUtils = hostUtils;
const rpcProtocol = new RPCProtocol(protocol, null, uriTransformer);
this._rpcProtocol = new RPCProtocol(protocol, null, uriTransformer);
// ensure URIs are transformed and revived
initData = ExtensionHostMain._transform(initData, rpcProtocol);
initData = ExtensionHostMain._transform(initData, this._rpcProtocol);
// bootstrap services
const services = new ServiceCollection(...getSingletonServiceDescriptors());
services.set(IExtHostInitDataService, { _serviceBrand: undefined, ...initData });
services.set(IExtHostRpcService, new ExtHostRpcService(rpcProtocol));
services.set(IExtHostRpcService, new ExtHostRpcService(this._rpcProtocol));
services.set(IURITransformerService, new URITransformerService(uriTransformer));
services.set(IHostUtils, hostUtils);
@@ -99,8 +100,8 @@ export class ExtensionHostMain {
};
});
const mainThreadExtensions = rpcProtocol.getProxy(MainContext.MainThreadExtensionService);
const mainThreadErrors = rpcProtocol.getProxy(MainContext.MainThreadErrors);
const mainThreadExtensions = this._rpcProtocol.getProxy(MainContext.MainThreadExtensionService);
const mainThreadErrors = this._rpcProtocol.getProxy(MainContext.MainThreadErrors);
errors.setUnexpectedErrorHandler(err => {
const data = errors.transformErrorForSerialization(err);
const extension = extensionErrors.get(err);
@@ -124,9 +125,12 @@ export class ExtensionHostMain {
this._disposables.dispose();
errors.setUnexpectedErrorHandler((err) => {
// TODO: write to log once we have one
this._logService.error(err);
});
// Invalidate all proxies
this._rpcProtocol.dispose();
const extensionsDeactivated = this._extensionService.deactivateAll();
// Give extensions 1 second to wrap up any async dispose, then exit in at most 4 seconds

View File

@@ -24,6 +24,7 @@ import { IExtensionHost, ExtensionHostKind, ActivationKind } from 'vs/workbench/
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { timeout } from 'vs/base/common/async';
import { URI } from 'vs/base/common/uri';
// Enable to see detailed message communication between window and extension host
const LOG_EXTENSION_HOST_COMMUNICATION = false;
@@ -132,6 +133,10 @@ export class ExtensionHostManager extends Disposable {
return p.value;
}
public async ready(): Promise<void> {
await this._getProxy();
}
private async _measureLatency(proxy: ExtHostExtensionServiceShape): Promise<number> {
const COUNT = 10;
@@ -287,6 +292,15 @@ export class ExtensionHostManager extends Disposable {
}
}
public async getCanonicalURI(remoteAuthority: string, uri: URI): Promise<URI> {
const proxy = await this._getProxy();
if (!proxy) {
throw new Error(`Cannot resolve canonical URI`);
}
const result = await proxy.$getCanonicalURI(remoteAuthority, uri);
return URI.revive(result);
}
public async start(enabledExtensionIds: ExtensionIdentifier[]): Promise<void> {
const proxy = await this._getProxy();
if (!proxy) {

View File

@@ -1,10 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IExtensionManifest, ExtensionKind, ExtensionIdentifier, ExtensionUntrustedWorkpaceSupportType } from 'vs/platform/extensions/common/extensions';
import { IExtensionManifest, ExtensionKind, ExtensionIdentifier, ExtensionUntrustedWorkpaceSupportType, ExtensionVirtualWorkpaceSupportType } from 'vs/platform/extensions/common/extensions';
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { isNonEmptyArray } from 'vs/base/common/arrays';
@@ -13,7 +13,9 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ExtensionUntrustedWorkspaceSupport } from 'vs/base/common/product';
import { Disposable } from 'vs/base/common/lifecycle';
import { isWorkspaceTrustEnabled, WORKSPACE_TRUST_EXTENSION_SUPPORT } from 'vs/workbench/services/workspaces/common/workspaceTrust';
import { WORKSPACE_TRUST_EXTENSION_SUPPORT } from 'vs/workbench/services/workspaces/common/workspaceTrust';
import { isBoolean } from 'vs/base/common/types';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
export const IExtensionManifestPropertiesService = createDecorator<IExtensionManifestPropertiesService>('extensionManifestPropertiesService');
@@ -30,7 +32,7 @@ export interface IExtensionManifestPropertiesService {
getExtensionKind(manifest: IExtensionManifest): ExtensionKind[];
getExtensionUntrustedWorkspaceSupportType(manifest: IExtensionManifest): ExtensionUntrustedWorkpaceSupportType;
canSupportVirtualWorkspace(manifest: IExtensionManifest): boolean;
getExtensionVirtualWorkspaceSupportType(manifest: IExtensionManifest): ExtensionVirtualWorkpaceSupportType;
}
export class ExtensionManifestPropertiesService extends Disposable implements IExtensionManifestPropertiesService {
@@ -50,6 +52,7 @@ export class ExtensionManifestPropertiesService extends Disposable implements IE
constructor(
@IProductService private readonly productService: IProductService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService
) {
super();
@@ -123,7 +126,7 @@ export class ExtensionManifestPropertiesService extends Disposable implements IE
getExtensionUntrustedWorkspaceSupportType(manifest: IExtensionManifest): ExtensionUntrustedWorkpaceSupportType {
// Workspace trust feature is disabled, or extension has no entry point
if (!isWorkspaceTrustEnabled(this.configurationService) || !manifest.main) {
if (!this.workspaceTrustManagementService.workspaceTrustEnabled || !manifest.main) {
return true;
}
@@ -156,7 +159,7 @@ export class ExtensionManifestPropertiesService extends Disposable implements IE
return false;
}
canSupportVirtualWorkspace(manifest: IExtensionManifest): boolean {
getExtensionVirtualWorkspaceSupportType(manifest: IExtensionManifest): ExtensionVirtualWorkpaceSupportType {
// check user configured
const userConfiguredVirtualWorkspaceSupport = this.getConfiguredVirtualWorkspaceSupport(manifest);
if (userConfiguredVirtualWorkspaceSupport !== undefined) {
@@ -171,8 +174,14 @@ export class ExtensionManifestPropertiesService extends Disposable implements IE
}
// check the manifest
if (manifest.capabilities?.virtualWorkspaces !== undefined) {
return manifest.capabilities?.virtualWorkspaces;
const virtualWorkspaces = manifest.capabilities?.virtualWorkspaces;
if (isBoolean(virtualWorkspaces)) {
return virtualWorkspaces;
} else if (virtualWorkspaces) {
const supported = virtualWorkspaces.supported;
if (isBoolean(supported) || supported === 'limited') {
return supported;
}
}
// check default from product

View File

@@ -320,6 +320,16 @@ export const schema: IJSONSchema = {
body: 'onAuthenticationRequest:${11:authenticationProviderId}',
description: nls.localize('vscode.extension.activationEvents.onAuthenticationRequest', 'An activation event emitted whenever sessions are requested from the specified authentication provider.')
},
{
label: 'onRenderer',
description: nls.localize('vscode.extension.activationEvents.onRenderer', 'An activation event emitted whenever a notebook output renderer is used.'),
body: 'onRenderer:${11:rendererId}'
},
{
label: 'onTerminalProfile',
body: 'onTerminalProfile:${1:terminalType}',
description: nls.localize('vscode.extension.activationEvents.onTerminalProfile', 'An activation event emitted when a specific terminal profile is launched.'),
},
{
label: '*',
description: nls.localize('vscode.extension.activationEvents.star', 'An activation event emitted on VS Code startup. To ensure a great end user experience, please use this activation event in your extension only when no other activation events combination works in your use-case.'),
@@ -421,8 +431,28 @@ export const schema: IJSONSchema = {
properties: {
virtualWorkspaces: {
description: nls.localize('vscode.extension.capabilities.virtualWorkspaces', "Declares whether the extension should be enabled in virtual workspaces. A virtual workspace is a workspace which is not backed by any on-disk resources. When false, this extension will be automatically disabled in virtual workspaces. Default is true."),
type: 'boolean',
default: true
type: ['boolean', 'object'],
defaultSnippets: [
{ label: 'limited', body: { supported: '${1:limited}', description: '${2}' } },
{ label: 'false', body: { supported: false, description: '${2}' } },
],
default: true.valueOf,
properties: {
supported: {
markdownDescription: nls.localize('vscode.extension.capabilities.virtualWorkspaces.supported', "Declares the level of support for virtual workspaces by the extension."),
type: ['string', 'boolean'],
enum: ['limited', true, false],
enumDescriptions: [
nls.localize('vscode.extension.capabilities.virtualWorkspaces.supported.limited', "The extension will be enabled in virtual workspaces with some functionality disabled."),
nls.localize('vscode.extension.capabilities.virtualWorkspaces.supported.true', "The extension will be enabled in virtual workspaces with all functionality enabled."),
nls.localize('vscode.extension.capabilities.virtualWorkspaces.supported.false', "The extension will not be enabled in virtual workspaces."),
]
},
description: {
type: 'string',
markdownDescription: nls.localize('vscode.extension.capabilities.virtualWorkspaces.description', "A description of how virtual workspaces affects the extensions behavior and why it is needed. This only applies when `supported` is not `true`."),
}
}
},
untrustedWorkspaces: {
description: nls.localize('vscode.extension.capabilities.untrustedWorkspaces', 'Declares how the extension should be handled in untrusted workspaces.'),

View File

@@ -236,9 +236,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
globalStorageHome: remoteInitData.globalStorageHome,
workspaceStorageHome: remoteInitData.workspaceStorageHome,
webviewResourceRoot: this._environmentService.webviewResourceRoot,
webviewCspSource: this._environmentService.webviewCspSource,
workspaceStorageHome: remoteInitData.workspaceStorageHome
},
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : {
configuration: workspace.configuration,

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as nls from 'vs/nls';
import * as path from 'vs/base/common/path';
import * as errors from 'vs/base/common/errors';
@@ -69,9 +68,10 @@ export class CachedExtensionScanner {
const version = this._productService.version;
const commit = this._productService.commit;
const date = this._productService.date;
const devMode = !!process.env['VSCODE_DEV'];
const locale = platform.language;
const input = new ExtensionScannerInput(version, commit, locale, devMode, path, isBuiltin, false, translations);
const input = new ExtensionScannerInput(version, date, commit, locale, devMode, path, isBuiltin, false, translations);
return ExtensionScanner.scanSingleExtension(input, log);
}
@@ -130,7 +130,7 @@ export class CachedExtensionScanner {
}
try {
await pfs.rimraf(cacheFile, pfs.RimRafMode.MOVE);
await pfs.Promises.rm(cacheFile, pfs.RimRafMode.MOVE);
} catch (err) {
errors.onUnexpectedError(err);
console.error(err);
@@ -151,7 +151,7 @@ export class CachedExtensionScanner {
const cacheFile = path.join(cacheFolder, cacheKey);
try {
const cacheRawContents = await fs.promises.readFile(cacheFile, 'utf8');
const cacheRawContents = await pfs.Promises.readFile(cacheFile, 'utf8');
return JSON.parse(cacheRawContents);
} catch (err) {
// That's ok...
@@ -165,13 +165,13 @@ export class CachedExtensionScanner {
const cacheFile = path.join(cacheFolder, cacheKey);
try {
await fs.promises.mkdir(cacheFolder, { recursive: true });
await pfs.Promises.mkdir(cacheFolder, { recursive: true });
} catch (err) {
// That's ok...
}
try {
await pfs.writeFile(cacheFile, JSON.stringify(cacheContents));
await pfs.Promises.writeFile(cacheFile, JSON.stringify(cacheContents));
} catch (err) {
// That's ok...
}
@@ -184,7 +184,7 @@ export class CachedExtensionScanner {
}
try {
const folderStat = await fs.promises.stat(input.absoluteFolderPath);
const folderStat = await pfs.Promises.stat(input.absoluteFolderPath);
input.mtime = folderStat.mtime.getTime();
} catch (err) {
// That's ok...
@@ -224,7 +224,7 @@ export class CachedExtensionScanner {
private static async _readTranslationConfig(): Promise<Translations> {
if (platform.translationsConfigFile) {
try {
const content = await fs.promises.readFile(platform.translationsConfigFile, 'utf8');
const content = await pfs.Promises.readFile(platform.translationsConfigFile, 'utf8');
return JSON.parse(content) as Translations;
} catch (err) {
// no problemo
@@ -245,6 +245,7 @@ export class CachedExtensionScanner {
const version = productService.version;
const commit = productService.commit;
const date = productService.date;
const devMode = !!process.env['VSCODE_DEV'];
const locale = platform.language;
@@ -253,7 +254,7 @@ export class CachedExtensionScanner {
notificationService,
environmentService,
BUILTIN_MANIFEST_CACHE_FILE,
new ExtensionScannerInput(version, commit, locale, devMode, getSystemExtensionsRoot(), true, false, translations),
new ExtensionScannerInput(version, date, commit, locale, devMode, getSystemExtensionsRoot(), true, false, translations),
log
);
@@ -263,10 +264,10 @@ export class CachedExtensionScanner {
const builtInExtensions = Promise.resolve<IBuiltInExtension[]>(productService.builtInExtensions || []);
const controlFilePath = joinPath(environmentService.userHome, '.vscode-oss-dev', 'extensions', 'control.json').fsPath;
const controlFile = fs.promises.readFile(controlFilePath, 'utf8')
const controlFile = pfs.Promises.readFile(controlFilePath, 'utf8')
.then<IBuiltInExtensionControl>(raw => JSON.parse(raw), () => ({} as any));
const input = new ExtensionScannerInput(version, commit, locale, devMode, getExtraDevSystemExtensionsRoot(), true, false, translations);
const input = new ExtensionScannerInput(version, date, commit, locale, devMode, getExtraDevSystemExtensionsRoot(), true, false, translations);
const extraBuiltinExtensions = Promise.all([builtInExtensions, controlFile])
.then(([builtInExtensions, control]) => new ExtraBuiltInExtensionResolver(builtInExtensions, control))
.then(resolver => ExtensionScanner.scanExtensions(input, log, resolver));
@@ -279,7 +280,7 @@ export class CachedExtensionScanner {
notificationService,
environmentService,
USER_MANIFEST_CACHE_FILE,
new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionsPath, false, false, translations),
new ExtensionScannerInput(version, date, commit, locale, devMode, environmentService.extensionsPath, false, false, translations),
log
));
@@ -288,7 +289,7 @@ export class CachedExtensionScanner {
if (environmentService.isExtensionDevelopment && environmentService.extensionDevelopmentLocationURI) {
const extDescsP = environmentService.extensionDevelopmentLocationURI.filter(extLoc => extLoc.scheme === Schemas.file).map(extLoc => {
return ExtensionScanner.scanOneOrMultipleExtensions(
new ExtensionScannerInput(version, commit, locale, devMode, originalFSPath(extLoc), false, true, translations), log
new ExtensionScannerInput(version, date, commit, locale, devMode, originalFSPath(extLoc), false, true, translations), log
);
});
developedExtensions = Promise.all(extDescsP).then((extDescArrays: IExtensionDescription[][]) => {

View File

@@ -15,7 +15,7 @@ import { IWorkbenchExtensionEnablementService, EnablementState, IWebExtensionsSc
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IRemoteExtensionHostDataProvider, RemoteExtensionHost, IRemoteExtensionHostInitData } from 'vs/workbench/services/extensions/common/remoteExtensionHost';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, RemoteTrustOption, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
@@ -42,12 +42,8 @@ import { Schemas } from 'vs/base/common/network';
import { ExtensionHostExitCode } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { updateProxyConfigurationsScope } from 'vs/platform/request/common/request';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { Codicon } from 'vs/base/common/codicons';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
const MACHINE_PROMPT = false;
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
export class ExtensionService extends AbstractExtensionService implements IExtensionService {
@@ -75,7 +71,6 @@ export class ExtensionService extends AbstractExtensionService implements IExten
@IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService,
@IExtensionGalleryService private readonly _extensionGalleryService: IExtensionGalleryService,
@ILogService private readonly _logService: ILogService,
@IDialogService private readonly _dialogService: IDialogService,
@IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService,
@IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService,
) {
@@ -144,7 +139,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
return {
getInitData: async () => {
if (isInitialStart) {
const localExtensions = this._checkEnabledAndProposedAPI(await this._scanAllLocalExtensions());
// Here we load even extensions that would be disabled by workspace trust
const localExtensions = this._checkEnabledAndProposedAPI(await this._scanAllLocalExtensions(), /* ignore workspace trust */true);
const runningLocation = this._runningLocationClassifier.determineRunningLocation(localExtensions, []);
const localProcessExtensions = filterByRunningLocation(localExtensions, runningLocation, desiredRunningLocation);
return {
@@ -343,11 +339,24 @@ export class ExtensionService extends AbstractExtensionService implements IExten
const remoteAuthority = this._environmentService.remoteAuthority;
const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess)!;
const localExtensions = this._checkEnabledAndProposedAPI(await this._scanAllLocalExtensions());
let remoteEnv: IRemoteAgentEnvironment | null = null;
let remoteExtensions: IExtensionDescription[] = [];
if (remoteAuthority) {
this._remoteAuthorityResolverService._setCanonicalURIProvider(async (uri) => {
if (uri.scheme !== Schemas.vscodeRemote || uri.authority !== remoteAuthority) {
// The current remote authority resolver cannot give the canonical URI for this URI
return uri;
}
const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess)!;
return localProcessExtensionHost.getCanonicalURI(remoteAuthority, uri);
});
// Now that the canonical URI provider has been registered, we need to wait for the trust state to be
// calculated. The trust state will be used while resolving the authority, however the resolver can
// override the trust state through the resolver result.
await this._workspaceTrustManagementService.workspaceResolved;
let resolverResult: ResolverResult;
try {
@@ -364,42 +373,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten
this._remoteAuthorityResolverService._setResolvedAuthorityError(remoteAuthority, err);
// Proceed with the local extension host
await this._startLocalExtensionHost(localExtensions);
await this._startLocalExtensionHost();
return;
}
let promptForMachineTrust = MACHINE_PROMPT;
if (resolverResult.options?.trust === RemoteTrustOption.DisableTrust) {
promptForMachineTrust = false;
this._workspaceTrustManagementService.setWorkspaceTrust(true);
} else if (resolverResult.options?.trust === RemoteTrustOption.MachineTrusted) {
promptForMachineTrust = false;
}
if (promptForMachineTrust) {
const dialogResult = await this._dialogService.show(
Severity.Info,
nls.localize('machineTrustQuestion', "Do you trust the machine you're connecting to?"),
[nls.localize('yes', "Yes, connect."), nls.localize('no', "No, do not connect.")],
{
cancelId: 1,
custom: {
icon: Codicon.remoteExplorer
},
// checkbox: { label: nls.localize('remember', "Remember my choice"), checked: true }
}
);
if (dialogResult.choice !== 0) {
// Did not confirm trust
this._notificationService.notify({ severity: Severity.Warning, message: nls.localize('trustFailure', "Refused to connect to untrusted machine.") });
// Proceed with the local extension host
await this._startLocalExtensionHost(localExtensions);
return;
}
}
// set the resolved authority
this._remoteAuthorityResolverService._setResolvedAuthority(resolverResult.authority, resolverResult.options);
this._remoteExplorerService.setTunnelInformation(resolverResult.tunnelInformation);
@@ -420,23 +397,31 @@ export class ExtensionService extends AbstractExtensionService implements IExten
this._remoteAgentService.getEnvironment(),
this._remoteAgentService.scanExtensions()
]);
remoteExtensions = this._checkEnabledAndProposedAPI(remoteExtensions);
if (!remoteEnv) {
this._notificationService.notify({ severity: Severity.Error, message: nls.localize('getEnvironmentFailure', "Could not fetch remote environment") });
// Proceed with the local extension host
await this._startLocalExtensionHost(localExtensions);
await this._startLocalExtensionHost();
return;
}
updateProxyConfigurationsScope(remoteEnv.useHostProxy ? ConfigurationScope.APPLICATION : ConfigurationScope.MACHINE);
} else {
this._remoteAuthorityResolverService._setCanonicalURIProvider(async (uri) => uri);
}
await this._startLocalExtensionHost(localExtensions, remoteAuthority, remoteEnv, remoteExtensions);
await this._startLocalExtensionHost(remoteAuthority, remoteEnv, remoteExtensions);
}
private async _startLocalExtensionHost(localExtensions: IExtensionDescription[], remoteAuthority: string | undefined = undefined, remoteEnv: IRemoteAgentEnvironment | null = null, remoteExtensions: IExtensionDescription[] = []): Promise<void> {
private async _startLocalExtensionHost(remoteAuthority: string | undefined = undefined, remoteEnv: IRemoteAgentEnvironment | null = null, remoteExtensions: IExtensionDescription[] = []): Promise<void> {
// Ensure that the workspace trust state has been fully initialized so
// that the extension host can start with the correct set of extensions.
await this._workspaceTrustManagementService.workspaceTrustInitialized;
remoteExtensions = this._checkEnabledAndProposedAPI(remoteExtensions, false);
const localExtensions = this._checkEnabledAndProposedAPI(await this._scanAllLocalExtensions(), false);
this._runningLocation = this._runningLocationClassifier.determineRunningLocation(localExtensions, remoteExtensions);
// remove non-UI extensions from the local extensions
@@ -516,7 +501,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
const allExtensions = await this._scanAllLocalExtensions();
const extension = allExtensions.filter(e => e.identifier.value === resolverExtensionId)[0];
if (extension) {
if (!this._isEnabled(extension)) {
if (!this._isEnabled(extension, false)) {
const message = nls.localize('enableResolver', "Extension '{0}' is required to open the remote window.\nOK to enable?", recommendation.friendlyName);
this._notificationService.prompt(Severity.Info, message,
[{

View File

@@ -180,9 +180,9 @@ export class LocalProcessExtensionHost implements IExtensionHost {
}
if (this._isExtensionDevHost) {
// Unset `VSCODE_NODE_CACHED_DATA_DIR` when developing extensions because it might
// Unset `VSCODE_CODE_CACHE_PATH` when developing extensions because it might
// be that dependencies, that otherwise would be cached, get modified.
delete env['VSCODE_NODE_CACHED_DATA_DIR'];
delete env['VSCODE_CODE_CACHE_PATH'];
}
const opts = {
@@ -477,8 +477,6 @@ export class LocalProcessExtensionHost implements IExtensionHost {
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
globalStorageHome: this._environmentService.globalStorageHome,
workspaceStorageHome: this._environmentService.workspaceStorageHome,
webviewResourceRoot: this._environmentService.webviewResourceRoot,
webviewCspSource: this._environmentService.webviewCspSource,
},
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : {
configuration: withNullAsUndefined(workspace.configuration),
@@ -628,6 +626,8 @@ export class LocalProcessExtensionHost implements IExtensionHost {
// (graceful termination)
protocol.send(createMessageOfType(MessageType.Terminate));
protocol.getSocket().dispose();
protocol.dispose();
// Give the extension host 10s, after which we will

View File

@@ -18,7 +18,7 @@ import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessag
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';
import { exists } from 'vs/base/node/pfs';
import { Promises } from 'vs/base/node/pfs';
import { realpath } from 'vs/base/node/extpath';
import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService';
import { RunOnceScheduler } from 'vs/base/common/async';
@@ -173,6 +173,9 @@ function _createExtHostProtocol(): Promise<PersistentProtocol> {
});
socket.once('error', reject);
socket.on('close', () => {
onTerminate('renderer closed the socket');
});
});
}
}
@@ -323,7 +326,7 @@ export async function startExtensionHostProcess(): Promise<void> {
const hostUtils = new class NodeHost implements IHostUtils {
declare readonly _serviceBrand: undefined;
exit(code: number) { nativeExit(code); }
exists(path: string) { return exists(path); }
exists(path: string) { return Promises.exists(path); }
realpath(path: string) { return realpath(path); }
};

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as nls from 'vs/nls';
import * as path from 'vs/base/common/path';
import * as semver from 'vs/base/common/semver/semver';
@@ -30,14 +29,16 @@ export interface NlsConfiguration {
abstract class ExtensionManifestHandler {
protected readonly _ourVersion: string;
protected readonly _ourProductDate: string | undefined;
protected readonly _log: ILog;
protected readonly _absoluteFolderPath: string;
protected readonly _isBuiltin: boolean;
protected readonly _isUnderDevelopment: boolean;
protected readonly _absoluteManifestPath: string;
constructor(ourVersion: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean) {
constructor(ourVersion: string, ourProductDate: string | undefined, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean) {
this._ourVersion = ourVersion;
this._ourProductDate = ourProductDate;
this._log = log;
this._absoluteFolderPath = absoluteFolderPath;
this._isBuiltin = isBuiltin;
@@ -58,7 +59,7 @@ class ExtensionManifestParser extends ExtensionManifestHandler {
}
public parse(): Promise<IExtensionDescription> {
return fs.promises.readFile(this._absoluteManifestPath).then((manifestContents) => {
return pfs.Promises.readFile(this._absoluteManifestPath).then((manifestContents) => {
const errors: json.ParseError[] = [];
const manifest = ExtensionManifestParser._fastParseJSON(manifestContents.toString(), errors);
if (json.getNodeType(manifest) !== 'object') {
@@ -94,8 +95,8 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
private readonly _nlsConfig: NlsConfiguration;
constructor(ourVersion: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration) {
super(ourVersion, log, absoluteFolderPath, isBuiltin, isUnderDevelopment);
constructor(ourVersion: string, ourProductDate: string | undefined, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration) {
super(ourVersion, ourProductDate, log, absoluteFolderPath, isBuiltin, isUnderDevelopment);
this._nlsConfig = nlsConfig;
}
@@ -131,7 +132,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
let translationPath = this._nlsConfig.translations[translationId];
let localizedMessages: Promise<LocalizedMessages | undefined>;
if (translationPath) {
localizedMessages = fs.promises.readFile(translationPath, 'utf8').then<LocalizedMessages, LocalizedMessages>((content) => {
localizedMessages = pfs.Promises.readFile(translationPath, 'utf8').then<LocalizedMessages, LocalizedMessages>((content) => {
let errors: json.ParseError[] = [];
let translationBundle: TranslationBundle = json.parse(content, errors);
if (errors.length > 0) {
@@ -156,7 +157,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
if (!messageBundle.localized) {
return { values: undefined, default: messageBundle.original };
}
return fs.promises.readFile(messageBundle.localized, 'utf8').then(messageBundleContent => {
return pfs.Promises.readFile(messageBundle.localized, 'utf8').then(messageBundleContent => {
let errors: json.ParseError[] = [];
let messages: MessageBag = json.parse(messageBundleContent, errors);
if (errors.length > 0) {
@@ -205,7 +206,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
private static resolveOriginalMessageBundle(originalMessageBundle: string | null, errors: json.ParseError[]) {
return new Promise<{ [key: string]: string; } | null>((c, e) => {
if (originalMessageBundle) {
fs.promises.readFile(originalMessageBundle).then(originalBundleContent => {
pfs.Promises.readFile(originalMessageBundle).then(originalBundleContent => {
c(json.parse(originalBundleContent.toString(), errors));
}, (err) => {
c(null);
@@ -321,7 +322,7 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
extensionDescription.isUnderDevelopment = this._isUnderDevelopment;
let notices: string[] = [];
if (!ExtensionManifestValidator.isValidExtensionDescription(this._ourVersion, this._absoluteFolderPath, extensionDescription, notices)) {
if (!ExtensionManifestValidator.isValidExtensionDescription(this._ourVersion, this._ourProductDate, this._absoluteFolderPath, extensionDescription, notices)) {
notices.forEach((error) => {
this._log.error(this._absoluteFolderPath, error);
});
@@ -347,7 +348,7 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
return extensionDescription;
}
private static isValidExtensionDescription(version: string, extensionFolderPath: string, extensionDescription: IExtensionDescription, notices: string[]): boolean {
private static isValidExtensionDescription(version: string, productDate: string | undefined, extensionFolderPath: string, extensionDescription: IExtensionDescription, notices: string[]): boolean {
if (!ExtensionManifestValidator.baseIsValidExtensionDescription(extensionFolderPath, extensionDescription, notices)) {
return false;
@@ -358,7 +359,7 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
return false;
}
return isValidExtensionVersion(version, extensionDescription, notices);
return isValidExtensionVersion(version, productDate, extensionDescription, notices);
}
private static baseIsValidExtensionDescription(extensionFolderPath: string, extensionDescription: IExtensionDescription, notices: string[]): boolean {
@@ -456,6 +457,7 @@ export class ExtensionScannerInput {
constructor(
public readonly ourVersion: string,
public readonly ourProductDate: string | undefined,
public readonly commit: string | undefined,
public readonly locale: string | undefined,
public readonly devMode: boolean,
@@ -479,6 +481,7 @@ export class ExtensionScannerInput {
public static equals(a: ExtensionScannerInput, b: ExtensionScannerInput): boolean {
return (
a.ourVersion === b.ourVersion
&& a.ourProductDate === b.ourProductDate
&& a.commit === b.commit
&& a.locale === b.locale
&& a.devMode === b.devMode
@@ -505,7 +508,7 @@ class DefaultExtensionResolver implements IExtensionResolver {
constructor(private root: string) { }
resolveExtensions(): Promise<IExtensionReference[]> {
return pfs.readDirsInDir(this.root)
return pfs.Promises.readDirsInDir(this.root)
.then(folders => folders.map(name => ({ name, path: path.join(this.root, name) })));
}
}
@@ -515,23 +518,23 @@ export class ExtensionScanner {
/**
* Read the extension defined in `absoluteFolderPath`
*/
private static scanExtension(version: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration): Promise<IExtensionDescription | null> {
private static scanExtension(version: string, productDate: string | undefined, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration): Promise<IExtensionDescription | null> {
absoluteFolderPath = path.normalize(absoluteFolderPath);
let parser = new ExtensionManifestParser(version, log, absoluteFolderPath, isBuiltin, isUnderDevelopment);
let parser = new ExtensionManifestParser(version, productDate, log, absoluteFolderPath, isBuiltin, isUnderDevelopment);
return parser.parse().then<IExtensionDescription | null>((extensionDescription) => {
if (extensionDescription === null) {
return null;
}
let nlsReplacer = new ExtensionManifestNLSReplacer(version, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig);
let nlsReplacer = new ExtensionManifestNLSReplacer(version, productDate, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig);
return nlsReplacer.replaceNLS(extensionDescription);
}).then((extensionDescription) => {
if (extensionDescription === null) {
return null;
}
let validator = new ExtensionManifestValidator(version, log, absoluteFolderPath, isBuiltin, isUnderDevelopment);
let validator = new ExtensionManifestValidator(version, productDate, log, absoluteFolderPath, isBuiltin, isUnderDevelopment);
return validator.validate(extensionDescription);
});
}
@@ -552,7 +555,7 @@ export class ExtensionScanner {
let obsolete: { [folderName: string]: boolean; } = {};
if (!isBuiltin) {
try {
const obsoleteFileContents = await fs.promises.readFile(path.join(absoluteFolderPath, '.obsolete'), 'utf8');
const obsoleteFileContents = await pfs.Promises.readFile(path.join(absoluteFolderPath, '.obsolete'), 'utf8');
obsolete = JSON.parse(obsoleteFileContents);
} catch (err) {
// Don't care
@@ -569,7 +572,7 @@ export class ExtensionScanner {
}
const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
let _extensionDescriptions = await Promise.all(refs.map(r => this.scanExtension(input.ourVersion, log, r.path, isBuiltin, isUnderDevelopment, nlsConfig)));
let _extensionDescriptions = await Promise.all(refs.map(r => this.scanExtension(input.ourVersion, input.ourProductDate, log, r.path, isBuiltin, isUnderDevelopment, nlsConfig)));
let extensionDescriptions = arrays.coalesce(_extensionDescriptions);
extensionDescriptions = extensionDescriptions.filter(item => item !== null && !obsolete[new ExtensionIdentifierWithVersion({ id: getGalleryExtensionId(item.publisher, item.name) }, item.version).key()]);
@@ -604,7 +607,7 @@ export class ExtensionScanner {
return pfs.SymlinkSupport.existsFile(path.join(absoluteFolderPath, MANIFEST_FILE)).then((exists) => {
if (exists) {
const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
return this.scanExtension(input.ourVersion, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig).then((extensionDescription) => {
return this.scanExtension(input.ourVersion, input.ourProductDate, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig).then((extensionDescription) => {
if (extensionDescription === null) {
return [];
}
@@ -623,7 +626,7 @@ export class ExtensionScanner {
const isBuiltin = input.isBuiltin;
const isUnderDevelopment = input.isUnderDevelopment;
const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
return this.scanExtension(input.ourVersion, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig);
return this.scanExtension(input.ourVersion, input.ourProductDate, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig);
}
public static mergeBuiltinExtensions(builtinExtensions: Promise<IExtensionDescription[]>, extraBuiltinExtensions: Promise<IExtensionDescription[]>): Promise<IExtensionDescription[]> {

View File

@@ -12,11 +12,13 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IProductService } from 'vs/platform/product/common/productService';
import { isWeb } from 'vs/base/common/platform';
import { TestWorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
suite('ExtensionManifestPropertiesService - ExtensionKind', () => {
function check(manifest: Partial<IExtensionManifest>, expected: ExtensionKind[]): void {
const extensionManifestPropertiesService = new ExtensionManifestPropertiesService(TestProductService, new TestConfigurationService());
const extensionManifestPropertiesService = new ExtensionManifestPropertiesService(TestProductService, new TestConfigurationService(), new TestWorkspaceTrustManagementService());
assert.deepStrictEqual(extensionManifestPropertiesService.deduceExtensionKind(<IExtensionManifest>manifest), expected);
}
@@ -80,6 +82,7 @@ if (!isWeb) {
test('test extension workspace trust request when main entry point is missing', () => {
instantiationService.stub(IProductService, <Partial<IProductService>>{});
instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService());
const extensionMaifest = getExtensionManifest();
assertUntrustedWorkspaceSupport(extensionMaifest, true);
@@ -87,7 +90,7 @@ if (!isWeb) {
test('test extension workspace trust request when workspace trust is disabled', async () => {
instantiationService.stub(IProductService, <Partial<IProductService>>{});
await testConfigurationService.setUserConfiguration('security', { workspace: { trust: { enabled: false } } });
instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService(false));
const extensionMaifest = getExtensionManifest({ main: './out/extension.js' });
assertUntrustedWorkspaceSupport(extensionMaifest, true);
@@ -95,37 +98,34 @@ if (!isWeb) {
test('test extension workspace trust request when override exists in settings.json', async () => {
instantiationService.stub(IProductService, <Partial<IProductService>>{});
instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService());
await testConfigurationService.setUserConfiguration('security', { workspace: { trust: { extensionUntrustedSupport: { 'pub.a': { supported: true } } } } });
await testConfigurationService.setUserConfiguration('extensions', { supportUntrustedWorkspaces: { 'pub.a': { supported: true } } });
const extensionMaifest = getExtensionManifest({ main: './out/extension.js', capabilities: { untrustedWorkspaces: { supported: 'limited' } } });
assertUntrustedWorkspaceSupport(extensionMaifest, true);
});
test('test extension workspace trust request when override for the version exists in settings.json', async () => {
instantiationService.stub(IProductService, <Partial<IProductService>>{});
instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService());
await testConfigurationService.setUserConfiguration('security', { workspace: { trust: { extensionUntrustedSupport: { 'pub.a': { supported: true, version: '1.0.0' } } } } });
await testConfigurationService.setUserConfiguration('extensions', { supportUntrustedWorkspaces: { 'pub.a': { supported: true, version: '1.0.0' } } });
const extensionMaifest = getExtensionManifest({ main: './out/extension.js', capabilities: { untrustedWorkspaces: { supported: 'limited' } } });
assertUntrustedWorkspaceSupport(extensionMaifest, true);
});
test('test extension workspace trust request when override for a different version exists in settings.json', async () => {
instantiationService.stub(IProductService, <Partial<IProductService>>{});
instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService());
await testConfigurationService.setUserConfiguration('security', {
workspace: {
trust: {
enabled: true,
extensionUntrustedSupport: { 'pub.a': { supported: true, version: '2.0.0' } }
}
}
});
await testConfigurationService.setUserConfiguration('extensions', { supportUntrustedWorkspaces: { 'pub.a': { supported: true, version: '2.0.0' } } });
const extensionMaifest = getExtensionManifest({ main: './out/extension.js', capabilities: { untrustedWorkspaces: { supported: 'limited' } } });
assertUntrustedWorkspaceSupport(extensionMaifest, 'limited');
});
test('test extension workspace trust request when default exists in product.json', () => {
instantiationService.stub(IProductService, <Partial<IProductService>>{ extensionUntrustedWorkspaceSupport: { 'pub.a': { default: true } } });
instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService());
const extensionMaifest = getExtensionManifest({ main: './out/extension.js' });
assertUntrustedWorkspaceSupport(extensionMaifest, true);
@@ -133,6 +133,7 @@ if (!isWeb) {
test('test extension workspace trust request when override exists in product.json', () => {
instantiationService.stub(IProductService, <Partial<IProductService>>{ extensionUntrustedWorkspaceSupport: { 'pub.a': { override: 'limited' } } });
instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService());
const extensionMaifest = getExtensionManifest({ main: './out/extension.js', capabilities: { untrustedWorkspaces: { supported: true } } });
assertUntrustedWorkspaceSupport(extensionMaifest, 'limited');
@@ -140,6 +141,7 @@ if (!isWeb) {
test('test extension workspace trust request when value exists in package.json', () => {
instantiationService.stub(IProductService, <Partial<IProductService>>{});
instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService());
const extensionMaifest = getExtensionManifest({ main: './out/extension.js', capabilities: { untrustedWorkspaces: { supported: 'limited' } } });
assertUntrustedWorkspaceSupport(extensionMaifest, 'limited');
@@ -147,6 +149,7 @@ if (!isWeb) {
test('test extension workspace trust request when no value exists in package.json', () => {
instantiationService.stub(IProductService, <Partial<IProductService>>{});
instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService());
const extensionMaifest = getExtensionManifest({ main: './out/extension.js' });
assertUntrustedWorkspaceSupport(extensionMaifest, false);

View File

@@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer';

View File

@@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';

View File

@@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer';

View File

@@ -7,7 +7,8 @@ import { localize } from 'vs/nls';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IEditor } from 'vs/editor/common/editorCommon';
import { ITextEditorOptions, IResourceEditorInput, TextEditorSelectionRevealType, IEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditorInput, IEditorPane, EditorExtensions, EditorInput, IEditorCloseEvent, IEditorInputFactoryRegistry, EditorResourceAccessor, IEditorIdentifier, GroupIdentifier, EditorsOrder, SideBySideEditor } from 'vs/workbench/common/editor';
import { IEditorInput, IEditorPane, EditorExtensions, IEditorCloseEvent, IEditorInputFactoryRegistry, EditorResourceAccessor, IEditorIdentifier, GroupIdentifier, EditorsOrder, SideBySideEditor } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { FileChangesEvent, IFileService, FileChangeType, FILES_EXCLUDE_CONFIG, FileOperationEvent, FileOperation } from 'vs/platform/files/common/files';
@@ -398,7 +399,7 @@ export class HistoryService extends Disposable implements IHistoryService {
return this.editorService.openEditor(location.input, options);
}
return this.editorService.openEditor({ resource: (location.input as IResourceEditorInput).resource, options });
return this.editorService.openEditor({ resource: location.input.resource, options });
}
private handleEditorEventInNavigationStack(control: IEditorPane | undefined, event?: ICursorPositionChangedEvent): void {
@@ -1011,7 +1012,7 @@ export class HistoryService extends Disposable implements IHistoryService {
const editorSerializer = this.editorInputFactory.getEditorInputSerializer(editorInputJSON.typeId);
if (editorSerializer) {
const input = editorSerializer.deserialize(this.instantiationService, editorInputJSON.deserialized);
if (input) {
if (input instanceof EditorInput) {
this.onEditorDispose(input, () => this.removeFromHistory(input), this.editorHistoryListeners);
}

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { EditorOptions } from 'vs/workbench/common/editor';
import { URI } from 'vs/base/common/uri';
import { workbenchInstantiationService, TestFileEditorInput, registerTestEditor, createEditorPart } from 'vs/workbench/test/browser/workbenchTestServices';
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
@@ -18,7 +17,7 @@ import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { timeout } from 'vs/base/common/async';
import { Event } from 'vs/base/common/event';
suite.skip('HistoryService', function () {
suite.skip('HistoryService', function () { // {{SQL CARBON EDIT}} Skip suite
const TEST_EDITOR_ID = 'MyTestEditorForEditorHistory';
const TEST_EDITOR_INPUT_ID = 'testEditorInputForHistoyService';
@@ -53,11 +52,11 @@ suite.skip('HistoryService', function () {
const [part, historyService, editorService] = await createServices();
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID);
await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input1, { pinned: true });
assert.strictEqual(part.activeGroup.activeEditor, input1);
const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID);
await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input2, { pinned: true });
assert.strictEqual(part.activeGroup.activeEditor, input2);
let editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange);
@@ -78,10 +77,10 @@ suite.skip('HistoryService', function () {
assert.strictEqual(history.length, 0);
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID);
await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input1, { pinned: true });
const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID);
await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input2, { pinned: true });
history = historyService.getHistory();
assert.strictEqual(history.length, 2);
@@ -98,7 +97,7 @@ suite.skip('HistoryService', function () {
assert.ok(!historyService.getLastActiveFile('foo'));
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID);
await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input1, { pinned: true });
assert.strictEqual(historyService.getLastActiveFile('foo')?.toString(), input1.resource.toString());
});
@@ -109,10 +108,10 @@ suite.skip('HistoryService', function () {
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID);
const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID);
await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input1, { pinned: true });
assert.strictEqual(part.activeGroup.activeEditor, input1);
await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input2, { pinned: true });
assert.strictEqual(part.activeGroup.activeEditor, input2);
let editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange);
@@ -145,8 +144,8 @@ suite.skip('HistoryService', function () {
const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT);
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
await sideGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
await rootGroup.openEditor(input1, { pinned: true });
await sideGroup.openEditor(input2, { pinned: true });
let editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange);
historyService.openPreviouslyUsedEditor();
@@ -169,9 +168,9 @@ suite.skip('HistoryService', function () {
const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID);
const input4 = new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID);
await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input1, { pinned: true });
await part.activeGroup.openEditor(input2, { pinned: true });
await part.activeGroup.openEditor(input3, { pinned: true });
let editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange);
historyService.openPreviouslyUsedEditor();
@@ -179,7 +178,7 @@ suite.skip('HistoryService', function () {
assert.strictEqual(part.activeGroup.activeEditor, input2);
await timeout(0);
await part.activeGroup.openEditor(input4, EditorOptions.create({ pinned: true }));
await part.activeGroup.openEditor(input4, { pinned: true });
editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange);
historyService.openPreviouslyUsedEditor();

View File

@@ -11,7 +11,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWindowSettings, IWindowOpenable, IOpenWindowOptions, isFolderToOpen, isWorkspaceToOpen, isFileToOpen, IOpenEmptyWindowOptions, IPathData, IFileToOpen } from 'vs/platform/windows/common/windows';
import { pathsToEditors } from 'vs/workbench/common/editor';
import { whenTextEditorClosed } from 'vs/workbench/browser/editor';
import { whenEditorClosed } from 'vs/workbench/browser/editor';
import { IFileService } from 'vs/platform/files/common/files';
import { ILabelService } from 'vs/platform/label/common/label';
import { IModifierKeyStatus, ModifierKeyEmitter, trackFocus } from 'vs/base/browser/dom';
@@ -256,8 +256,8 @@ export class BrowserHostService extends Disposable implements IHostService {
// Same Window: open via editor service in current window
if (this.shouldReuse(options, true /* file */)) {
editorService.openEditor({
leftResource: editors[0].resource,
rightResource: editors[1].resource,
originalInput: { resource: editors[0].resource },
modifiedInput: { resource: editors[1].resource },
options: { pinned: true }
});
}
@@ -292,7 +292,7 @@ export class BrowserHostService extends Disposable implements IHostService {
openables = [openable];
}
editorService.openEditors(await pathsToEditors(openables, this.fileService));
editorService.openEditors(await pathsToEditors(openables, this.fileService), undefined, { validateTrust: true });
}
// New Window: open into empty window
@@ -315,7 +315,7 @@ export class BrowserHostService extends Disposable implements IHostService {
(async () => {
// Wait for the resources to be closed in the text editor...
await this.instantiationService.invokeFunction(accessor => whenTextEditorClosed(accessor, fileOpenables.map(fileOpenable => fileOpenable.fileUri)));
await this.instantiationService.invokeFunction(accessor => whenEditorClosed(accessor, fileOpenables.map(fileOpenable => fileOpenable.fileUri)));
// ...before deleting the wait marker file
await this.fileService.del(waitMarkerFileURI);

View File

@@ -12,7 +12,7 @@ export const IHostService = createDecorator<IHostService>('hostService');
/**
* A set of methods supported in both web and native environments.
*
* @see `INativeHostService` for methods that are specific to native
* @see {@link INativeHostService} for methods that are specific to native
* environments.
*/
export interface IHostService {

View File

@@ -8,11 +8,12 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { editorHoverBackground, editorHoverBorder, textLinkForeground, editorHoverForeground, editorHoverStatusBarBackground, textCodeBlockBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
import { IHoverService, IHoverOptions } from 'vs/workbench/services/hover/browser/hover';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { HoverWidget } from 'vs/workbench/services/hover/browser/hoverWidget';
import { IContextViewProvider, IDelegate } from 'vs/base/browser/ui/contextview/contextview';
import { IDisposable } from 'vs/base/common/lifecycle';
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { addDisposableListener, EventType } from 'vs/base/browser/dom';
export class HoverService implements IHoverService {
declare readonly _serviceBrand: undefined;
@@ -21,8 +22,10 @@ export class HoverService implements IHoverService {
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IContextViewService private readonly _contextViewService: IContextViewService
@IContextViewService private readonly _contextViewService: IContextViewService,
@IContextMenuService contextMenuService: IContextMenuService
) {
contextMenuService.onDidShowContextMenu(() => this.hideHover());
}
showHover(options: IHoverOptions, focus?: boolean): IDisposable | undefined {
@@ -31,17 +34,28 @@ export class HoverService implements IHoverService {
}
this._currentHoverOptions = options;
const hoverDisposables = new DisposableStore();
const hover = this._instantiationService.createInstance(HoverWidget, options);
hover.onDispose(() => this._currentHoverOptions = undefined);
hover.onDispose(() => {
this._currentHoverOptions = undefined;
hoverDisposables.dispose();
});
const provider = this._contextViewService as IContextViewProvider;
provider.showContextView(new HoverContextViewDelegate(hover, focus));
hover.onRequestLayout(() => provider.layout());
if ('targetElements' in options.target) {
for (const element of options.target.targetElements) {
hoverDisposables.add(addDisposableListener(element, EventType.CLICK, () => this.hideHover()));
}
} else {
hoverDisposables.add(addDisposableListener(options.target, EventType.CLICK, () => this.hideHover()));
}
if ('IntersectionObserver' in window) {
const observer = new IntersectionObserver(e => this._intersectionChange(e, hover), { threshold: 0 });
const firstTargetElement = 'targetElements' in options.target ? options.target.targetElements[0] : options.target;
observer.observe(firstTargetElement);
hover.onDispose(() => observer.disconnect());
hoverDisposables.add(toDisposable(() => observer.disconnect()));
}
return hover;

View File

@@ -18,6 +18,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer';
import { isString } from 'vs/base/common/types';
const $ = dom.$;
type TargetRect = {
@@ -66,7 +67,7 @@ export class HoverWidget extends Widget {
) {
super();
this._linkHandler = options.linkHandler || this._openerService.open;
this._linkHandler = options.linkHandler || (url => this._openerService.open(url, { allowCommands: (!isString(options.text) && options.text.isTrusted) }));
this._target = 'targetElements' in options.target ? options.target : new ElementHoverTarget(options.target);

View File

@@ -6,7 +6,7 @@
import { IssueReporterStyles, IssueReporterData, ProcessExplorerData, IssueReporterExtensionData } from 'vs/platform/issue/common/issue';
import { IIssueService } from 'vs/platform/issue/electron-sandbox/issue';
import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService';
import { textLinkForeground, inputBackground, inputBorder, inputForeground, buttonBackground, buttonHoverBackground, buttonForeground, inputValidationErrorBorder, foreground, inputActiveOptionBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, editorBackground, editorForeground, listHoverBackground, listHoverForeground, textLinkActiveForeground, inputValidationErrorBackground, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry';
import { textLinkForeground, inputBackground, inputBorder, inputForeground, buttonBackground, buttonHoverBackground, buttonForeground, inputValidationErrorBorder, foreground, inputActiveOptionBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, editorBackground, editorForeground, listHoverBackground, listHoverForeground, textLinkActiveForeground, inputValidationErrorBackground, inputValidationErrorForeground, listActiveSelectionBackground, listActiveSelectionForeground, listFocusOutline, listFocusBackground, listFocusForeground, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
@@ -19,6 +19,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService';
import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
export class WorkbenchIssueService implements IWorkbenchIssueService {
declare readonly _serviceBrand: undefined;
@@ -29,6 +30,7 @@ export class WorkbenchIssueService implements IWorkbenchIssueService {
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
@INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService,
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,
@IProductService private readonly productService: IProductService,
@ITASExperimentService private readonly experimentService: ITASExperimentService,
@IAuthenticationService private readonly authenticationService: IAuthenticationService
@@ -70,15 +72,24 @@ export class WorkbenchIssueService implements IWorkbenchIssueService {
});
}
const experiments = await this.experimentService.getCurrentExperiments();
const githubSessions = await this.authenticationService.getSessions('github');
const potentialSessions = githubSessions.filter(session => session.scopes.includes('repo'));
let githubAccessToken = '';
try {
const githubSessions = await this.authenticationService.getSessions('github');
const potentialSessions = githubSessions.filter(session => session.scopes.includes('repo'));
githubAccessToken = potentialSessions[0]?.accessToken;
} catch (e) {
// Ignore
}
const theme = this.themeService.getColorTheme();
const issueReporterData: IssueReporterData = Object.assign({
styles: getIssueReporterStyles(theme),
zoomLevel: getZoomLevel(),
enabledExtensions: extensionData,
experiments: experiments?.join('\n'),
githubAccessToken: potentialSessions[0]?.accessToken
restrictedMode: !this.workspaceTrustManagementService.isWorkpaceTrusted(),
githubAccessToken,
}, dataOverrides);
return this.issueService.openReporter(issueReporterData);
}
@@ -91,8 +102,14 @@ export class WorkbenchIssueService implements IWorkbenchIssueService {
styles: {
backgroundColor: getColor(theme, editorBackground),
color: getColor(theme, editorForeground),
hoverBackground: getColor(theme, listHoverBackground),
hoverForeground: getColor(theme, listHoverForeground)
listHoverBackground: getColor(theme, listHoverBackground),
listHoverForeground: getColor(theme, listHoverForeground),
listFocusBackground: getColor(theme, listFocusBackground),
listFocusForeground: getColor(theme, listFocusForeground),
listFocusOutline: getColor(theme, listFocusOutline),
listActiveSelectionBackground: getColor(theme, listActiveSelectionBackground),
listActiveSelectionForeground: getColor(theme, listActiveSelectionForeground),
listHoverOutline: getColor(theme, activeContrastBorder),
},
platform: platform,
applicationName: this.productService.applicationName

View File

@@ -20,4 +20,4 @@ export class KeyboardLayoutContribution {
registerKeyboardLayout(layout: IKeymapInfo) {
this._layoutInfos.push(layout);
}
}
}

View File

@@ -184,4 +184,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
MailForward: [],
MailSend: []
}
});
});

View File

@@ -166,4 +166,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
BrowserRefresh: [],
BrowserFavorites: []
}
});
});

View File

@@ -167,4 +167,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
BrowserFavorites: []
}
});
});

View File

@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
AltRight: [],
MetaRight: []
}
});
});

View File

@@ -166,4 +166,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
BrowserRefresh: [],
BrowserFavorites: []
}
});
});

View File

@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
AltRight: [],
MetaRight: []
}
});
});

View File

@@ -128,4 +128,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
AltRight: [],
MetaRight: []
}
});
});

View File

@@ -167,4 +167,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
BrowserFavorites: []
}
});
});

View File

@@ -137,4 +137,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
AltRight: [],
MetaRight: []
}
});
});

View File

@@ -171,4 +171,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
BrowserRefresh: [],
BrowserFavorites: []
}
});
});

View File

@@ -167,4 +167,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
BrowserFavorites: []
}
});
});

View File

@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
AltRight: [],
MetaRight: []
}
});
});

View File

@@ -184,4 +184,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
MailForward: [],
MailSend: []
}
});
});

View File

@@ -166,4 +166,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
BrowserRefresh: [],
BrowserFavorites: []
}
});
});

View File

@@ -166,4 +166,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
BrowserRefresh: [],
BrowserFavorites: []
}
});
});

View File

@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
AltRight: [],
MetaRight: []
}
});
});

View File

@@ -166,4 +166,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
BrowserRefresh: [],
BrowserFavorites: []
}
});
});

View File

@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
AltRight: [],
MetaRight: []
}
});
});

View File

@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
AltRight: [],
MetaRight: []
}
});
});

View File

@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
AltRight: [],
MetaRight: []
}
});
});

View File

@@ -9,4 +9,4 @@ import 'vs/workbench/services/keybinding/browser/keyboardLayouts/de.linux';
import 'vs/workbench/services/keybinding/browser/keyboardLayouts/fr.linux';
import 'vs/workbench/services/keybinding/browser/keyboardLayouts/ru.linux';
export { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution';
export { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution';

View File

@@ -26,4 +26,4 @@ import 'vs/workbench/services/keybinding/browser/keyboardLayouts/de-swiss.win';
import 'vs/workbench/services/keybinding/browser/keyboardLayouts/en-belgian.win';
import 'vs/workbench/services/keybinding/browser/keyboardLayouts/cz.win';
export { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution';
export { KeyboardLayoutContribution } from 'vs/workbench/services/keybinding/browser/keyboardLayouts/_.contribution';

View File

@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
AltRight: [],
MetaRight: []
}
});
});

View File

@@ -166,4 +166,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
BrowserRefresh: [],
BrowserFavorites: []
}
});
});

View File

@@ -167,4 +167,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
BrowserRefresh: [],
BrowserFavorites: []
}
});
});

View File

@@ -167,4 +167,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
BrowserFavorites: []
}
});
});

View File

@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
AltRight: [],
MetaRight: []
}
});
});

View File

@@ -166,4 +166,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
BrowserRefresh: [],
BrowserFavorites: []
}
});
});

View File

@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
AltRight: [],
MetaRight: []
}
});
});

View File

@@ -165,4 +165,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
BrowserRefresh: [],
BrowserFavorites: []
}
});
});

View File

@@ -128,4 +128,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
AltRight: [],
MetaRight: []
}
});
});

View File

@@ -12,4 +12,4 @@ export interface IKeyboard {
}
export type INavigatorWithKeyboard = Navigator & {
keyboard: IKeyboard
};
};

View File

@@ -993,6 +993,7 @@ export class MacLinuxKeyboardMapper implements IKeyboardMapper {
|| (keyCode === KeyCode.End)
|| (keyCode === KeyCode.PageDown)
|| (keyCode === KeyCode.PageUp)
|| (keyCode === KeyCode.Backspace)
) {
// "Dispatch" on keyCode for these key codes to workaround issues with remote desktoping software
// where the scan codes appear to be incorrect (see https://github.com/microsoft/vscode/issues/24107)

View File

@@ -5,11 +5,10 @@
import * as assert from 'assert';
import * as path from 'vs/base/common/path';
import { promises } from 'fs';
import { getPathFromAmdModule } from 'vs/base/test/node/testUtils';
import { Keybinding, ResolvedKeybinding, SimpleKeybinding } from 'vs/base/common/keyCodes';
import { ScanCodeBinding } from 'vs/base/common/scanCode';
import { writeFile } from 'vs/base/node/pfs';
import { Promises } from 'vs/base/node/pfs';
import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
import { IKeyboardMapper } from 'vs/platform/keyboardLayout/common/keyboardMapper';
@@ -53,7 +52,7 @@ export function assertResolveUserBinding(mapper: IKeyboardMapper, parts: (Simple
}
export function readRawMapping<T>(file: string): Promise<T> {
return promises.readFile(getPathFromAmdModule(require, `vs/workbench/services/keybinding/test/electron-browser/${file}.js`)).then((buff) => {
return Promises.readFile(getPathFromAmdModule(require, `vs/workbench/services/keybinding/test/electron-browser/${file}.js`)).then((buff) => {
let contents = buff.toString();
let func = new Function('define', contents);
let rawMappings: T | null = null;
@@ -67,12 +66,12 @@ export function readRawMapping<T>(file: string): Promise<T> {
export function assertMapping(writeFileIfDifferent: boolean, mapper: IKeyboardMapper, file: string): Promise<void> {
const filePath = path.normalize(getPathFromAmdModule(require, `vs/workbench/services/keybinding/test/electron-browser/${file}`));
return promises.readFile(filePath).then((buff) => {
return Promises.readFile(filePath).then((buff) => {
const expected = buff.toString().replace(/\r\n/g, '\n');
const actual = mapper.dumpDebugInfo().replace(/\r\n/g, '\n');
if (actual !== expected && writeFileIfDifferent) {
const destPath = filePath.replace(/vscode[\/\\]out[\/\\]vs/, 'vscode/src/vs');
writeFile(destPath, actual);
Promises.writeFile(destPath, actual);
}
assert.deepStrictEqual(actual, expected);
});

View File

@@ -494,4 +494,4 @@ define({
MailForward: { value: '', withShift: '', withAltGr: '', withShiftAltGr: '' },
MailSend: { value: '', withShift: '', withAltGr: '', withShiftAltGr: '' }
});
});

View File

@@ -1090,4 +1090,4 @@ define({
withAltGr: '',
withShiftAltGr: ''
}
});
});

Some files were not shown because too many files have changed in this diff Show More