From 29135a0f2c5978934422e6ca4650493961628813 Mon Sep 17 00:00:00 2001 From: wajdiJomaa Date: Tue, 20 Jan 2026 20:30:15 +0200 Subject: [PATCH 1/7] feat: Add Xonsh shell integration support. --- .../terminal/node/terminalEnvironment.ts | 13 ++ .../common/scripts/shellIntegration-xonsh.xsh | 146 ++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh diff --git a/src/vs/platform/terminal/node/terminalEnvironment.ts b/src/vs/platform/terminal/node/terminalEnvironment.ts index 2eaf38e7e39474..7047bc98bec839 100644 --- a/src/vs/platform/terminal/node/terminalEnvironment.ts +++ b/src/vs/platform/terminal/node/terminalEnvironment.ts @@ -280,6 +280,17 @@ export async function getShellIntegrationInjection( }); return { type, newArgs, envMixin, filesToCopy }; } + case 'xonsh': { + newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Xonsh); + + 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 }; @@ -331,6 +342,7 @@ enum ShellIntegrationExecutable { Bash = 'bash', Fish = 'fish', FishLogin = 'fish-login', + Xonsh = 'xonsh', } const shellIntegrationArgs: Map = new Map(); @@ -344,6 +356,7 @@ 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']); const pwshLoginArgs = ['-login', '-l']; const shLoginArgs = ['--login', '-l']; const shInteractiveArgs = ['-i', '--interactive']; diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh new file mode 100644 index 00000000000000..b6cea12b8e03e0 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh @@ -0,0 +1,146 @@ +import os +import sys + +# Prevent the script from running multiple times +if 'VSCODE_SHELL_INTEGRATION' not in __xonsh__.env: + + __xonsh__.env['VSCODE_SHELL_INTEGRATION'] = '1' + _vscode_nonce = os.environ.pop('VSCODE_NONCE', '') + + def _vsc_escape_value(value: str) -> str: + """ + 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 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(cmd: str): + """Send OSC 633 ; E and C - Command line and execution started""" + # Trim whitespace to match how VS Code stores commands in history + trimmed_cmd = cmd.strip() if cmd else '' + escaped_cmd = _vsc_escape_value(trimmed_cmd) + # Send command executed marker first (VS Code indiscriminately reads buffer here) + print('\x1b]633;C\x07', end='', flush=True) + # Then send command line (overwriting the buffer reading with the correct command) + print(f'\x1b]633;E;{escaped_cmd};{_vscode_nonce}\x07', end='', flush=True) + + def _vsc_command_finished(cmd, exit_code=None): + """Send OSC 633 ; D - Command finished with exit code""" + if not cmd or cmd == '': + # Empty command (just pressed Enter) + print('\x1b]633;D\x07', end='', flush=True) + else: + # Command with exit code + print(f'\x1b]633;D;{exit_code};{_vscode_nonce}\x07', end='', flush=True) + + + + # --- Apply Environment Variable Collections --- + + def _vsc_apply_env_replace(): + """Apply VSCODE_ENV_REPLACE mutations""" + env_replace = os.environ.pop('VSCODE_ENV_REPLACE', '') + if env_replace: + for item in env_replace.split(':'): + if '=' in item: + varname, value = item.split('=', 1) + os.environ[varname] = value + + def _vsc_apply_env_prepend(): + """Apply VSCODE_ENV_PREPEND mutations""" + env_prepend = os.environ.pop('VSCODE_ENV_PREPEND', '') + if env_prepend: + for item in env_prepend.split(':'): + if '=' in item: + varname, value = item.split('=', 1) + current = os.environ.get(varname, '') + os.environ[varname] = value + current + + def _vsc_apply_env_append(): + """Apply VSCODE_ENV_APPEND mutations""" + env_append = os.environ.pop('VSCODE_ENV_APPEND', '') + if env_append: + for item in env_append.split(':'): + if '=' in item: + varname, value = item.split('=', 1) + current = os.environ.get(varname, '') + os.environ[varname] = current + value + + # Apply environment variable collections + _vsc_apply_env_replace() + _vsc_apply_env_prepend() + _vsc_apply_env_append() + + # --- Hook into Xonsh Events --- + + @events.on_precommand + def _vsc_on_precommand(cmd, **kwargs): + """Called before a command is executed""" + _vsc_command_executed(cmd) + + @events.on_postcommand + def _vsc_on_postcommand(cmd, rtn, out, ts, **kwargs): + """Called after a command is executed""" + _vsc_command_finished(cmd, rtn if rtn is not None else 0) + + # --- Source User's RC Files --- + # Since VS Code uses --rc to inject this script, xonsh's normal rc files + # are not loaded. We need to source them manually from $XONSHRC. + + _vsc_xonshrc = os.environ.get('XONSHRC', '') + if _vsc_xonshrc: + for _vsc_rc_file in _vsc_xonshrc.split(':'): + if _vsc_rc_file and os.path.isfile(_vsc_rc_file): + try: + source @(_vsc_rc_file) + except Exception: + # Silently ignore errors in rc files to avoid breaking the terminal + pass + + # Apply path prefix fix (for macOS path_helper issue) + _vsc_path_prefix = os.environ.pop('VSCODE_PATH_PREFIX', '') + if _vsc_path_prefix: + $PATH = _vsc_path_prefix + $PATH + + if 'VSCODE_INJECTION' in os.environ: + del os.environ['VSCODE_INJECTION'] + + + # print("\001\x1b]633;A\x07\002", end="", flush=True) + # Report shell properties + print('\x1b]633;P;HasRichCommandDetection=True\x07', end='', flush=True) + + # --- Set Up Prompts --- + # Print A before prompt and B after prompt + + _vsc_orig_prompt = $PROMPT + + def _vsc_wrapped_prompt(): + """Generate prompt with A marker before it""" + global _vsc_orig_prompt + # Get the original prompt value + if callable(_vsc_orig_prompt): + original = _vsc_orig_prompt() + else: + original = str(_vsc_orig_prompt) + # Return prompt with A and B markers wrapped in \001 and \002 + # \001 and \002 mark non-printing characters for readline to avoid wrapping issues + return f'\001\x1b]633;A\x07\002{original}\001\x1b]633;B\x07\002' + + $PROMPT = _vsc_wrapped_prompt + From a0215dc63bdf132549a88158152d29faa831a3e6 Mon Sep 17 00:00:00 2001 From: wajdiJomaa Date: Wed, 21 Jan 2026 19:07:22 +0200 Subject: [PATCH 2/7] feat: Add Xonsh shell integration support. --- build/filters.ts | 1 + .../common/scripts/shellIntegration-xonsh.xsh | 220 +++++++++--------- 2 files changed, 113 insertions(+), 108 deletions(-) diff --git a/build/filters.ts b/build/filters.ts index c75f9cb02016ac..3339b0d119acc5 100644 --- a/build/filters.ts +++ b/build/filters.ts @@ -166,6 +166,7 @@ export const copyrightFilter = Object.freeze([ '!**/*.sh', '!**/*.zsh', '!**/*.fish', + '!**/*.xsh', '!**/*.txt', '!**/*.xpm', '!**/*.opts', diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh index b6cea12b8e03e0..c87927c1e736c5 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh @@ -1,146 +1,150 @@ -import os +# --------------------------------------------------------------------------------------------- +# 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 -# Prevent the script from running multiple times -if 'VSCODE_SHELL_INTEGRATION' not in __xonsh__.env: - - __xonsh__.env['VSCODE_SHELL_INTEGRATION'] = '1' - _vscode_nonce = os.environ.pop('VSCODE_NONCE', '') - - def _vsc_escape_value(value: str) -> str: - """ - 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 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(cmd: str): - """Send OSC 633 ; E and C - Command line and execution started""" - # Trim whitespace to match how VS Code stores commands in history - trimmed_cmd = cmd.strip() if cmd else '' - escaped_cmd = _vsc_escape_value(trimmed_cmd) - # Send command executed marker first (VS Code indiscriminately reads buffer here) - print('\x1b]633;C\x07', end='', flush=True) - # Then send command line (overwriting the buffer reading with the correct command) - print(f'\x1b]633;E;{escaped_cmd};{_vscode_nonce}\x07', end='', flush=True) - - def _vsc_command_finished(cmd, exit_code=None): - """Send OSC 633 ; D - Command finished with exit code""" - if not cmd or cmd == '': - # Empty command (just pressed Enter) - print('\x1b]633;D\x07', end='', flush=True) - else: - # Command with exit code - print(f'\x1b]633;D;{exit_code};{_vscode_nonce}\x07', end='', flush=True) - +class _vsCodeXonsh: + """vscode shell integration for xonsh shell.""" + def __init__(self): + self._vscode_nonce = __xonsh__.env.get('VSCODE_NONCE', '') - # --- Apply Environment Variable Collections --- + if 'VSCODE_INJECTION' in __xonsh__.env: + self.stdout = sys.stdout + self.vsc_apply_env() + self.vsc_apply_xonshrc() + self.vsc_wrapped_prompt() + self.add_vsc_events() + __xonsh__.env['VSCODE_SHELL_INTEGRATION'] = '1' + del __xonsh__.env['VSCODE_INJECTION'] - def _vsc_apply_env_replace(): + def _vsc_apply_env_replace(self): """Apply VSCODE_ENV_REPLACE mutations""" - env_replace = os.environ.pop('VSCODE_ENV_REPLACE', '') + env_replace = __xonsh__.env.get('VSCODE_ENV_REPLACE', '') if env_replace: - for item in env_replace.split(':'): + for item in env_replace: if '=' in item: varname, value = item.split('=', 1) os.environ[varname] = value - def _vsc_apply_env_prepend(): + def _vsc_apply_env_prepend(self): """Apply VSCODE_ENV_PREPEND mutations""" - env_prepend = os.environ.pop('VSCODE_ENV_PREPEND', '') + env_prepend = __xonsh__.env.get('VSCODE_ENV_PREPEND', '') if env_prepend: - for item in env_prepend.split(':'): + for item in env_prepend: if '=' in item: varname, value = item.split('=', 1) current = os.environ.get(varname, '') os.environ[varname] = value + current - def _vsc_apply_env_append(): + def _vsc_apply_env_append(self): """Apply VSCODE_ENV_APPEND mutations""" - env_append = os.environ.pop('VSCODE_ENV_APPEND', '') + env_append = __xonsh__.env.get('VSCODE_ENV_APPEND', '') if env_append: - for item in env_append.split(':'): + for item in env_append: if '=' in item: varname, value = item.split('=', 1) current = os.environ.get(varname, '') os.environ[varname] = current + value - # Apply environment variable collections - _vsc_apply_env_replace() - _vsc_apply_env_prepend() - _vsc_apply_env_append() - - # --- Hook into Xonsh Events --- + def vsc_apply_env(self): + self._vsc_apply_env_replace() + self._vsc_apply_env_prepend() + self._vsc_apply_env_append() + + 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_wrapped_prompt(self): + """Generate prompt with A marker before it""" + _vsc_orig_prompt = $PROMPT - @events.on_precommand - def _vsc_on_precommand(cmd, **kwargs): - """Called before a command is executed""" - _vsc_command_executed(cmd) + if callable(_vsc_orig_prompt): + original = _vsc_orig_prompt() + else: + original = str(_vsc_orig_prompt) - @events.on_postcommand - def _vsc_on_postcommand(cmd, rtn, out, ts, **kwargs): - """Called after a command is executed""" - _vsc_command_finished(cmd, rtn if rtn is not None else 0) + if '\001' in original: + return original - # --- Source User's RC Files --- - # Since VS Code uses --rc to inject this script, xonsh's normal rc files - # are not loaded. We need to source them manually from $XONSHRC. + $PROMPT = f'\001\x1b]633;A\x07\002{original}\001\x1b]633;B\x07\002' - _vsc_xonshrc = os.environ.get('XONSHRC', '') - if _vsc_xonshrc: - for _vsc_rc_file in _vsc_xonshrc.split(':'): - if _vsc_rc_file and os.path.isfile(_vsc_rc_file): - try: - source @(_vsc_rc_file) - except Exception: - # Silently ignore errors in rc files to avoid breaking the terminal - pass + 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 '' - # Apply path prefix fix (for macOS path_helper issue) - _vsc_path_prefix = os.environ.pop('VSCODE_PATH_PREFIX', '') - if _vsc_path_prefix: - $PATH = _vsc_path_prefix + $PATH + result = [] + for char in value: + code = ord(char) + if code < 32: + result.append(f'\\x{code:02x}') + elif char == '\\': + result.append('\\\\') + elif char == ';': + result.append('\\x3b') + else: + result.append(char) - if 'VSCODE_INJECTION' in os.environ: - del os.environ['VSCODE_INJECTION'] + 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}') - # print("\001\x1b]633;A\x07\002", end="", flush=True) - # Report shell properties - print('\x1b]633;P;HasRichCommandDetection=True\x07', end='', flush=True) + 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}') - # --- Set Up Prompts --- - # Print A before prompt and B after prompt + 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) - _vsc_orig_prompt = $PROMPT + @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) - def _vsc_wrapped_prompt(): - """Generate prompt with A marker before it""" - global _vsc_orig_prompt - # Get the original prompt value - if callable(_vsc_orig_prompt): - original = _vsc_orig_prompt() - else: - original = str(_vsc_orig_prompt) - # Return prompt with A and B markers wrapped in \001 and \002 - # \001 and \002 mark non-printing characters for readline to avoid wrapping issues - return f'\001\x1b]633;A\x07\002{original}\001\x1b]633;B\x07\002' + @events.on_pre_prompt + def _vsc_on_pre_prompt(**kwargs): + self.vsc_wrapped_prompt() - $PROMPT = _vsc_wrapped_prompt +if 'VSCODE_SHELL_INTEGRATION' not in __xonsh__.env: + __xonsh__.vsc = _vsCodeXonsh() From 53eccb2ede0852bc20799eff56d9f1e81e66353c Mon Sep 17 00:00:00 2001 From: wajdiJomaa Date: Wed, 21 Jan 2026 19:15:51 +0200 Subject: [PATCH 3/7] remove env loading for now --- .../common/scripts/shellIntegration-xonsh.xsh | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh index c87927c1e736c5..e69701094f5ef3 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh @@ -13,47 +13,12 @@ class _vsCodeXonsh: if 'VSCODE_INJECTION' in __xonsh__.env: self.stdout = sys.stdout - self.vsc_apply_env() self.vsc_apply_xonshrc() self.vsc_wrapped_prompt() self.add_vsc_events() __xonsh__.env['VSCODE_SHELL_INTEGRATION'] = '1' del __xonsh__.env['VSCODE_INJECTION'] - def _vsc_apply_env_replace(self): - """Apply VSCODE_ENV_REPLACE mutations""" - env_replace = __xonsh__.env.get('VSCODE_ENV_REPLACE', '') - if env_replace: - for item in env_replace: - if '=' in item: - varname, value = item.split('=', 1) - os.environ[varname] = value - - def _vsc_apply_env_prepend(self): - """Apply VSCODE_ENV_PREPEND mutations""" - env_prepend = __xonsh__.env.get('VSCODE_ENV_PREPEND', '') - if env_prepend: - for item in env_prepend: - if '=' in item: - varname, value = item.split('=', 1) - current = os.environ.get(varname, '') - os.environ[varname] = value + current - - def _vsc_apply_env_append(self): - """Apply VSCODE_ENV_APPEND mutations""" - env_append = __xonsh__.env.get('VSCODE_ENV_APPEND', '') - if env_append: - for item in env_append: - if '=' in item: - varname, value = item.split('=', 1) - current = os.environ.get(varname, '') - os.environ[varname] = current + value - - def vsc_apply_env(self): - self._vsc_apply_env_replace() - self._vsc_apply_env_prepend() - self._vsc_apply_env_append() - def vsc_apply_xonshrc(self): vscode_xonshrc = __xonsh__.env.get('XONSHRC', '') if vscode_xonshrc: From c53479ac5533fbdbd9cacd2ea6feb16416a2d9a1 Mon Sep 17 00:00:00 2001 From: wajdiJomaa Date: Wed, 21 Jan 2026 19:20:52 +0200 Subject: [PATCH 4/7] fix --- .../contrib/terminal/common/scripts/shellIntegration-xonsh.xsh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh index e69701094f5ef3..5c6aac2c1e170a 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh @@ -9,9 +9,8 @@ class _vsCodeXonsh: """vscode shell integration for xonsh shell.""" def __init__(self): - self._vscode_nonce = __xonsh__.env.get('VSCODE_NONCE', '') - if 'VSCODE_INJECTION' in __xonsh__.env: + self._vscode_nonce = __xonsh__.env.get('VSCODE_NONCE', '') self.stdout = sys.stdout self.vsc_apply_xonshrc() self.vsc_wrapped_prompt() From fa76b0514607babf870dfdc2f26e0a807eb5bdbe Mon Sep 17 00:00:00 2001 From: wajdiJomaa Date: Mon, 26 Jan 2026 17:16:39 +0200 Subject: [PATCH 5/7] fix indentation problem when running recent command --- .../common/scripts/shellIntegration-xonsh.xsh | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh index 5c6aac2c1e170a..a87ce77818d8fc 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh @@ -13,7 +13,8 @@ class _vsCodeXonsh: self._vscode_nonce = __xonsh__.env.get('VSCODE_NONCE', '') self.stdout = sys.stdout self.vsc_apply_xonshrc() - self.vsc_wrapped_prompt() + self.vsc_prompt_fields() + self.add_vsc_to_prompt() self.add_vsc_events() __xonsh__.env['VSCODE_SHELL_INTEGRATION'] = '1' del __xonsh__.env['VSCODE_INJECTION'] @@ -40,19 +41,17 @@ class _vsCodeXonsh: self.stdout.write(f"{msg}") self._write_end_osc() - def vsc_wrapped_prompt(self): + def vsc_prompt_fields(self): """Generate prompt with A marker before it""" - _vsc_orig_prompt = $PROMPT + $PROMPT_FIELDS['vsc_prompt_start'] = "\001\x1b" + "]633;A" + "\x07\002" + $PROMPT_FIELDS['vsc_prompt_end'] = "\001\x1b" + "]633;B" + "\x07\002" - if callable(_vsc_orig_prompt): - original = _vsc_orig_prompt() - else: - original = str(_vsc_orig_prompt) - if '\001' in original: - return original + 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}' - $PROMPT = f'\001\x1b]633;A\x07\002{original}\001\x1b]633;B\x07\002' def _vsc_escape_value(self, value): """ @@ -65,7 +64,11 @@ class _vsCodeXonsh: result = [] for char in value: code = ord(char) - if code < 32: + 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('\\\\') @@ -86,6 +89,7 @@ class _vsCodeXonsh: 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: @@ -107,7 +111,7 @@ class _vsCodeXonsh: @events.on_pre_prompt def _vsc_on_pre_prompt(**kwargs): - self.vsc_wrapped_prompt() + self.add_vsc_to_prompt() if 'VSCODE_SHELL_INTEGRATION' not in __xonsh__.env: From 4f1408def85d0551e36c21d4f996633e1145fbbe Mon Sep 17 00:00:00 2001 From: wajdiJomaa Date: Mon, 2 Feb 2026 19:29:03 +0200 Subject: [PATCH 6/7] - fix shell type issue - add support for xonsh login shell variant - add support for cwd sequence --- .../terminal/node/terminalEnvironment.ts | 17 +++++++++++++++-- .../platform/terminal/node/terminalProcess.ts | 2 ++ .../common/scripts/shellIntegration-xonsh.xsh | 11 ++++++++--- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/terminal/node/terminalEnvironment.ts b/src/vs/platform/terminal/node/terminalEnvironment.ts index d95914071f4973..bc0e43965ba3bd 100644 --- a/src/vs/platform/terminal/node/terminalEnvironment.ts +++ b/src/vs/platform/terminal/node/terminalEnvironment.ts @@ -281,8 +281,11 @@ export async function getShellIntegrationInjection( return { type, newArgs, envMixin, filesToCopy }; } case 'xonsh': { - newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Xonsh); - + if (!originalArgs || originalArgs.length === 0) { + newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Xonsh); + } else if (areXonshLoginArgs(originalArgs)) { + newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.XonshLogin); + } if (!newArgs) { return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.UnsupportedArgs }; } @@ -343,6 +346,7 @@ enum ShellIntegrationExecutable { Fish = 'fish', FishLogin = 'fish-login', Xonsh = 'xonsh', + XonshLogin = 'xonsh-login', } const shellIntegrationArgs: Map = new Map(); @@ -357,10 +361,12 @@ shellIntegrationArgs.set(ShellIntegrationExecutable.Bash, ['--init-file', '{0}/o 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']); +shellIntegrationArgs.set(ShellIntegrationExecutable.XonshLogin, ['-l', '-i', '--rc', '{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh']); const pwshLoginArgs = ['-login', '-l']; const shLoginArgs = ['--login', '-l']; const shInteractiveArgs = ['-i', '--interactive']; const pwshImpliedArgs = ['-nol', '-nologo']; +const xonshLoginArgs = ['--login', '-l']; function arePwshLoginArgs(originalArgs: SingleOrMany): boolean { if (isString(originalArgs)) { @@ -388,3 +394,10 @@ function areZshBashFishLoginArgs(originalArgs: SingleOrMany): boolean { return isString(originalArgs) && shLoginArgs.includes(originalArgs.toLowerCase()) || !isString(originalArgs) && originalArgs.length === 1 && shLoginArgs.includes(originalArgs[0].toLowerCase()); } + +function areXonshLoginArgs(originalArgs: SingleOrMany): boolean { + if (isString(originalArgs)) { + return xonshLoginArgs.includes(originalArgs.toLowerCase()); + } + return originalArgs.length === 1 && xonshLoginArgs.includes(originalArgs[0].toLowerCase()); +} diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index b1117a9304c92b..a13e60b6ede073 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -429,6 +429,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 }); diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh index a87ce77818d8fc..dcf60db93d4576 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-xonsh.xsh @@ -5,7 +5,7 @@ import sys import os -class _vsCodeXonsh: +class _VSCodeXonshShellIntegration: """vscode shell integration for xonsh shell.""" def __init__(self): @@ -89,7 +89,6 @@ class _vsCodeXonsh: 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: @@ -98,6 +97,9 @@ class _vsCodeXonsh: 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): @@ -113,6 +115,9 @@ class _vsCodeXonsh: 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 = _vsCodeXonsh() + __xonsh__.vsc = _VSCodeXonshShellIntegration() From 3ea07f34193e0ce47339d2daa1923670b40c81cf Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 15 Jul 2026 22:02:57 -0700 Subject: [PATCH 7/7] missing bracket --- src/vs/platform/terminal/node/terminalEnvironment.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/platform/terminal/node/terminalEnvironment.ts b/src/vs/platform/terminal/node/terminalEnvironment.ts index 47a8700ecd4d23..4db5cda0b38788 100644 --- a/src/vs/platform/terminal/node/terminalEnvironment.ts +++ b/src/vs/platform/terminal/node/terminalEnvironment.ts @@ -394,6 +394,8 @@ function areXonshLoginArgs(originalArgs: SingleOrMany): boolean { return xonshLoginArgs.includes(originalArgs.toLowerCase()); } return originalArgs.length === 1 && xonshLoginArgs.includes(originalArgs[0].toLowerCase()); +} + /** * Patterns that indicate sensitive environment variable names. */