diff --git a/docs/kittens/ssh.rst b/docs/kittens/ssh.rst index a6e281266..a989ca081 100644 --- a/docs/kittens/ssh.rst +++ b/docs/kittens/ssh.rst @@ -27,6 +27,13 @@ rc files: So you can now type just ``s hostname`` to connect. +If you define a mapping in :file:`kitty.conf` such as:: + + map f1 new_window_with_cwd + +Then, pressing :kbd:`F1` will open a new window automatically logged +into the same server using the ssh kitten, at the same directory. + The ssh kitten can be configured using the :file:`~/.config/kitty/ssh.conf` file where you can specify environment variables to set on the remote server and files to copy from your local machine to the remote server. Let's see a diff --git a/docs/shell-integration.rst b/docs/shell-integration.rst index 006496662..43ce00ab6 100644 --- a/docs/shell-integration.rst +++ b/docs/shell-integration.rst @@ -68,7 +68,10 @@ no-title setting functionality instead. no-cwd - Turn off reporting + Turn off reporting the current working directory. This is used to allow + :ref:`action-new_window_with_cwd` and similar to open windows logged + into remote machines using the :doc:`ssh kitten ` + automatically with the same working directory as the current window. no-prompt-mark Turn off marking of prompts. This disables jumping to prompt, browsing diff --git a/kittens/ssh/main.py b/kittens/ssh/main.py index c8457e5e2..592d5dbbf 100644 --- a/kittens/ssh/main.py +++ b/kittens/ssh/main.py @@ -42,6 +42,27 @@ from .copy import CopyInstruction from .options.types import Options as SSHOptions from .options.utils import DELETE_ENV_VAR + +def is_kitten_cmdline(q: List[str]) -> bool: + if len(q) < 4: + return False + if os.path.basename(q[0]).lower() != 'kitty': + return False + return q[1:3] == ['+kitten', 'ssh'] or q[1:4] == ['+', 'kitten', 'ssh'] + + +def set_cwd_in_cmdline(cwd: str, argv: List[str]) -> None: + for i, arg in enumerate(tuple(argv)): + if arg.startswith('--kitten=cwd'): + argv[i] = f'--kitten=cwd={cwd}' + return + elif i > 0 and argv[i-1] == '--kitten' and (arg.startswith('cwd=') or arg.startswith('cwd ')): + argv[i] = cwd + return + idx = argv.index('ssh') + argv.insert(idx + 1, f'--kitten=cwd={cwd}') + + # See https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html quote_pat = re.compile('([\\`"\n])') diff --git a/kitty/child.py b/kitty/child.py index 95a29e425..a210630b5 100644 --- a/kitty/child.py +++ b/kitty/child.py @@ -201,7 +201,7 @@ class Child: self.argv = list(argv) if cwd_from is not None: try: - cwd = cwd_from.cwd_of_child or cwd + cwd = cwd_from.modify_argv_for_launch_with_cwd(self.argv) or cwd except Exception as err: log_error(f'Failed to read cwd of {cwd_from} with error: {err}') else: diff --git a/kitty/utils.py b/kitty/utils.py index 5eebbd924..b6fc0d7ee 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -936,3 +936,12 @@ def cleanup_ssh_control_masters() -> None: for x in files: with suppress(OSError): os.remove(x) + + +def path_from_osc7_url(url: str) -> str: + if url.startswith('kitty-shell-cwd://'): + return '/' + url.split('/', 3)[-1] + if url.startswith('file://'): + from urllib.parse import urlparse + return urlparse(url).path + return '' diff --git a/kitty/window.py b/kitty/window.py index f1f08e516..e1b0adacd 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -20,7 +20,7 @@ from typing import ( from .child import ProcessDesc from .cli_stub import CLIOptions from .config import build_ansi_color_table -from .constants import appname, is_macos, wakeup +from .constants import appname, is_macos, shell_path, wakeup from .fast_data_types import ( BGIMAGE_PROGRAM, BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM, CELL_PROGRAM, CELL_SPECIAL_PROGRAM, CURSOR_BEAM, CURSOR_BLOCK, @@ -46,8 +46,8 @@ from .types import MouseEvent, WindowGeometry, ac from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict from .utils import ( get_primary_selection, kitty_ansi_sanitizer_pat, load_shaders, log_error, - open_cmd, open_url, parse_color_set, resolve_custom_file, sanitize_title, - set_primary_selection + open_cmd, open_url, parse_color_set, path_from_osc7_url, + resolve_custom_file, sanitize_title, set_primary_selection ) MatchPatternType = Union[Pattern[str], Tuple[Pattern[str], Optional[Pattern[str]]]] @@ -323,6 +323,7 @@ def cmd_output(screen: Screen, which: CommandOutput = CommandOutput.last_run, as def process_remote_print(msg: str) -> str: from base64 import standard_b64decode + from .cli import green text = standard_b64decode(msg).decode('utf-8', 'replace') return text.replace('\x1b', green(r'\e')).replace('\a', green(r'\a')).replace('\0', green(r'\0')) @@ -1138,6 +1139,23 @@ class Window: def cwd_of_child(self) -> Optional[str]: return self.child.foreground_cwd or self.child.current_cwd + def modify_argv_for_launch_with_cwd(self, argv: List[str]) -> str: + if argv[0] != shell_path or not self.screen.last_reported_cwd: + return self.cwd_of_child or '' + from kittens.ssh.main import is_kitten_cmdline, set_cwd_in_cmdline + ssh_kitten_cmdline: List[str] = [] + for p in self.child.foreground_processes: + q = list(p['cmdline'] or ()) + if is_kitten_cmdline(q): + ssh_kitten_cmdline = q + break + if ssh_kitten_cmdline: + cwd = path_from_osc7_url(self.screen.last_reported_cwd) + if cwd: + set_cwd_in_cmdline(path_from_osc7_url(self.screen.last_reported_cwd), ssh_kitten_cmdline) + argv[:] = ssh_kitten_cmdline + return self.cwd_of_child or '' + def pipe_data(self, text: str, has_wrap_markers: bool = False) -> PipeData: text = text or '' if has_wrap_markers: