diff --git a/kitty/boss.py b/kitty/boss.py index 4f9a36863..6fe3640bc 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -94,11 +94,19 @@ class Boss: if dpi_changed: self.on_dpi_change(os_window_id) - def new_os_window(self, *args): - sw = self.args_to_special_window(args) if args else None - startup_session = create_session(self.opts, special_window=sw) + def _new_os_window(self, args, cwd_from=None): + sw = self.args_to_special_window(args, cwd_from) if args else None + startup_session = create_session(self.opts, special_window=sw, cwd_from=cwd_from) self.add_os_window(startup_session) + def new_os_window(self, *args): + self._new_os_window(args) + + def new_os_window_with_cwd(self, *args): + w = self.active_window + cwd_from = w.child.pid if w is not None else None + self._new_os_window(args, cwd_from) + def add_child(self, window): self.child_monitor.add_child(window.id, window.child.pid, window.child.child_fd, window.screen) self.window_id_map[window.id] = window @@ -357,7 +365,7 @@ class Boss: if tm is not None: tm.next_tab(-1) - def args_to_special_window(self, args): + def args_to_special_window(self, args, cwd_from=None): args = list(args) stdin = None w = self.active_window @@ -383,23 +391,39 @@ class Boss: if not arg: continue cmd.append(arg) - return SpecialWindow(cmd, stdin) + return SpecialWindow(cmd, stdin, cwd_from=cwd_from) - def new_tab(self, *args): + def _new_tab(self, args, cwd_from=None): special_window = None if args: - special_window = self.args_to_special_window(args) + special_window = self.args_to_special_window(args, cwd_from=cwd_from) tm = self.active_tab_manager if tm is not None: - tm.new_tab(special_window=special_window) + tm.new_tab(special_window=special_window, cwd_from=cwd_from) - def new_window(self, *args): + def new_tab(self, *args): + self._new_tab(args) + + def new_tab_with_cwd(self, *args): + w = self.active_window + cwd_from = w.child.pid if w is not None else None + self._new_tab(args, cwd_from=cwd_from) + + def _new_window(self, args, cwd_from=None): tab = self.active_tab if tab is not None: if args: - tab.new_special_window(self.args_to_special_window(args)) + tab.new_special_window(self.args_to_special_window(args, cwd_from=cwd_from)) else: - tab.new_window() + tab.new_window(cwd_from=cwd_from) + + def new_window(self, *args): + self._new_window(args) + + def new_window_with_cwd(self, *args): + w = self.active_window + cwd_from = w.child.pid if w is not None else None + self._new_window(args, cwd_from=cwd_from) def move_tab_forward(self): tm = self.active_tab_manager diff --git a/kitty/child.py b/kitty/child.py index 9dbe61e79..31a6aad1d 100644 --- a/kitty/child.py +++ b/kitty/child.py @@ -8,7 +8,13 @@ import sys import kitty.fast_data_types as fast_data_types -from .constants import terminfo_dir +from .constants import terminfo_dir, is_macos + + +def cwd_of_process(pid): + if is_macos: + raise NotImplementedError('getting cwd of child processes not implemented') + return os.readlink('/proc/{}/cwd'.format(pid)) def remove_cloexec(fd): @@ -20,8 +26,14 @@ class Child: child_fd = pid = None forked = False - def __init__(self, argv, cwd, opts, stdin=None, env=None): + def __init__(self, argv, cwd, opts, stdin=None, env=None, cwd_from=None): self.argv = argv + if cwd_from is not None: + try: + cwd = cwd_of_process(cwd_from) + except Exception: + import traceback + traceback.print_exc() self.cwd = os.path.abspath(os.path.expandvars(os.path.expanduser(cwd or os.getcwd()))) self.opts = opts self.stdin = stdin diff --git a/kitty/config.py b/kitty/config.py index ad40fd1db..8e427f399 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -94,7 +94,7 @@ def parse_shortcut(sc): KeyAction = namedtuple('KeyAction', 'func args') -shlex_actions = {'pass_selection_to_program', 'new_window', 'new_tab'} +shlex_actions = {'pass_selection_to_program', 'new_window', 'new_tab', 'new_os_window', 'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd'} def parse_key_action(action): @@ -350,7 +350,9 @@ with open( defaults = parse_config(f.read().decode('utf-8').splitlines(), check_keys=False) Options = namedtuple('Defaults', ','.join(defaults.keys())) defaults = Options(**defaults) -actions = frozenset(a.func for a in defaults.keymap.values()) | frozenset({'combine', 'send_text', 'goto_tab'}) +actions = frozenset(a.func for a in defaults.keymap.values()) | frozenset( + 'combine send_text goto_tab new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'.split() +) no_op_actions = frozenset({'noop', 'no-op', 'no_op'}) diff --git a/kitty/kitty.conf b/kitty/kitty.conf index 65638b4ba..8d86ed5e7 100644 --- a/kitty/kitty.conf +++ b/kitty/kitty.conf @@ -278,14 +278,20 @@ map ctrl+shift+7 seventh_window map ctrl+shift+8 eighth_window map ctrl+shift+9 ninth_window map ctrl+shift+0 tenth_window -# You can also open a new window running an arbitrary program, for example: +# You can open a new window running an arbitrary program, for example: # map ctrl+shift+y new_window mutt -# You can also pass the current selection to the new program by using the @selection placeholder +# +# You can pass the current selection to the new program by using the @selection placeholder # map ctrl+shift+y new_window less @selection -# Finally, you can even send the contents of the current screen + history buffer as stdin using +# +# You can even send the contents of the current screen + history buffer as stdin using # the placeholders @text (which is the plain text) and @ansi (which includes text styling escape codes) # For example, the following command opens the scrollback buffer in less in a new window. # map ctrl+shift+y new_window @ansi less +G -R +# +# You can open a new window with the current working directory set to the +# working directory of the current window using +# map ctrl+alt+enter new_window_with_cwd # Tab management @@ -301,7 +307,7 @@ map ctrl+shift+, move_tab_backward # map ctrl+alt+2 goto_tab 2 # Just as with new_window above, you can also pass the name of arbitrary -# commands to run when using new_tab. +# commands to run when using new_tab and use new_tab_with_cwd. # Miscellaneous diff --git a/kitty/session.py b/kitty/session.py index ef434bd9f..045d98b84 100644 --- a/kitty/session.py +++ b/kitty/session.py @@ -85,7 +85,7 @@ def parse_session(raw, opts): return ans -def create_session(opts, args=None, special_window=None): +def create_session(opts, args=None, special_window=None, cwd_from=None): if args and args.session: with open(args.session) as f: return parse_session(f.read(), opts) @@ -100,11 +100,11 @@ def create_session(opts, args=None, special_window=None): ans.tabs[-1].layout = current_layout if special_window is None: cmd = args.args if args and args.args else [shell_path] + from kitty.tabs import SpecialWindow if getattr(args, 'title', None): - from kitty.tabs import SpecialWindow - ans.add_window(SpecialWindow(cmd, override_title=args.title)) + ans.add_window(SpecialWindow(cmd, override_title=args.title, cwd_from=cwd_from)) else: - ans.add_window(cmd) + ans.add_window(SpecialWindow(cmd, cwd_from=cwd_from)) else: ans.add_special_window(special_window) return ans diff --git a/kitty/tabs.py b/kitty/tabs.py index 881455e19..8faa13662 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -22,16 +22,16 @@ from .utils import color_as_int from .window import Window, calculate_gl_geometry TabbarData = namedtuple('TabbarData', 'title is_active is_last') -SpecialWindowInstance = namedtuple('SpecialWindow', 'cmd stdin override_title') +SpecialWindowInstance = namedtuple('SpecialWindow', 'cmd stdin override_title cwd_from') -def SpecialWindow(cmd, stdin=None, override_title=None): - return SpecialWindowInstance(cmd, stdin, override_title) +def SpecialWindow(cmd, stdin=None, override_title=None, cwd_from=None): + return SpecialWindowInstance(cmd, stdin, override_title, cwd_from) class Tab: # {{{ - def __init__(self, tab_manager, session_tab=None, special_window=None): + def __init__(self, tab_manager, session_tab=None, special_window=None, cwd_from=None): self.tab_manager_ref = weakref.ref(tab_manager) self.os_window_id = tab_manager.os_window_id self.id = add_tab(self.os_window_id) @@ -50,7 +50,7 @@ class Tab: # {{{ sl = self.enabled_layouts[0] self.current_layout = all_layouts[sl](self.os_window_id, self.opts, self.borders.border_width, self.windows) if special_window is None: - self.new_window() + self.new_window(cwd_from=cwd_from) else: self.new_special_window(special_window) else: @@ -109,7 +109,7 @@ class Tab: # {{{ w.set_visible_in_layout(i, True) self.relayout() - def launch_child(self, use_shell=False, cmd=None, stdin=None): + def launch_child(self, use_shell=False, cmd=None, stdin=None, cwd_from=None): if cmd is None: if use_shell: cmd = [shell_path] @@ -122,12 +122,12 @@ class Tab: # {{{ except Exception: import traceback traceback.print_exc() - ans = Child(cmd, self.cwd, self.opts, stdin, env) + ans = Child(cmd, self.cwd, self.opts, stdin, env, cwd_from) ans.fork() return ans - def new_window(self, use_shell=True, cmd=None, stdin=None, override_title=None): - child = self.launch_child(use_shell=use_shell, cmd=cmd, stdin=stdin) + def new_window(self, use_shell=True, cmd=None, stdin=None, override_title=None, cwd_from=None): + child = self.launch_child(use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from) window = Window(self, child, self.opts, self.args, override_title=override_title) # Must add child before laying out so that resize_pty succeeds get_boss().add_child(window) @@ -401,10 +401,10 @@ class TabManager: # {{{ def title_changed(self, new_title): self.update_tab_bar() - def new_tab(self, special_window=None): + def new_tab(self, special_window=None, cwd_from=None): needs_resize = len(self.tabs) == 1 idx = len(self.tabs) - self._add_tab(Tab(self, special_window=special_window)) + self._add_tab(Tab(self, special_window=special_window, cwd_from=cwd_from)) self._set_active_tab(idx) self.update_tab_bar() if needs_resize: