Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose shell's environment - pwsh #237415

Merged
merged 54 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
714bb39
start terminal shell env proposed
anthonykim1 Sep 17, 2024
3c412ff
fix typo
anthonykim1 Sep 17, 2024
f214756
progress on shellEnvDetectionCapability, mainThreadTerminalSI
anthonykim1 Sep 19, 2024
3923373
update IShellEnvDetectionCapability interface
anthonykim1 Sep 19, 2024
0465588
touch up on $shellEnvChange
anthonykim1 Sep 19, 2024
e1d611a
adjust IShellEnvDetectionCapability
anthonykim1 Sep 19, 2024
98131e7
properly listen to envChangeEvent
anthonykim1 Sep 19, 2024
e37f119
Serialize env map, expose on exthost
anthonykim1 Nov 4, 2024
a0602bc
Merge remote-tracking branch 'upstream/main' into shellEnvAPI
anthonykim1 Nov 4, 2024
94c6574
start adding to zsh script
anthonykim1 Nov 4, 2024
e4ab9e8
receive environment variable in extension host, properly escape "
anthonykim1 Nov 19, 2024
e2b4ca1
clean up
anthonykim1 Nov 19, 2024
1cf51a5
Add TODO: properly escape double quotes, figure out why JSON parse fa…
anthonykim1 Nov 21, 2024
f0b1578
Fix nonce check, ignore PS1 for now in bash
Tyriar Dec 2, 2024
1b2eb1f
Add some simple PS1 string tests to deserializeMessage
Tyriar Dec 2, 2024
0956b86
New approach of sending env entries separately
Tyriar Dec 2, 2024
bb735f8
be able to get EnvSingleVar
anthonykim1 Dec 3, 2024
a1503da
few comments
anthonykim1 Jan 4, 2025
61653d5
add function signature for start, set, end environment var
anthonykim1 Jan 5, 2025
db981ae
implement EnvStart, EnvEntry, EnvEnd for single env entry
anthonykim1 Jan 6, 2025
a25a9a9
deserialize env value for EnvEntry
anthonykim1 Jan 6, 2025
7fe7c26
Remove unncessary comments
anthonykim1 Jan 6, 2025
338514c
only leave pwsh in this PR and exclude other shells
anthonykim1 Jan 7, 2025
9b97e1b
keep exlcuding other shell env - only pwsh should remain
anthonykim1 Jan 7, 2025
fc9f3f5
Update src/vs/workbench/api/common/extHostTerminalShellIntegration.ts
anthonykim1 Jan 8, 2025
bb93fdd
Update src/vscode-dts/vscode.proposed.terminalShellEnv.d.ts
anthonykim1 Jan 8, 2025
0ca8a75
Update src/vs/workbench/contrib/terminal/common/scripts/shellIntegrat…
anthonykim1 Jan 8, 2025
935f14a
Update src/vs/workbench/contrib/terminal/common/scripts/shellIntegrat…
anthonykim1 Jan 8, 2025
2f7b969
Update src/vs/workbench/contrib/terminal/common/scripts/shellIntegrat…
anthonykim1 Jan 8, 2025
cb997d5
Update src/vs/workbench/api/common/extHostTerminalShellIntegration.ts
anthonykim1 Jan 8, 2025
b6b673f
Update src/vs/platform/terminal/common/capabilities/shellEnvDetection…
anthonykim1 Jan 8, 2025
0ab7322
Update src/vs/workbench/api/common/extHost.protocol.ts
anthonykim1 Jan 8, 2025
fe93145
Update src/vs/workbench/api/browser/mainThreadTerminalShellIntegratio…
anthonykim1 Jan 8, 2025
badfb37
Update src/vs/platform/terminal/common/capabilities/shellEnvDetection…
anthonykim1 Jan 8, 2025
3f156f9
Update src/vs/platform/terminal/common/capabilities/shellEnvDetection…
anthonykim1 Jan 8, 2025
4e8c2cb
Update src/vs/platform/terminal/common/capabilities/shellEnvDetection…
anthonykim1 Jan 8, 2025
333c8d5
Update src/vs/platform/terminal/common/capabilities/capabilities.ts
anthonykim1 Jan 8, 2025
c68c58a
Update src/vs/workbench/api/browser/mainThreadTerminalShellIntegratio…
anthonykim1 Jan 8, 2025
944ce9a
Update src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts
anthonykim1 Jan 8, 2025
b2afe32
Update src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts
anthonykim1 Jan 8, 2025
dbbe2df
Update src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts
anthonykim1 Jan 8, 2025
0b248e2
add comment for ShellEnvDetection
anthonykim1 Jan 8, 2025
d246ba7
change envs in shellEnvDetectionCapability to env
anthonykim1 Jan 8, 2025
6b081da
Mention escaping character for EnvJSON similar to commandLine
anthonykim1 Jan 8, 2025
d555813
Do not fire env event if env has not changed
anthonykim1 Jan 8, 2025
3591fa4
add link to CommandLine
anthonykim1 Jan 9, 2025
143a4cc
follow main branch format so I avoid merge conflict
anthonykim1 Jan 9, 2025
6509fa1
Merge branch 'main' into shellEnvPwshOnly
anthonykim1 Jan 9, 2025
03fe326
remove resolved TODO
anthonykim1 Jan 9, 2025
d501cee
Update src/vs/workbench/api/browser/mainThreadTerminalShellIntegratio…
anthonykim1 Jan 9, 2025
e94cf4d
Update src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts
anthonykim1 Jan 9, 2025
0b831a5
use vscode object equals
anthonykim1 Jan 9, 2025
5f9f2db
Merge branch 'main' into shellEnvPwshOnly
Tyriar Jan 9, 2025
949fbec
Merge branch 'main' into shellEnvPwshOnly
Tyriar Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/vs/platform/extensions/common/extensionsApiProposals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,9 @@ const _allApiProposals = {
terminalSelection: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalSelection.d.ts',
},
terminalShellEnv: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalShellEnv.d.ts',
},
testObserver: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testObserver.d.ts',
},
Expand Down
16 changes: 15 additions & 1 deletion src/vs/platform/terminal/common/capabilities/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,13 @@ export const enum TerminalCapability {
* the request (task, debug, etc) provides an ID, optional marker, hoverMessage, and hidden property. When
* hidden is not provided, a generic decoration is added to the buffer and overview ruler.
*/
BufferMarkDetection
BufferMarkDetection,

/**
* The terminal can detect the latest environment of user's current shell.
*/
ShellEnvDetection,

anthonykim1 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down Expand Up @@ -133,6 +139,7 @@ export interface ITerminalCapabilityImplMap {
[TerminalCapability.NaiveCwdDetection]: INaiveCwdDetectionCapability;
[TerminalCapability.PartialCommandDetection]: IPartialCommandDetectionCapability;
[TerminalCapability.BufferMarkDetection]: IBufferMarkCapability;
[TerminalCapability.ShellEnvDetection]: IShellEnvDetectionCapability;
}

export interface ICwdDetectionCapability {
Expand All @@ -143,6 +150,13 @@ export interface ICwdDetectionCapability {
updateCwd(cwd: string): void;
}

export interface IShellEnvDetectionCapability {
readonly type: TerminalCapability.ShellEnvDetection;
readonly onDidChangeEnv: Event<Map<string, string>>;
get env(): Map<string, string>;
setEnvironment(envs: { [key: string]: string | undefined } | undefined, isTrusted: boolean): void;
}

export const enum CommandInvalidationReason {
Windows = 'windows',
NoProblemsReported = 'noProblemsReported'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Disposable } from '../../../../base/common/lifecycle.js';
import { IShellEnvDetectionCapability, TerminalCapability } from './capabilities.js';
import { Emitter } from '../../../../base/common/event.js';

export class ShellEnvDetectionCapability extends Disposable implements IShellEnvDetectionCapability {
readonly type = TerminalCapability.ShellEnvDetection;

private readonly _env: Map<string, string> = new Map();
get env(): Map<string, string> { return this._env; }

private readonly _onDidChangeEnv = this._register(new Emitter<Map<string, string>>());
readonly onDidChangeEnv = this._onDidChangeEnv.event;

setEnvironment(env: { [key: string]: string | undefined }, isTrusted: boolean): void {
if (!isTrusted) {
return;
}

let envChanged = false;
for (const [key, value] of Object.entries(env)) {
if (value !== this._env.get(key)) {
envChanged = true;
break;
}
}
Tyriar marked this conversation as resolved.
Show resolved Hide resolved

if (!envChanged && this._env.size === Object.keys(env).length) {
return;
}

this._env.clear();
for (const [key, value] of Object.entries(env)) {
if (value !== undefined) {
this._env.set(key, value);
}
}

// Convert to event and fire event
this._onDidChangeEnv.fire(this._env);
}
}
40 changes: 39 additions & 1 deletion src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Disposable, dispose, IDisposable, toDisposable } from '../../../../base
import { TerminalCapabilityStore } from '../capabilities/terminalCapabilityStore.js';
import { CommandDetectionCapability } from '../capabilities/commandDetectionCapability.js';
import { CwdDetectionCapability } from '../capabilities/cwdDetectionCapability.js';
import { IBufferMarkCapability, ICommandDetectionCapability, ICwdDetectionCapability, ISerializedCommandDetectionCapability, TerminalCapability } from '../capabilities/capabilities.js';
import { IBufferMarkCapability, ICommandDetectionCapability, ICwdDetectionCapability, ISerializedCommandDetectionCapability, IShellEnvDetectionCapability, TerminalCapability } from '../capabilities/capabilities.js';
import { PartialCommandDetectionCapability } from '../capabilities/partialCommandDetectionCapability.js';
import { ILogService } from '../../../log/common/log.js';
import { ITelemetryService } from '../../../telemetry/common/telemetry.js';
Expand All @@ -18,6 +18,7 @@ import type { ITerminalAddon, Terminal } from '@xterm/headless';
import { URI } from '../../../../base/common/uri.js';
import { sanitizeCwd } from '../terminalEnvironment.js';
import { removeAnsiEscapeCodesFromPrompt } from '../../../../base/common/strings.js';
import { ShellEnvDetectionCapability } from '../capabilities/shellEnvDetectionCapability.js';


/**
Expand Down Expand Up @@ -224,6 +225,21 @@ const enum VSCodeOscPt {
* WARNING: This sequence is unfinalized, DO NOT use this in your shell integration script.
*/
SetMark = 'SetMark',

/**
* Sends the shell's environment.
*
* Format: `OSC 633 ; EnvJson ; <Environment> ; <Nonce>`
*
* The EnvJson can escape ascii characters in the same way in which {@link CommandLine} does.
* `Environment` - A JSON string containing the shell's environment variables.
* `Nonce` - An optional nonce can be provided which is may be required by the terminal in order enable
* some features. This helps ensure no malicious command injection has occurred.
*
* WARNING: This sequence is unfinalized, DO NOT use this in your shell integration script.
*/
anthonykim1 marked this conversation as resolved.
Show resolved Hide resolved
EnvJson = 'EnvJson',

}

/**
Expand Down Expand Up @@ -418,6 +434,19 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
this._createOrGetCommandDetection(this._terminal).handleContinuationEnd();
return true;
}
case VSCodeOscPt.EnvJson: {
const arg0 = args[0];
const arg1 = args[1];
if (arg0 !== undefined) {
try {
const env = JSON.parse(deserializeMessage(arg0));
this._createOrGetShellEnvDetection().setEnvironment(env, arg1 === this._nonce);
} catch (e) {
this._logService.warn('Failed to parse environment from shell integration sequence', arg0);
}
}
return true;
}
case VSCodeOscPt.RightPromptStart: {
this._createOrGetCommandDetection(this._terminal).handleRightPromptStart();
return true;
Expand Down Expand Up @@ -614,6 +643,15 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
}
return bufferMarkDetection;
}

protected _createOrGetShellEnvDetection(): IShellEnvDetectionCapability {
let shellEnvDetection = this.capabilities.get(TerminalCapability.ShellEnvDetection);
if (!shellEnvDetection) {
shellEnvDetection = this._register(new ShellEnvDetectionCapability());
this.capabilities.add(TerminalCapability.ShellEnvDetection, shellEnvDetection);
}
return shellEnvDetection;
}
}

export function deserializeMessage(message: string): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ export class MainThreadTerminalShellIntegration extends Disposable implements Ma
this._proxy.$cwdChange(e.instance.instanceId, this._convertCwdToUri(e.data));
}));

// onDidChangeTerminalShellIntegrationEnvironment
anthonykim1 marked this conversation as resolved.
Show resolved Hide resolved
const envChangeEvent = this._store.add(this._terminalService.createOnInstanceCapabilityEvent(TerminalCapability.ShellEnvDetection, e => e.onDidChangeEnv));
this._store.add(envChangeEvent.event(e => {
const keysArr = Array.from(e.data.keys());
const valuesArr = Array.from(e.data.values());
this._proxy.$shellEnvChange(e.instance.instanceId, keysArr, valuesArr);
}));

// onDidStartTerminalShellExecution
const commandDetectionStartEvent = this._store.add(this._terminalService.createOnInstanceCapabilityEvent(TerminalCapability.CommandDetection, e => e.onCommandExecuted));
let currentCommand: ITerminalCommand | undefined;
Expand Down
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 @@ -2439,6 +2439,7 @@ export interface ExtHostTerminalShellIntegrationShape {
$shellExecutionStart(instanceId: number, commandLineValue: string, commandLineConfidence: TerminalShellExecutionCommandLineConfidence, isTrusted: boolean, cwd: UriComponents | undefined): void;
$shellExecutionEnd(instanceId: number, commandLineValue: string, commandLineConfidence: TerminalShellExecutionCommandLineConfidence, isTrusted: boolean, exitCode: number | undefined): void;
$shellExecutionData(instanceId: number, data: string): void;
$shellEnvChange(instanceId: number, shellEnvKeys: string[], shellEnvValues: string[]): void;
$cwdChange(instanceId: number, cwd: UriComponents | undefined): void;
$closeTerminal(instanceId: number): void;
}
Expand Down
24 changes: 23 additions & 1 deletion src/vs/workbench/api/common/extHostTerminalShellIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ export class ExtHostTerminalShellIntegration extends Disposable implements IExtH
this._activeShellIntegrations.get(instanceId)?.emitData(data);
}

public $shellEnvChange(instanceId: number, shellEnvKeys: string[], shellEnvValues: string[]): void {
this._activeShellIntegrations.get(instanceId)?.setEnv(shellEnvKeys, shellEnvValues);
}

public $cwdChange(instanceId: number, cwd: UriComponents | undefined): void {
this._activeShellIntegrations.get(instanceId)?.setCwd(URI.revive(cwd));
}
Expand All @@ -147,6 +151,7 @@ class InternalTerminalShellIntegration extends Disposable {
get currentExecution(): InternalTerminalShellExecution | undefined { return this._currentExecution; }

private _ignoreNextExecution: boolean = false;
private _env: { [key: string]: string | undefined } | undefined;
private _cwd: URI | undefined;

readonly store: DisposableStore = this._register(new DisposableStore());
Expand All @@ -171,6 +176,9 @@ class InternalTerminalShellIntegration extends Disposable {
get cwd(): URI | undefined {
return that._cwd;
},
get env(): { [key: string]: string | undefined } | undefined {
return that._env;
},
// executeCommand(commandLine: string): vscode.TerminalShellExecution;
// executeCommand(executable: string, args: string[]): vscode.TerminalShellExecution;
executeCommand(commandLineOrExecutable: string, args?: string[]): vscode.TerminalShellExecution {
Expand Down Expand Up @@ -232,6 +240,16 @@ class InternalTerminalShellIntegration extends Disposable {
}
}

setEnv(keys: string[], values: string[]): void {
const env: { [key: string]: string | undefined } = {};
for (let i = 0; i < keys.length; i++) {
env[keys[i]] = values[i];
}
this._env = env;
// TODO: Make sure env changed. If env doesnt change, dont fire event.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do this, can we stop it at the capability time, rather than here or on the exthost?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added them here: d555813
at shellEnvDetectionCapability!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏

this._fireChangeEvent();
}

setCwd(cwd: URI | undefined): void {
let wasChanged = false;
if (URI.isUri(this._cwd)) {
Expand All @@ -241,9 +259,13 @@ class InternalTerminalShellIntegration extends Disposable {
}
if (wasChanged) {
this._cwd = cwd;
this._onDidRequestChangeShellIntegration.fire({ terminal: this._terminal, shellIntegration: this.value });
this._fireChangeEvent();
}
}

private _fireChangeEvent() {
this._onDidRequestChangeShellIntegration.fire({ terminal: this._terminal, shellIntegration: this.value });
}
}

class InternalTerminalShellExecution {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,16 @@ function Global:Prompt() {
# Current working directory
# OSC 633 ; <Property>=<Value> ST
$Result += if ($pwd.Provider.Name -eq 'FileSystem') { "$([char]0x1b)]633;P;Cwd=$(__VSCode-Escape-Value $pwd.ProviderPath)`a" }

# Send current environment variables as JSON
# OSC 633 ; Env ; <Environment> ; <Nonce>
if ($isStable -eq "0") {
$envMap = @{}
Get-ChildItem Env: | ForEach-Object { $envMap[$_.Name] = $_.Value }
$envJson = $envMap | ConvertTo-Json -Compress
$Result += "$([char]0x1b)]633;EnvJson;$(__VSCode-Escape-Value $envJson);$Nonce`a"
}

# Before running the original prompt, put $? back to what it was:
if ($FakeCode -ne 0) {
Write-Error "failure" -ea ignore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,8 @@ suite('ShellIntegrationAddon', () => {
['escaped newline (upper hex)', `${Backslash}x0A`, Newline],
['escaped backslash followed by literal "x0a" is not a newline', `${Backslash}${Backslash}x0a`, `${Backslash}x0a`],
['non-initial escaped backslash followed by literal "x0a" is not a newline', `foo${Backslash}${Backslash}x0a`, `foo${Backslash}x0a`],
['PS1 simple', '[\\u@\\h \\W]\\$', '[\\u@\\h \\W]\\$'],
['PS1 VSC SI', `${Backslash}x1b]633;A${Backslash}x07\\[${Backslash}x1b]0;\\u@\\h:\\w\\a\\]${Backslash}x1b]633;B${Backslash}x07`, '\x1b]633;A\x07\\[\x1b]0;\\u@\\h:\\w\\a\\]\x1b]633;B\x07']
];

cases.forEach(([title, input, expected]) => {
Expand Down
18 changes: 18 additions & 0 deletions src/vscode-dts/vscode.proposed.terminalShellEnv.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

declare module 'vscode' {
// @anthonykim1 @tyriar https://github.com/microsoft/vscode/issues/227467

export interface TerminalShellIntegration {
/**
* The environment of the shell process. This is undefined if the shell integration script
* does not send the environment.
*/
readonly env: { [key: string]: string | undefined } | undefined;
}

// TODO: Is it fine that this shares onDidChangeTerminalShellIntegration with cwd and the shellIntegration object itself?
}
Loading