Skip to content

Commit

Permalink
variables: allow resolving extensionDir
Browse files Browse the repository at this point in the history
This allows us to fix microsoft/vscode-remote-release#5516 (comment)

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.
  • Loading branch information
connor4312 committed Mar 29, 2022
1 parent edcfff3 commit 41abb3d
Show file tree
Hide file tree
Showing 28 changed files with 429 additions and 284 deletions.
23 changes: 23 additions & 0 deletions src/vs/base/common/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,29 @@ export function lastNonWhitespaceIndex(str: string, startIndex: number = str.len
return -1;
}

/**
* Function that works identically to String.prototype.replace, except, the
* replace function is allowed to be async and return a Promise.
*/
export function replaceAsync(str: string, search: RegExp, replacer: (match: string, ...args: any[]) => Promise<string>): Promise<string> {
let parts: (string | Promise<string>)[] = [];

let last = 0;
for (const match of str.matchAll(search)) {
parts.push(str.slice(last, match.index));
if (match.index === undefined) {
throw new Error('match.index should be defined');
}

last = match.index + match[0].length;
parts.push(replacer(match[0], ...match.slice(1), match.index, str, match.groups));
}

parts.push(str.slice(last));

return Promise.all(parts).then(p => p.join(''));
}

export function compare(a: string, b: string): number {
if (a < b) {
return -1;
Expand Down
9 changes: 9 additions & 0 deletions src/vs/base/test/common/strings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,4 +386,13 @@ suite('Strings', () => {
assert.strictEqual('hello world', strings.truncate('hello world', 100));
assert.strictEqual('hello…', strings.truncate('hello world', 5));
});

test('replaceAsync', async () => {
let i = 0;
assert.strictEqual(await strings.replaceAsync('abcabcabcabc', /b(.)/g, async (match, after) => {
assert.strictEqual(match, 'bc');
assert.strictEqual(after, 'c');
return `${i++}${after}`;
}), 'a0ca1ca2ca3c');
});
});
22 changes: 15 additions & 7 deletions src/vs/server/node/remoteTerminalChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ import { AbstractVariableResolverService } from 'vs/workbench/services/configura
import { buildUserEnvironment } from 'vs/server/node/extensionHostConnection';
import { IServerEnvironmentService } from 'vs/server/node/serverEnvironmentService';
import { IProductService } from 'vs/platform/product/common/productService';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';

class CustomVariableResolver extends AbstractVariableResolverService {
constructor(
env: platform.IProcessEnvironment,
workspaceFolders: IWorkspaceFolder[],
activeFileResource: URI | undefined,
resolvedVariables: { [name: string]: string }
resolvedVariables: { [name: string]: string },
extensionService: IExtensionManagementService,
) {
super({
getFolderUri: (folderName: string): URI | undefined => {
Expand Down Expand Up @@ -68,7 +70,12 @@ class CustomVariableResolver extends AbstractVariableResolverService {
},
getLineNumber: (): string | undefined => {
return resolvedVariables['lineNumber'];
}
},
getExtension: async id => {
const installed = await extensionService.getInstalled();
const found = installed.find(e => e.identifier.id === id);
return found && { extensionLocation: found.location };
},
}, undefined, Promise.resolve(os.homedir()), Promise.resolve(env));
}
}
Expand All @@ -89,7 +96,8 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel<
private readonly _environmentService: IServerEnvironmentService,
private readonly _logService: ILogService,
private readonly _ptyService: IPtyService,
private readonly _productService: IProductService
private readonly _productService: IProductService,
private readonly _extensionManagementService: IExtensionManagementService,
) {
super();
}
Expand Down Expand Up @@ -196,16 +204,16 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel<
const workspaceFolders = args.workspaceFolders.map(reviveWorkspaceFolder);
const activeWorkspaceFolder = args.activeWorkspaceFolder ? reviveWorkspaceFolder(args.activeWorkspaceFolder) : undefined;
const activeFileResource = args.activeFileResource ? URI.revive(uriTransformer.transformIncoming(args.activeFileResource)) : undefined;
const customVariableResolver = new CustomVariableResolver(baseEnv, workspaceFolders, activeFileResource, args.resolvedVariables);
const customVariableResolver = new CustomVariableResolver(baseEnv, workspaceFolders, activeFileResource, args.resolvedVariables, this._extensionManagementService);
const variableResolver = terminalEnvironment.createVariableResolver(activeWorkspaceFolder, process.env, customVariableResolver);

// Get the initial cwd
const initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, os.homedir(), variableResolver, activeWorkspaceFolder?.uri, args.configuration['terminal.integrated.cwd'], this._logService);
const initialCwd = await terminalEnvironment.getCwd(shellLaunchConfig, os.homedir(), variableResolver, activeWorkspaceFolder?.uri, args.configuration['terminal.integrated.cwd'], this._logService);
shellLaunchConfig.cwd = initialCwd;

const envPlatformKey = platform.isWindows ? 'terminal.integrated.env.windows' : (platform.isMacintosh ? 'terminal.integrated.env.osx' : 'terminal.integrated.env.linux');
const envFromConfig = args.configuration[envPlatformKey];
const env = terminalEnvironment.createTerminalEnvironment(
const env = await terminalEnvironment.createTerminalEnvironment(
shellLaunchConfig,
envFromConfig,
variableResolver,
Expand All @@ -222,7 +230,7 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel<
}
const envVariableCollections = new Map<string, IEnvironmentVariableCollection>(entries);
const mergedCollection = new MergedEnvironmentVariableCollection(envVariableCollections);
mergedCollection.applyToProcessEnvironment(env);
await mergedCollection.applyToProcessEnvironment(env, variableResolver);
}

// Fork the process and listen for messages
Expand Down
2 changes: 1 addition & 1 deletion src/vs/server/node/serverServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken
const telemetryChannel = new ServerTelemetryChannel(accessor.get(IServerTelemetryService), appInsightsAppender);
socketServer.registerChannel('telemetry', telemetryChannel);

socketServer.registerChannel(REMOTE_TERMINAL_CHANNEL_NAME, new RemoteTerminalChannel(environmentService, logService, ptyService, productService));
socketServer.registerChannel(REMOTE_TERMINAL_CHANNEL_NAME, new RemoteTerminalChannel(environmentService, logService, ptyService, productService, extensionManagementService));

const remoteFileSystemChannel = new RemoteAgentFileSystemProviderChannel(logService, environmentService);
socketServer.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, remoteFileSystemChannel);
Expand Down
3 changes: 3 additions & 0 deletions src/vs/workbench/api/browser/mainThreadExtensionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha
public dispose(): void {
}

$getExtension(extensionId: string) {
return this._extensionService.getExtension(extensionId);
}
$activateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> {
return this._internalExtensionService._activateById(extensionId, reason);
}
Expand Down
2 changes: 2 additions & 0 deletions src/vs/workbench/api/common/extHost.common.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -48,3 +49,4 @@ registerSingleton(IExtHostWorkspace, ExtHostWorkspace);
registerSingleton(IExtHostSecretState, ExtHostSecretState);
registerSingleton(IExtHostTelemetry, ExtHostTelemetry);
registerSingleton(IExtHostEditorTabs, ExtHostEditorTabs);
registerSingleton(IExtHostVariableResolverProvider, ExtHostVariableResolverProviderService);
1 change: 1 addition & 0 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,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;
Expand Down
154 changes: 24 additions & 130 deletions src/vs/workbench/api/common/extHostDebugService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -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 = [];
Expand Down Expand Up @@ -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) {
Expand All @@ -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 {
Expand Down Expand Up @@ -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.groups.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;
Expand Down Expand Up @@ -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);
}
}
Loading

0 comments on commit 41abb3d

Please sign in to comment.