diff --git a/kitty/shell_integration.py b/kitty/shell_integration.py index dc4c6c903..a6445351c 100644 --- a/kitty/shell_integration.py +++ b/kitty/shell_integration.py @@ -31,11 +31,15 @@ def safe_read(path: str) -> str: return '' +def rc_inset(shell_name: str = 'bash', template: str = posix_template) -> str: + return template.format(path=f"shell-integration/{shell_name}/kitty.{shell_name}") + + def setup_integration(shell_name: str, rc_path: str, template: str = posix_template) -> None: import re rc_path = os.path.realpath(rc_path) rc = safe_read(rc_path) - integration = template.format(path=f"shell-integration/{shell_name}/kitty.{shell_name}") + integration = rc_inset(shell_name, template) newrc, num_subs = re.subn( r'^# BEGIN_KITTY_SHELL_INTEGRATION.+?^# END_KITTY_SHELL_INTEGRATION', integration, rc, flags=re.DOTALL | re.MULTILINE) diff --git a/kitty_tests/shell_integration.py b/kitty_tests/shell_integration.py index 309fa66b6..41a65c1a2 100644 --- a/kitty_tests/shell_integration.py +++ b/kitty_tests/shell_integration.py @@ -10,7 +10,7 @@ from contextlib import contextmanager from kitty.constants import kitty_base_dir, terminfo_dir from kitty.fast_data_types import CURSOR_BEAM -from kitty.shell_integration import setup_zsh_env +from kitty.shell_integration import rc_inset, setup_zsh_env from . import BaseTest @@ -34,6 +34,20 @@ def safe_env_for_running_shell(home_dir, rc='', shell='zsh'): with open(os.path.join(home_dir, '.zshrc'), 'w') as f: print(rc + '\n', file=f) setup_zsh_env(ans) + elif shell == 'bash': + with open(os.path.join(home_dir, '.bash_profile'), 'w') as f: + print('if [ -f ~/.bashrc ]; then . ~/.bashrc; fi', file=f) + with open(os.path.join(home_dir, '.bashrc'), 'w') as f: + # there is no way to prevent bash from reading the system + # /etc/profile and some distros set a PROMPT_COMMAND in it. + print('unset PROMPT_COMMAND', 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 @@ -43,7 +57,7 @@ class ShellIntegration(BaseTest): def run_shell(self, shell='zsh', rc='', cmd='{shell} -il'): home_dir = os.path.realpath(tempfile.mkdtemp()) try: - pty = self.create_pty(cmd.format(**locals()), cwd=home_dir, env=safe_env_for_running_shell(home_dir, rc)) + 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() @@ -67,11 +81,11 @@ RPS1="{rps1}" except TimeoutError: raise AssertionError(f'Cursor was not changed to beam. Screen contents: {repr(pty.screen_contents())}') self.ae(pty.screen_contents(), q) - self.ae(pty.callbacks.titlebuf, ['~']) + self.ae(pty.callbacks.titlebuf[-1], '~') pty.callbacks.clear() pty.send_cmd_to_child('mkdir test && ls -a') pty.wait_till(lambda: pty.screen_contents().count(rps1) == 2) - self.ae(pty.callbacks.titlebuf, ['mkdir test && ls -a', '~']) + self.ae(pty.callbacks.titlebuf[-2:], ['mkdir test && ls -a', '~']) q = '\n'.join(str(pty.screen.line(i)) for i in range(1, pty.screen.cursor.y)) self.ae(pty.last_cmd_output(), q) # shrink the screen @@ -90,3 +104,40 @@ RPS1="{rps1}" pty.wait_till(lambda: pty.screen_contents().count(rps1) == 3) self.ae('40', str(pty.screen.line(pty.screen.cursor.y - 1))) self.ae(q, str(pty.screen.line(pty.screen.cursor.y - 2))) + + @unittest.skipUnless(shutil.which('bash'), 'bash not installed') + def test_bash_integration(self): + ps1 = 'prompt> ' + with self.run_shell( + shell='bash', rc=f''' +PS1="{ps1}" +''') as pty: + try: + pty.wait_till(lambda: pty.screen.cursor.shape == CURSOR_BEAM) + except TimeoutError: + raise AssertionError(f'Cursor was not changed to beam. Screen contents: {repr(pty.screen_contents())}') + pty.wait_till(lambda: pty.screen_contents().count(ps1) == 1) + self.ae(pty.screen_contents(), ps1) + pty.wait_till(lambda: pty.callbacks.titlebuf[-1:] == ['~']) + self.ae(pty.callbacks.titlebuf[-1], '~') + pty.callbacks.clear() + pty.send_cmd_to_child('mkdir test && ls -a') + pty.wait_till(lambda: pty.callbacks.titlebuf[-2:] == ['mkdir test && ls -a', '~']) + pty.wait_till(lambda: pty.screen_contents().count(ps1) == 2) + q = '\n'.join(str(pty.screen.line(i)) for i in range(1, pty.screen.cursor.y)) + self.ae(pty.last_cmd_output(), q) + # shrink the screen + pty.write_to_child(r'echo $COLUMNS') + pty.set_window_size(rows=20, columns=40) + pty.process_input_from_child() + + def redrawn(): + q = pty.screen_contents() + return '$COLUMNS' in q and q.count(ps1) == 2 + + pty.wait_till(redrawn) + self.ae(ps1 + '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 - 1))) + self.ae(ps1 + 'echo $COLUMNS', str(pty.screen.line(pty.screen.cursor.y - 2)))