mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-10 10:12:34 -05:00
Merge from vscode e3c4990c67c40213af168300d1cfeb71d680f877 (#16569)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
});
|
||||
|
||||
31
src/vs/workbench/services/banner/browser/bannerService.ts
Normal file
31
src/vs/workbench/services/banner/browser/bannerService.ts
Normal 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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -36,8 +36,6 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService {
|
||||
readonly extensionEnabledProposedApi?: string[];
|
||||
|
||||
readonly webviewExternalEndpoint: string;
|
||||
readonly webviewResourceRoot: string;
|
||||
readonly webviewCspSource: string;
|
||||
|
||||
readonly skipReleaseNotes: boolean;
|
||||
|
||||
|
||||
@@ -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']; }
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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.');
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.'),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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[][]) => {
|
||||
|
||||
@@ -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,
|
||||
[{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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); }
|
||||
};
|
||||
|
||||
|
||||
@@ -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[]> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,4 +20,4 @@ export class KeyboardLayoutContribution {
|
||||
registerKeyboardLayout(layout: IKeymapInfo) {
|
||||
this._layoutInfos.push(layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,4 +184,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
MailForward: [],
|
||||
MailSend: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -166,4 +166,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
BrowserRefresh: [],
|
||||
BrowserFavorites: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -167,4 +167,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
BrowserFavorites: []
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
AltRight: [],
|
||||
MetaRight: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -166,4 +166,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
BrowserRefresh: [],
|
||||
BrowserFavorites: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
AltRight: [],
|
||||
MetaRight: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -128,4 +128,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
AltRight: [],
|
||||
MetaRight: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -167,4 +167,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
BrowserFavorites: []
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -137,4 +137,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
AltRight: [],
|
||||
MetaRight: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -171,4 +171,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
BrowserRefresh: [],
|
||||
BrowserFavorites: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -167,4 +167,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
BrowserFavorites: []
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
AltRight: [],
|
||||
MetaRight: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -184,4 +184,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
MailForward: [],
|
||||
MailSend: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -166,4 +166,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
BrowserRefresh: [],
|
||||
BrowserFavorites: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -166,4 +166,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
BrowserRefresh: [],
|
||||
BrowserFavorites: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
AltRight: [],
|
||||
MetaRight: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -166,4 +166,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
BrowserRefresh: [],
|
||||
BrowserFavorites: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
AltRight: [],
|
||||
MetaRight: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
AltRight: [],
|
||||
MetaRight: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
AltRight: [],
|
||||
MetaRight: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
AltRight: [],
|
||||
MetaRight: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -166,4 +166,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
BrowserRefresh: [],
|
||||
BrowserFavorites: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -167,4 +167,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
BrowserRefresh: [],
|
||||
BrowserFavorites: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -167,4 +167,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
BrowserFavorites: []
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
AltRight: [],
|
||||
MetaRight: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -166,4 +166,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
BrowserRefresh: [],
|
||||
BrowserFavorites: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -129,4 +129,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
AltRight: [],
|
||||
MetaRight: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -165,4 +165,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
BrowserRefresh: [],
|
||||
BrowserFavorites: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -128,4 +128,4 @@ KeyboardLayoutContribution.INSTANCE.registerKeyboardLayout({
|
||||
AltRight: [],
|
||||
MetaRight: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,4 +12,4 @@ export interface IKeyboard {
|
||||
}
|
||||
export type INavigatorWithKeyboard = Navigator & {
|
||||
keyboard: IKeyboard
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -494,4 +494,4 @@ define({
|
||||
MailForward: { value: '', withShift: '', withAltGr: '', withShiftAltGr: '' },
|
||||
MailSend: { value: '', withShift: '', withAltGr: '', withShiftAltGr: '' }
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1090,4 +1090,4 @@ define({
|
||||
withAltGr: '',
|
||||
withShiftAltGr: ''
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user