Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions build/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export const copyrightFilter = Object.freeze<string[]>([
'!**/*.sh',
'!**/*.zsh',
'!**/*.fish',
'!**/*.xsh',
'!**/*.txt',
'!**/*.xpm',
'!**/*.opts',
Expand Down
26 changes: 26 additions & 0 deletions src/vs/platform/terminal/node/terminalEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,20 @@ export async function getShellIntegrationInjection(
});
return { type, newArgs, envMixin, filesToCopy };
}
case 'xonsh': {
if (!originalArgs || originalArgs.length === 0) {
newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Xonsh);
} else if (areXonshLoginArgs(originalArgs)) {
newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.XonshLogin);
Comment on lines +277 to +281
}
if (!newArgs) {
return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.UnsupportedArgs };
}

newArgs = [...newArgs];
newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot);
return { type, newArgs, envMixin };
}
}
logService.warn(`Shell integration cannot be enabled for executable "${shellLaunchConfig.executable}" and args`, shellLaunchConfig.args);
return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.UnsupportedShell };
Expand Down Expand Up @@ -325,6 +339,8 @@ enum ShellIntegrationExecutable {
Bash = 'bash',
Fish = 'fish',
FishLogin = 'fish-login',
Xonsh = 'xonsh',
XonshLogin = 'xonsh-login',
}

const shellIntegrationArgs: Map<ShellIntegrationExecutable, string[]> = new Map();
Expand All @@ -338,10 +354,13 @@ shellIntegrationArgs.set(ShellIntegrationExecutable.ZshLogin, ['-il']);
shellIntegrationArgs.set(ShellIntegrationExecutable.Bash, ['--init-file', '{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh']);
shellIntegrationArgs.set(ShellIntegrationExecutable.Fish, ['--init-command', 'source "{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish"']);
shellIntegrationArgs.set(ShellIntegrationExecutable.FishLogin, ['-l', '--init-command', 'source "{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish"']);
shellIntegrationArgs.set(ShellIntegrationExecutable.Xonsh, ['-i', '--rc', '{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh']);
Comment thread
wajdiJomaa marked this conversation as resolved.
shellIntegrationArgs.set(ShellIntegrationExecutable.XonshLogin, ['-l', '-i', '--rc', '{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh']);
Comment on lines +357 to +358
const pwshLoginArgs = ['-login', '-l'];
const shLoginArgs = ['--login', '-l'];
const shInteractiveArgs = ['-i', '--interactive'];
const pwshImpliedArgs = ['-nol', '-nologo'];
const xonshLoginArgs = ['--login', '-l'];

function arePwshLoginArgs(originalArgs: SingleOrMany<string>): boolean {
if (isString(originalArgs)) {
Expand Down Expand Up @@ -370,6 +389,13 @@ function areZshBashFishLoginArgs(originalArgs: SingleOrMany<string>): boolean {
|| !isString(originalArgs) && originalArgs.length === 1 && shLoginArgs.includes(originalArgs[0].toLowerCase());
}

function areXonshLoginArgs(originalArgs: SingleOrMany<string>): boolean {
if (isString(originalArgs)) {
return xonshLoginArgs.includes(originalArgs.toLowerCase());
}
return originalArgs.length === 1 && xonshLoginArgs.includes(originalArgs[0].toLowerCase());
}

/**
* Patterns that indicate sensitive environment variable names.
*/
Expand Down
2 changes: 2 additions & 0 deletions src/vs/platform/terminal/node/terminalProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,8 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellType, value: GeneralShellType.Python });
} else if (sanitizedTitle.toLowerCase().startsWith('julia')) {
this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellType, value: GeneralShellType.Julia });
} else if (sanitizedTitle.toLowerCase().includes('xonsh')) {
this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellType, value: GeneralShellType.Xonsh });
} else {
const shellTypeValue = posixShellTypeMap.get(sanitizedTitle) || generalShellTypeMap.get(sanitizedTitle);
this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellType, value: shellTypeValue });
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# ---------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# ---------------------------------------------------------------------------------------------
import sys
import os

class _VSCodeXonshShellIntegration:
"""vscode shell integration for xonsh shell."""

def __init__(self):
if 'VSCODE_INJECTION' in __xonsh__.env:
self._vscode_nonce = __xonsh__.env.get('VSCODE_NONCE', '')
self.stdout = sys.stdout
self.vsc_apply_xonshrc()
self.vsc_prompt_fields()
self.add_vsc_to_prompt()
self.add_vsc_events()
__xonsh__.env['VSCODE_SHELL_INTEGRATION'] = '1'
del __xonsh__.env['VSCODE_INJECTION']

def vsc_apply_xonshrc(self):
vscode_xonshrc = __xonsh__.env.get('XONSHRC', '')
if vscode_xonshrc:
for _vsc_rc_file in vscode_xonshrc:
if _vsc_rc_file and os.path.isfile(_vsc_rc_file):
try:
source @(_vsc_rc_file)
except Exception:
pass

def _write_begin_osc(self):
self.stdout.write("\x1b]")

def _write_end_osc(self):
self.stdout.write("\x07")
self.stdout.flush()

def write_osc(self, msg):
self._write_begin_osc()
self.stdout.write(f"{msg}")
self._write_end_osc()

def vsc_prompt_fields(self):
"""Generate prompt with A marker before it"""
$PROMPT_FIELDS['vsc_prompt_start'] = "\001\x1b" + "]633;A" + "\x07\002"
$PROMPT_FIELDS['vsc_prompt_end'] = "\001\x1b" + "]633;B" + "\x07\002"


def add_vsc_to_prompt(self):
"""Add vscode markers to prompt"""
if 'vsc_prompt_start' not in $PROMPT:
$PROMPT = '{vsc_prompt_start}' + $PROMPT + '{vsc_prompt_end}'


def _vsc_escape_value(self, value):
"""
Escape a value for use in OSC sequences.
Backslashes are doubled, semicolons and control chars are hex-escaped.
"""
if not value:
return ''

result = []
for char in value:
code = ord(char)
if char == '\n':
result.append(f'\\x{code:02x}')
rctrl_u = chr(21)
result.append(f'\\x{ord(rctrl_u):02x}')
elif code < 32:
result.append(f'\\x{code:02x}')
elif char == '\\':
result.append('\\\\')
elif char == ';':
result.append('\\x3b')
else:
result.append(char)

return ''.join(result)

def _vsc_command_executed(self, cmd):
"""
Send OSC 633 ; E and C - Command line and execution started
note: When sending E before C - vscode is overriding the sent command
"""
trimmed_cmd = cmd.strip() if cmd else ''
escaped_cmd = self._vsc_escape_value(trimmed_cmd)
self.write_osc(f'633;C')
self.write_osc(f'633;E;{escaped_cmd};{self._vscode_nonce}')

def _vsc_command_finished(self, cmd, exit_code=None):
"""Send OSC 633 ; D - Command finished with exit code"""
if not cmd or cmd.strip() == '' or exit_code is None:
# Empty command (just pressed Enter)
self.write_osc('633;D')
else:
self.write_osc(f'633;D;{exit_code}')

def _vsc_cwd_updated(self, newdir):
self.write_osc(f'633;P;Cwd={self._vsc_escape_value(newdir)}')

def add_vsc_events(self):
@events.on_precommand
def _vsc_on_precommand(cmd, **kwargs):
"""Called before a command is executed"""
self._vsc_command_executed(cmd)

@events.on_postcommand
def _vsc_on_postcommand(cmd, rtn, out, ts, **kwargs):
"""Called after a command is executed"""
self._vsc_command_finished(cmd, rtn if rtn is not None else 0)

@events.on_pre_prompt
def _vsc_on_pre_prompt(**kwargs):
self.add_vsc_to_prompt()

@events.on_chdir
def _vsc_on_chdir(olddir, newdir):
self._vsc_cwd_updated(newdir)

if 'VSCODE_SHELL_INTEGRATION' not in __xonsh__.env:
__xonsh__.vsc = _VSCodeXonshShellIntegration()
Loading