#!/usr/bin/env python # License: GPLv3 Copyright: 2022, Kovid Goyal import os import shutil import tempfile import unittest from contextlib import contextmanager from kitty.constants import kitty_base_dir, terminfo_dir, is_macos from kitty.fast_data_types import CURSOR_BEAM from kitty.shell_integration import rc_inset, setup_zsh_env from . import BaseTest def safe_env_for_running_shell(home_dir, rc='', shell='zsh'): ans = { 'PATH': os.environ['PATH'], 'HOME': home_dir, 'TERM': 'xterm-kitty', 'TERMINFO': terminfo_dir, 'KITTY_SHELL_INTEGRATION': 'enabled', 'KITTY_INSTALLATION_DIR': kitty_base_dir, } for x in ('USER', 'LANG'): if os.environ.get(x): ans[x] = os.environ[x] if shell == 'zsh': ans['ZLE_RPROMPT_INDENT'] = '0' with open(os.path.join(home_dir, '.zshenv'), 'w') as f: print('unset GLOBAL_RCS', file=f) with open(os.path.join(home_dir, '.zshrc'), 'w') as f: print(rc + '\n', file=f) setup_zsh_env(ans) elif shell == 'bash': ans['ENV'] = '~/.bashrc' with open(os.path.join(home_dir, '.bashrc'), 'w') as f: # get out of POSIX mode print('set +o posix', file=f) # ensure LINES and COLUMNS are kept up to date print('shopt -s checkwinsize', file=f) # Not sure why bash turns off echo in this scenario print('stty echo', file=f) if rc: print(rc, file=f) print(rc_inset('bash'), file=f) return ans def launch_cmd_for_shell(shell): if shell == 'bash': # Sadly we cannot use --noprofile as the idiotic Linux distros compile # bash with -DSYS_BASHRC which causes it to unconditionally source the # system wide bashrc file (which is distro dependent). So we use POSIX # mode. return 'bash --posix' return shell class ShellIntegration(BaseTest): @contextmanager def run_shell(self, shell='zsh', rc='', cmd=''): home_dir = os.path.realpath(tempfile.mkdtemp()) cmd = cmd or launch_cmd_for_shell(shell) try: pty = self.create_pty(cmd.format(**locals()), cwd=home_dir, env=safe_env_for_running_shell(home_dir, rc=rc, shell=shell)) i = 10 while i > 0 and not pty.screen_contents().strip(): pty.process_input_from_child() i -= 1 yield pty finally: if os.path.exists(home_dir): shutil.rmtree(home_dir) @unittest.skipUnless(shutil.which('zsh'), 'zsh not installed') def test_zsh_integration(self): ps1, rps1 = 'left>', ' ', 'line1\nprompt> ', 'line1\\nprompt> ',): with self.subTest(ps1=ps1), self.run_shell( shell='bash', rc=f''' PS1="{ps1}" ''') as pty: ps1 = ps1.replace('\\n', '\n') pty.wait_till(lambda: pty.screen_contents().count(ps1) == 1) pty.send_cmd_to_child('echo test') pty.wait_till(lambda: pty.screen_contents().count(ps1) == 2) self.ae(pty.screen_contents(), f'{ps1}echo test\ntest\n{ps1}') pty.write_to_child(r'echo $COLUMNS') pty.set_window_size(rows=20, columns=40) pty.process_input_from_child() pty.wait_till(redrawn) self.ae(ps1.splitlines()[-1] + 'echo $COLUMNS', str(pty.screen.line(pty.screen.cursor.y))) pty.write_to_child('\r') pty.wait_till(lambda: pty.screen_contents().count(ps1) == 3) self.ae('40', str(pty.screen.line(pty.screen.cursor.y - len(ps1.splitlines())))) self.ae(ps1.splitlines()[-1] + 'echo $COLUMNS', str(pty.screen.line(pty.screen.cursor.y - 1 - len(ps1.splitlines()))))