variables: allow resolving extensionDir (#146274)

* variables: allow resolving `extensionDir`

This allows us to fix https://github.com/microsoft/vscode-remote-release/issues/5516#issuecomment-911597917

It enables a new replacement in the format `${extensionDir:<id>}` which
will expand to the filesystem path where the extension is stored. This
involved churn, since now resolution is always synchronous (where before
the terminal took a synchronous-only path.)

Additionally, changes were needed to inject this information in the
variable resolver. As part of this I made the extension host resolver
(used by debug and tasks) its own extension host service.

* fixup! preserve object key order in resolution, add extensionDir support

* fixup! address pr comments

* fixup! address pr comments

* fixup! address pr comments

* config: fix config replacement only working for first variable per line

* fixup! fix unit tests
This commit is contained in:
Connor Peet
2022-04-07 08:06:31 -07:00
committed by GitHub
parent 0fba8139ce
commit 0de44f9786
30 changed files with 450 additions and 298 deletions

View File

@@ -26,6 +26,7 @@ import { ExtHostEditorTabs, IExtHostEditorTabs } from 'vs/workbench/api/common/e
import { ExtHostLoggerService } from 'vs/workbench/api/common/extHostLoggerService';
import { ILoggerService, ILogService } from 'vs/platform/log/common/log';
import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService';
import { ExtHostVariableResolverProviderService, IExtHostVariableResolverProvider } from 'vs/workbench/api/common/extHostVariableResolverService';
registerSingleton(ILoggerService, ExtHostLoggerService);
registerSingleton(ILogService, ExtHostLogService);
@@ -48,3 +49,4 @@ registerSingleton(IExtHostWorkspace, ExtHostWorkspace);
registerSingleton(IExtHostSecretState, ExtHostSecretState);
registerSingleton(IExtHostTelemetry, ExtHostTelemetry);
registerSingleton(IExtHostEditorTabs, ExtHostEditorTabs);
registerSingleton(IExtHostVariableResolverProvider, ExtHostVariableResolverProviderService);

View File

@@ -1107,6 +1107,7 @@ export interface MainThreadTaskShape extends IDisposable {
}
export interface MainThreadExtensionServiceShape extends IDisposable {
$getExtension(extensionId: string): Promise<Dto<IExtensionDescription> | undefined>;
$activateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void>;
$onWillActivateExtension(extensionId: ExtensionIdentifier): Promise<void>;
$onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void;

View File

@@ -3,36 +3,29 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'vs/base/common/path';
import { URI, UriComponents } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { asPromise } from 'vs/base/common/async';
import {
MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, DebugSessionUUID,
IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto
} from 'vs/workbench/api/common/extHost.protocol';
import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DataBreakpoint, DebugConsoleMode, DebugAdapterInlineImplementation, DebugAdapterNamedPipeServer, TextDiffTabInput, NotebookDiffEditorTabInput, TextTabInput, NotebookEditorTabInput, CustomEditorTabInput } from 'vs/workbench/api/common/extHostTypes';
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor, IDebugAdapterNamedPipeServer } from 'vs/workbench/contrib/debug/common/debug';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver';
import { ExtHostConfigProvider, IExtHostConfiguration } from '../common/extHostConfiguration';
import { convertToVSCPaths, convertToDAPaths, isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import { ISignService } from 'vs/platform/sign/common/sign';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import type * as vscode from 'vscode';
import { Emitter, Event } from 'vs/base/common/event';
import { withNullAsUndefined } from 'vs/base/common/types';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { withNullAsUndefined } from 'vs/base/common/types';
import * as process from 'vs/base/common/process';
import { ISignService } from 'vs/platform/sign/common/sign';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { DebugSessionUUID, ExtHostDebugServiceShape, IBreakpointsDeltaDto, IDebugSessionDto, IFunctionBreakpointDto, ISourceMultiBreakpointDto, MainContext, MainThreadDebugServiceShape } from 'vs/workbench/api/common/extHost.protocol';
import { IExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs';
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { DataBreakpoint, DebugAdapterExecutable, DebugAdapterInlineImplementation, DebugAdapterNamedPipeServer, DebugAdapterServer, DebugConsoleMode, Disposable, FunctionBreakpoint, Location, Position, SourceBreakpoint } from 'vs/workbench/api/common/extHostTypes';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
import { IAdapterDescriptor, IConfig, IDebugAdapter, IDebugAdapterExecutable, IDebugAdapterNamedPipeServer, IDebugAdapterServer, IDebuggerContribution } from 'vs/workbench/contrib/debug/common/debug';
import { convertToDAPaths, convertToVSCPaths, isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import type * as vscode from 'vscode';
import { IExtHostConfiguration } from '../common/extHostConfiguration';
import { IExtHostVariableResolverProvider } from './extHostVariableResolverService';
export const IExtHostDebugService = createDecorator<IExtHostDebugService>('IExtHostDebugService');
@@ -101,17 +94,15 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E
private _debugAdapters: Map<number, IDebugAdapter>;
private _debugAdaptersTrackers: Map<number, vscode.DebugAdapterTracker>;
private _variableResolver: IConfigurationResolverService | undefined;
private _signService: ISignService | undefined;
constructor(
@IExtHostRpcService extHostRpcService: IExtHostRpcService,
@IExtHostWorkspace protected _workspaceService: IExtHostWorkspace,
@IExtHostExtensionService private _extensionService: IExtHostExtensionService,
@IExtHostDocumentsAndEditors private _editorsService: IExtHostDocumentsAndEditors,
@IExtHostConfiguration protected _configurationService: IExtHostConfiguration,
@IExtHostEditorTabs protected _editorTabs: IExtHostEditorTabs
@IExtHostEditorTabs protected _editorTabs: IExtHostEditorTabs,
@IExtHostVariableResolverProvider private _variableResolver: IExtHostVariableResolverProvider,
) {
this._configProviderHandleCounter = 0;
this._configProviders = [];
@@ -371,13 +362,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E
return Promise.resolve(undefined);
}
protected abstract createVariableResolver(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider): AbstractVariableResolverService;
public async $substituteVariables(folderUri: UriComponents | undefined, config: IConfig): Promise<IConfig> {
if (!this._variableResolver) {
const [workspaceFolders, configProvider] = await Promise.all([this._workspaceService.getWorkspaceFolders2(), this._configurationService.getConfigProvider()]);
this._variableResolver = this.createVariableResolver(workspaceFolders || [], this._editorsService, configProvider!);
}
let ws: IWorkspaceFolder | undefined;
const folder = await this.getFolder(folderUri);
if (folder) {
@@ -390,7 +375,8 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E
}
};
}
return this._variableResolver.resolveAnyAsync(ws, config);
const variableResolver = await this._variableResolver.getResolver();
return variableResolver.resolveAnyAsync(ws, config);
}
protected createDebugAdapter(adapter: IAdapterDescriptor, session: ExtHostDebugSession): AbstractDebugAdapter | undefined {
@@ -939,94 +925,6 @@ export class ExtHostDebugConsole {
}
}
export class ExtHostVariableResolverService extends AbstractVariableResolverService {
constructor(folders: vscode.WorkspaceFolder[],
editorService: ExtHostDocumentsAndEditors | undefined,
configurationService: ExtHostConfigProvider,
editorTabs: IExtHostEditorTabs,
workspaceService?: IExtHostWorkspace,
userHome?: string) {
function getActiveUri(): URI | undefined {
if (editorService) {
const activeEditor = editorService.activeEditor();
if (activeEditor) {
return activeEditor.document.uri;
}
const activeTab = editorTabs.tabGroups.all.find(group => group.isActive)?.activeTab;
if (activeTab !== undefined) {
// Resolve a resource from the tab
if (activeTab.kind instanceof TextDiffTabInput || activeTab.kind instanceof NotebookDiffEditorTabInput) {
return activeTab.kind.modified;
} else if (activeTab.kind instanceof TextTabInput || activeTab.kind instanceof NotebookEditorTabInput || activeTab.kind instanceof CustomEditorTabInput) {
return activeTab.kind.uri;
}
}
}
return undefined;
}
super({
getFolderUri: (folderName: string): URI | undefined => {
const found = folders.filter(f => f.name === folderName);
if (found && found.length > 0) {
return found[0].uri;
}
return undefined;
},
getWorkspaceFolderCount: (): number => {
return folders.length;
},
getConfigurationValue: (folderUri: URI | undefined, section: string): string | undefined => {
return configurationService.getConfiguration(undefined, folderUri).get<string>(section);
},
getAppRoot: (): string | undefined => {
return process.cwd();
},
getExecPath: (): string | undefined => {
return process.env['VSCODE_EXEC_PATH'];
},
getFilePath: (): string | undefined => {
const activeUri = getActiveUri();
if (activeUri) {
return path.normalize(activeUri.fsPath);
}
return undefined;
},
getWorkspaceFolderPathForFile: (): string | undefined => {
if (workspaceService) {
const activeUri = getActiveUri();
if (activeUri) {
const ws = workspaceService.getWorkspaceFolder(activeUri);
if (ws) {
return path.normalize(ws.uri.fsPath);
}
}
}
return undefined;
},
getSelectedText: (): string | undefined => {
if (editorService) {
const activeEditor = editorService.activeEditor();
if (activeEditor && !activeEditor.selection.isEmpty) {
return activeEditor.document.getText(activeEditor.selection);
}
}
return undefined;
},
getLineNumber: (): string | undefined => {
if (editorService) {
const activeEditor = editorService.activeEditor();
if (activeEditor) {
return String(activeEditor.selection.end.line + 1);
}
}
return undefined;
}
}, undefined, userHome ? Promise.resolve(userHome) : undefined, Promise.resolve(process.env));
}
}
interface ConfigProviderTuple {
type: string;
handle: number;
@@ -1108,14 +1006,10 @@ export class WorkerExtHostDebugService extends ExtHostDebugServiceBase {
@IExtHostRpcService extHostRpcService: IExtHostRpcService,
@IExtHostWorkspace workspaceService: IExtHostWorkspace,
@IExtHostExtensionService extensionService: IExtHostExtensionService,
@IExtHostDocumentsAndEditors editorsService: IExtHostDocumentsAndEditors,
@IExtHostConfiguration configurationService: IExtHostConfiguration,
@IExtHostEditorTabs editorTabs: IExtHostEditorTabs
@IExtHostEditorTabs editorTabs: IExtHostEditorTabs,
@IExtHostVariableResolverProvider variableResolver: IExtHostVariableResolverProvider
) {
super(extHostRpcService, workspaceService, extensionService, editorsService, configurationService, editorTabs);
}
protected createVariableResolver(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider): AbstractVariableResolverService {
return new ExtHostVariableResolverService(folders, editorService, configurationService, this._editorTabs);
super(extHostRpcService, workspaceService, extensionService, configurationService, editorTabs, variableResolver);
}
}

View File

@@ -229,6 +229,15 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
return false;
}
public async getExtension(extensionId: string): Promise<IExtensionDescription | undefined> {
const ext = await this._mainThreadExtensionsProxy.$getExtension(extensionId);
return ext && {
...ext,
identifier: new ExtensionIdentifier(ext.identifier.value),
extensionLocation: URI.revive(ext.extensionLocation),
};
}
private _activateByEvent(activationEvent: string, startup: boolean): Promise<void> {
return this._activator.activateByEvent(activationEvent, startup);
}
@@ -885,6 +894,7 @@ export const IExtHostExtensionService = createDecorator<IExtHostExtensionService
export interface IExtHostExtensionService extends AbstractExtHostExtensionService {
readonly _serviceBrand: undefined;
initialize(): Promise<void>;
getExtension(extensionId: string): Promise<IExtensionDescription | undefined>;
isActivated(extensionId: ExtensionIdentifier): boolean;
activateByIdWithErrors(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void>;
deactivateAll(): Promise<void>;

View File

@@ -0,0 +1,167 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Lazy } from 'vs/base/common/lazy';
import { Disposable } from 'vs/base/common/lifecycle';
import * as path from 'vs/base/common/path';
import * as process from 'vs/base/common/process';
import { URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs';
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { CustomEditorTabInput, NotebookDiffEditorTabInput, NotebookEditorTabInput, TextDiffTabInput, TextTabInput } from 'vs/workbench/api/common/extHostTypes';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver';
import * as vscode from 'vscode';
import { ExtHostConfigProvider, IExtHostConfiguration } from './extHostConfiguration';
export interface IExtHostVariableResolverProvider {
readonly _serviceBrand: undefined;
getResolver(): Promise<IConfigurationResolverService>;
}
export const IExtHostVariableResolverProvider = createDecorator<IExtHostVariableResolverProvider>('IExtHostVariableResolverProvider');
interface DynamicContext {
folders: vscode.WorkspaceFolder[];
}
class ExtHostVariableResolverService extends AbstractVariableResolverService {
constructor(
extensionService: IExtHostExtensionService,
workspaceService: IExtHostWorkspace,
editorService: IExtHostDocumentsAndEditors,
editorTabs: IExtHostEditorTabs,
configProvider: ExtHostConfigProvider,
context: DynamicContext,
homeDir: string | undefined,
) {
function getActiveUri(): URI | undefined {
if (editorService) {
const activeEditor = editorService.activeEditor();
if (activeEditor) {
return activeEditor.document.uri;
}
const activeTab = editorTabs.tabGroups.all.find(group => group.isActive)?.activeTab;
if (activeTab !== undefined) {
// Resolve a resource from the tab
if (activeTab.kind instanceof TextDiffTabInput || activeTab.kind instanceof NotebookDiffEditorTabInput) {
return activeTab.kind.modified;
} else if (activeTab.kind instanceof TextTabInput || activeTab.kind instanceof NotebookEditorTabInput || activeTab.kind instanceof CustomEditorTabInput) {
return activeTab.kind.uri;
}
}
}
return undefined;
}
super({
getFolderUri: (folderName: string): URI | undefined => {
const found = context.folders.filter(f => f.name === folderName);
if (found && found.length > 0) {
return found[0].uri;
}
return undefined;
},
getWorkspaceFolderCount: (): number => {
return context.folders.length;
},
getConfigurationValue: (folderUri: URI | undefined, section: string): string | undefined => {
return configProvider.getConfiguration(undefined, folderUri).get<string>(section);
},
getAppRoot: (): string | undefined => {
return process.cwd();
},
getExecPath: (): string | undefined => {
return process.env['VSCODE_EXEC_PATH'];
},
getFilePath: (): string | undefined => {
const activeUri = getActiveUri();
if (activeUri) {
return path.normalize(activeUri.fsPath);
}
return undefined;
},
getWorkspaceFolderPathForFile: (): string | undefined => {
if (workspaceService) {
const activeUri = getActiveUri();
if (activeUri) {
const ws = workspaceService.getWorkspaceFolder(activeUri);
if (ws) {
return path.normalize(ws.uri.fsPath);
}
}
}
return undefined;
},
getSelectedText: (): string | undefined => {
if (editorService) {
const activeEditor = editorService.activeEditor();
if (activeEditor && !activeEditor.selection.isEmpty) {
return activeEditor.document.getText(activeEditor.selection);
}
}
return undefined;
},
getLineNumber: (): string | undefined => {
if (editorService) {
const activeEditor = editorService.activeEditor();
if (activeEditor) {
return String(activeEditor.selection.end.line + 1);
}
}
return undefined;
},
getExtension: (id) => {
return extensionService.getExtension(id);
},
}, undefined, homeDir ? Promise.resolve(homeDir) : undefined, Promise.resolve(process.env));
}
}
export class ExtHostVariableResolverProviderService extends Disposable implements IExtHostVariableResolverProvider {
declare readonly _serviceBrand: undefined;
private _resolver = new Lazy(async () => {
const configProvider = await this.configurationService.getConfigProvider();
const folders = await this.workspaceService.getWorkspaceFolders2() || [];
const dynamic: DynamicContext = { folders };
this._register(this.workspaceService.onDidChangeWorkspace(async e => {
dynamic.folders = await this.workspaceService.getWorkspaceFolders2() || [];
}));
return new ExtHostVariableResolverService(
this.extensionService,
this.workspaceService,
this.editorService,
this.editorTabs,
configProvider,
dynamic,
this.homeDir(),
);
});
constructor(
@IExtHostExtensionService private readonly extensionService: IExtHostExtensionService,
@IExtHostWorkspace private readonly workspaceService: IExtHostWorkspace,
@IExtHostDocumentsAndEditors private readonly editorService: IExtHostDocumentsAndEditors,
@IExtHostConfiguration private readonly configurationService: IExtHostConfiguration,
@IExtHostEditorTabs private readonly editorTabs: IExtHostEditorTabs,
) {
super();
}
public getResolver(): Promise<IConfigurationResolverService> {
return this._resolver.getValue();
}
protected homeDir(): string | undefined {
return undefined;
}
}