diff --git a/docs/changelog.rst b/docs/changelog.rst index aac698a98..345d147b2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -184,6 +184,11 @@ Detailed list of changes - Fix a regression in 0.43.0 that caused :opt:`tab_bar_margin_width` to be doubled on the right edge of the tab bar (:iss:`9154`) +- Session files: Add a new ``focus_tab`` command to specify which tab should be + active when a session is loaded. Accepts either a plain number (0-based index) + or a match expression for flexible tab selection, allowing sessions to preserve + the active tab state (:doc:`sessions`) + 0.43.1 [2025-10-01] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/sessions.rst b/docs/sessions.rst index 5fc62d5b2..8b379c35a 100644 --- a/docs/sessions.rst +++ b/docs/sessions.rst @@ -154,6 +154,14 @@ all the major keywords you can use in kitty session files: focus_os_window launch emacs + # Create another tab + new_tab logs + launch tail -f /var/log/syslog + + # Focus the first tab (index 0) when the session loads + # You can also use a match expression like: focus_tab title:logs + focus_tab 0 + # Create a complex layout using multiple splits. Creates two columns of # windows with two windows in each column. The windows in the first column are # split 50:50. In the second column the windows are not evenly split. @@ -272,6 +280,15 @@ documentation for them. otherwise the window manager might block changing focus to prevent *focus stealing*. +``focus_tab [tab specifier]`` + Set which tab should be active (focused) in the current OS Window. The tab + specifier can be either a plain number (treated as a 0-based index) or a + match expression. For example, ``focus_tab 0`` will focus the first tab, + ``focus_tab 1`` the second tab, and ``focus_tab title:logs`` will focus the + tab whose title matches "logs". See :ref:`search_syntax` for the full syntax + of match expressions. This is useful for session files that create multiple + tabs and want to ensure a specific tab is active when the session is loaded. + ``enabled_layouts comma separated list of layout names`` Set the layouts allowed in the current tab. Same syntax as :opt:`enabled_layouts`. diff --git a/kitty/session.py b/kitty/session.py index b69583d44..e6e175dd4 100644 --- a/kitty/session.py +++ b/kitty/session.py @@ -88,6 +88,7 @@ class Session: def __init__(self, default_title: str | None = None): self.tabs: list[Tab] = [] self.active_tab_idx = 0 + self.focus_tab_spec: str | None = None self.default_title = default_title self.os_window_size: WindowSizes | None = None self.os_window_class: str | None = None @@ -177,6 +178,9 @@ def focus(self) -> None: self.active_tab_idx = max(0, len(self.tabs) - 1) self.tabs[-1].active_window_idx = max(0, len(self.tabs[-1].windows) - 1) + def focus_tab(self, spec: str) -> None: + self.focus_tab_spec = spec + def set_enabled_layouts(self, raw: str) -> None: self.tabs[-1].enabled_layouts = to_layout_names(raw) if self.tabs[-1].layout not in self.tabs[-1].enabled_layouts: @@ -250,6 +254,8 @@ def finalize_session(ans: Session) -> Session: ans.add_window(rest, expand) elif cmd == 'focus': ans.focus() + elif cmd == 'focus_tab': + ans.focus_tab(rest) elif cmd == 'focus_os_window': ans.focus_os_window = True elif cmd == 'enabled_layouts': @@ -372,8 +378,9 @@ def skey(w: WindowType) -> float: seen_session_paths: dict[str, str] = {} -def create_session(boss: BossType, path: str) -> str: +def create_session(boss: BossType, path: str) -> tuple[str, bool]: session_name = '' + created_new_os_window = False for i, s in enumerate(create_sessions(get_options(), default_session=path)): if i == 0: session_name = s.session_name @@ -382,14 +389,16 @@ def create_session(boss: BossType, path: str) -> str: tm = boss.active_tab_manager if tm is None: os_window_id = boss.add_os_window(s) + created_new_os_window = True else: os_window_id = tm.os_window_id tm.add_tabs_from_session(s, session_name) else: os_window_id = boss.add_os_window(s) + created_new_os_window = True if s.focus_os_window: boss.focus_os_window(os_window_id) - return session_name + return session_name, created_new_os_window goto_session_history: list[str] = [] @@ -569,14 +578,15 @@ def goto_session(boss: BossType, cmdline: Sequence[str]) -> None: if switch_to_session(boss, session_name): return try: - session_name = create_session(boss, path) + session_name, created_new_os_window = create_session(boss, path) except Exception: import traceback tb = traceback.format_exc() boss.show_error(_('Failed to create session'), _('Could not create session from {0} with error:\n{1}').format(path, tb)) else: # Ensure newly created session is focused needed when it doesn't create its own OS Windows. - switch_to_session(boss, session_name) + if not created_new_os_window: + switch_to_session(boss, session_name) save_as_session_message = '''\ diff --git a/kitty/tabs.py b/kitty/tabs.py index 011dd5871..d2eafa2f3 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -1064,6 +1064,24 @@ def add_tabs_from_session(self, session: SessionType, session_name: str = '') -> self._add_tab(tab) if i == session.active_tab_idx: active_tab = tab + + # Handle focus_tab_spec if specified + if session.focus_tab_spec is not None: + spec = session.focus_tab_spec.strip() + # Try to parse as a plain number (index) + try: + idx = int(spec) + # Clamp to valid range + idx = max(0, min(idx, len(self.tabs) - 1)) + active_tab = self.tabs[idx] + except ValueError: + # Not a plain number, treat as match expression + from .fast_data_types import get_boss + boss = get_boss() + matched_tabs = list(boss.match_tabs(spec, self.tabs)) + if matched_tabs: + active_tab = matched_tabs[0] + if active_tab is not None: idx = self.tabs.index(active_tab) self._set_active_tab(idx)