From 681a2b7b28346e301e9719e5c0df50c3125061ea Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 19 Jul 2024 14:54:44 +0530 Subject: [PATCH] Sessions: A new command focus_matching_window to shift focus to a specific window, useful when creating complex layouts with splits --- docs/changelog.rst | 2 ++ docs/overview.rst | 19 +++++++++++++++++++ kitty/session.py | 14 ++++++++++++++ kitty/tabs.py | 17 ++++++++++++++++- 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index bbfdbf56b..8a1b35f92 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -76,6 +76,8 @@ Detailed list of changes - Add NERD fonts builtin so that users don't have to install them to use NERD symbols in kitty. The builtin font is used only if the symbols are not available in some system font +- Sessions: A new command ``focus_matching_window`` to shift focus to a specific window, useful when creating complex layouts with splits (:disc:`7635`) + - Wayland: Allow fractional scales less than one (:pull:`7549`) - Wayland: Fix specifying the output name for the panel kitten not working (:iss:`7573`) diff --git a/docs/overview.rst b/docs/overview.rst index 345a2e9b2..f8a21de68 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -176,6 +176,25 @@ option in :file:`kitty.conf`. An example, showing all available commands: focus_os_window launch emacs + # Create a complex layout using multiple splits. Creates two columns of + # windows with two windows in each column. The windows in the firt column are + # split 50:50. In the second column the windows are not evenly split. + new_tab complex tab + layout splits + # First window, set a user variable on it so we can focus it later + launch --var window=first + # Create the second column by splitting the first window vertically + launch --location=vsplit + # Create the third window in the second column by splitting the second window horizontally + launch --location=hsplit + # Make the third window shorter so that the split is not even + resize_window shorter 5 + # Go back to focusing the first window, so that we can split it + focus_matching_window var:window=first + # Create the final window in the first column + launch --location=hsplit + + .. note:: The :doc:`launch ` command when used in a session file cannot create new OS windows, or tabs. diff --git a/kitty/session.py b/kitty/session.py index e334f6a74..588046991 100644 --- a/kitty/session.py +++ b/kitty/session.py @@ -39,6 +39,7 @@ class WindowSpec: def __init__(self, launch_spec: Union['LaunchSpec', 'SpecialWindowInstance']): self.launch_spec = launch_spec self.resize_spec: Optional[ResizeSpec] = None + self.focus_matching_window_spec: str = '' self.is_background_process = False if hasattr(launch_spec, 'opts'): # LaunchSpec from .launch import LaunchSpec @@ -51,6 +52,7 @@ class Tab: def __init__(self, opts: Options, name: str): self.windows: List[WindowSpec] = [] self.pending_resize_spec: Optional[ResizeSpec] = None + self.pending_focus_matching_window: str = '' self.name = name.strip() self.active_window_idx = 0 self.enabled_layouts = opts.enabled_layouts @@ -122,6 +124,9 @@ class Session: if t.pending_resize_spec is not None: t.windows[-1].resize_spec = t.pending_resize_spec t.pending_resize_spec = None + if t.pending_focus_matching_window: + t.windows[-1].focus_matching_window_spec = t.pending_focus_matching_window + t.pending_focus_matching_window = '' def resize_window(self, args: List[str]) -> None: s = resize_window('resize_window', shlex.join(args))[1] @@ -132,6 +137,13 @@ class Session: else: t.pending_resize_spec = spec + def focus_matching_window(self, spec: str) -> None: + t = self.tabs[-1] + if t.windows: + t.windows[-1].focus_matching_window_spec = spec + else: + t.pending_focus_matching_window = spec + def add_special_window(self, sw: 'SpecialWindowInstance') -> None: self.tabs[-1].windows.append(WindowSpec(sw)) @@ -202,6 +214,8 @@ def parse_session(raw: str, opts: Options, environ: Optional[Mapping[str, str]] ans.os_window_state = rest elif cmd == 'resize_window': ans.resize_window(rest.split()) + elif cmd == 'focus_matching_window': + ans.focus_matching_window(rest) else: raise ValueError(f'Unknown command in session file: {cmd}') yield finalize_session(ans) diff --git a/kitty/tabs.py b/kitty/tabs.py index e9729f505..498b33378 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -39,6 +39,7 @@ from .fast_data_types import ( attach_window, current_focused_os_window_id, detach_window, + focus_os_window, get_boss, get_click_interval, get_options, @@ -211,15 +212,29 @@ class Tab: # {{{ self.mark_tab_bar_dirty() def startup(self, session_tab: 'SessionTab') -> None: + target_tab = self + boss = get_boss() for window in session_tab.windows: spec = window.launch_spec if isinstance(spec, SpecialWindowInstance): self.new_special_window(spec) else: from .launch import launch - launch(get_boss(), spec.opts, spec.args, target_tab=self, force_target_tab=True) + launched_window = launch(boss, spec.opts, spec.args, target_tab=target_tab, force_target_tab=True) if window.resize_spec is not None: self.resize_window(*window.resize_spec) + if window.focus_matching_window_spec: + for w in boss.match_windows(window.focus_matching_window_spec, launched_window or boss.active_window): + tab = w.tabref() + if tab: + target_tab = tab or self + tm = tab.tab_manager_ref() + if tm and boss.active_tab is not target_tab: + tm.set_active_tab(target_tab) + if target_tab.active_window is not w: + target_tab.set_active_window(w) + if current_focused_os_window_id() != w.os_window_id: + focus_os_window(w.os_window_id, True) with suppress(IndexError): self.windows.set_active_window_group_for(self.windows.all_windows[session_tab.active_window_idx])