Merge branch 'session-add-focus-tab' of https://github.com/jackielii/kitty

This commit is contained in:
Kovid Goyal 2025-10-26 18:32:16 +05:30
commit 1866ffe99e
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
4 changed files with 54 additions and 4 deletions

View file

@ -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]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -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`.

View file

@ -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 = '''\

View file

@ -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)