From cbca4d659375257c307ff75c96d60b9793fa9ad0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 24 Aug 2025 12:25:15 +0530 Subject: [PATCH] Implement tab_bar_filter Useful to manage multiple sessions in a single kitty OS Window. Add some docs to sessions.rst describing this use case. --- docs/sessions.rst | 14 +++ kitty/boss.py | 8 +- kitty/options/definition.py | 12 +++ kitty/options/parse.py | 3 + kitty/options/types.py | 2 + kitty/rc/base.py | 8 +- kitty/session.py | 4 + kitty/tab_bar.py | 39 ++++--- kitty/tabs.py | 197 ++++++++++++++++++++---------------- kitty/window.py | 15 ++- 10 files changed, 192 insertions(+), 110 deletions(-) diff --git a/docs/sessions.rst b/docs/sessions.rst index f8a911f2d..892dcdb77 100644 --- a/docs/sessions.rst +++ b/docs/sessions.rst @@ -229,6 +229,20 @@ When you run the session file in another kitty instance you will see both windows re-created, as expected with the correct working directories and running programs. +Managing multi tab sessions in a single OS Window +---------------------------------------------------- + +The natural way to organise sessions in kitty is one per :term:`os_window`. +However, if you prefer to manage multiple sessions in a single OS Window, you +can configure the kitty tab bar to only show tabs that belong to the currently +active session. To do so, use :opt:`tab_bar_filter` in :file:`kitty.conf` set:: + + tab_bar_filter session:~ or session:^$ + +This will restrict the tab bar to only showing tabs from the currently active +session as well tabs that do not belong to any session. Furthermore, when you +are in a window or tab that does not belong to any session, the tab bar will +show the tabs from the most recent active session, to maintain context. Keyword reference --------------------- diff --git a/kitty/boss.py b/kitty/boss.py index 17b61c4b4..9a690f230 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -129,6 +129,7 @@ from .session import ( default_save_as_session_opts, get_os_window_sizing_data, goto_session, + most_recent_session, save_as_session, ) from .shaders import load_shader_programs @@ -546,6 +547,7 @@ class Boss: wids = {w.id for w in all_windows} window_id_limit = max(wids, default=-1) + 1 active_session = self.active_session + prev_active_session = most_recent_session() def get_matches(location: str, query: str, candidates: set[int]) -> set[int]: if location == 'id' and query.startswith('-'): @@ -555,7 +557,7 @@ class Boss: return set() if q < 0: query = str(window_id_limit + q) - return {wid for wid in candidates if self.window_id_map[wid].matches_query(location, query, tab, self_window, active_session)} + return {wid for wid in candidates if self.window_id_map[wid].matches_query(location, query, tab, self_window, active_session, prev_active_session)} for wid in search(match, ( 'id', 'title', 'pid', 'cwd', 'cmdline', 'num', 'env', 'var', 'recent', 'state', 'neighbor', 'session', @@ -574,6 +576,8 @@ class Boss: tim = {t.id: t for t in all_tabs} tab_id_limit = max(tim, default=-1) + 1 window_id_limit = max(self.window_id_map, default=-1) + 1 + active_session = self.active_session + prev_active_session = most_recent_session() def get_matches(location: str, query: str, candidates: set[int]) -> set[int]: if location in ('id', 'window_id') and query.startswith('-'): @@ -584,7 +588,7 @@ class Boss: if q < 0: limit = tab_id_limit if location == 'id' else window_id_limit query = str(limit + q) - return {wid for wid in candidates if tim[wid].matches_query(location, query, tm)} + return {wid for wid in candidates if tim[wid].matches_query(location, query, tm, active_session, prev_active_session)} found = False for tid in search(match, ( diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 6162ff2e0..c8c7e58d6 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -1386,6 +1386,18 @@ The tab bar style, can be one of: ''' ) +opt('tab_bar_filter', '', long_text=''' +A :ref:`search expression `. Only tabs that match this expression +will be shown in the tab bar. The currently active tab is :italic:`always` shown, +regardless of whether it matches or not. When using this option, the tab bar may +be displayed with less tabs than specified in :opt:`tab_bar_min_tabs`, as evaluating +the filter is expensive and is done only at display time. This is most useful when +using :ref:`sessions `. An expression of :code:`session:~ or session:^$` +will show only tabs that belong to the current session or no session. The various +tab navigation actions such as :ac:`goto_tab`, :ac:`next_tab`, :ac:`previous_tab`, etc. +are automatically restricted to work only on matching tabs. +''') + opt('tab_bar_align', 'left', choices=('left', 'center', 'right'), long_text=''' diff --git a/kitty/options/parse.py b/kitty/options/parse.py index b9bcd51b1..a98aa5ccb 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -1270,6 +1270,9 @@ class Parser: def tab_bar_edge(self, val: str, ans: dict[str, typing.Any]) -> None: ans['tab_bar_edge'] = tab_bar_edge(val) + def tab_bar_filter(self, val: str, ans: dict[str, typing.Any]) -> None: + ans['tab_bar_filter'] = str(val) + def tab_bar_margin_color(self, val: str, ans: dict[str, typing.Any]) -> None: ans['tab_bar_margin_color'] = to_color_or_none(val) diff --git a/kitty/options/types.py b/kitty/options/types.py index 18ec1f166..d68dabe70 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -432,6 +432,7 @@ option_names = ( 'tab_bar_align', 'tab_bar_background', 'tab_bar_edge', + 'tab_bar_filter', 'tab_bar_margin_color', 'tab_bar_margin_height', 'tab_bar_margin_width', @@ -604,6 +605,7 @@ class Options: tab_bar_align: choices_for_tab_bar_align = 'left' tab_bar_background: kitty.fast_data_types.Color | None = None tab_bar_edge: int = 8 + tab_bar_filter: str = '' tab_bar_margin_color: kitty.fast_data_types.Color | None = None tab_bar_margin_height: TabBarMarginHeight = TabBarMarginHeight(outer=0, inner=0) tab_bar_margin_width: float = 0 diff --git a/kitty/rc/base.py b/kitty/rc/base.py index e8a77daea..00883293c 100644 --- a/kitty/rc/base.py +++ b/kitty/rc/base.py @@ -114,7 +114,8 @@ The field :code:`neighbor` refers to a neighbor of the active window in the spec The field :code:`session` matches windows that were created in the specified session. Use the expression :code:`^$` to match windows that were not created in a session and -:code:`.` to match the currently active session. +:code:`.` to match the currently active session and :code:`~` to match either the currently +active sesison or the last active session when no session is active. When using the :code:`env` field to match on environment variables, you can specify only the environment variable name or a name and value, for example, :code:`env:MY_ENV_VAR=2`. @@ -160,8 +161,9 @@ The :code:`recent` number matches recently active tabs in the currently active O active tab, one the previously active tab and so on. The field :code:`session` matches tabs that were created in the specified session. -Use the expression :code:`^$` to match tabs that were not created in a session and -:code:`.` to match the currently active session. +Use the expression :code:`^$` to match windows that were not created in a session and +:code:`.` to match the currently active session and :code:`~` to match either the currently +active sesison or the last active session when no session is active. When using the :code:`env` field to match on environment variables, you can specify only the environment variable name or a name and value, for example, :code:`env:MY_ENV_VAR=2`. Tabs containing any window with the specified environment diff --git a/kitty/session.py b/kitty/session.py index d296245de..096d908b3 100644 --- a/kitty/session.py +++ b/kitty/session.py @@ -396,6 +396,10 @@ def append_to_session_history(name: str) -> None: goto_session_history.append(name) +def most_recent_session() -> str: + return goto_session_history[-1] if goto_session_history else '' + + def switch_to_session(boss: BossType, session_name: str) -> bool: w = window_for_session_name(boss, session_name) if w is not None: diff --git a/kitty/tab_bar.py b/kitty/tab_bar.py index 51804201f..d65528e73 100644 --- a/kitty/tab_bar.py +++ b/kitty/tab_bar.py @@ -512,6 +512,19 @@ def load_custom_draw_tab() -> DrawTabFunc: return draw_tab +class CellRange(NamedTuple): + start: int + end: int + + +class TabExtent(NamedTuple): + tab_id: int + cell_range: CellRange + + def shifted(self, shift: int) -> 'TabExtent': + return TabExtent(self.tab_id, CellRange(self.cell_range.start + shift, self.cell_range.end + shift)) + + class TabBar: def __init__(self, os_window_id: int): @@ -519,7 +532,7 @@ class TabBar: self.num_tabs = 1 self.data_buffer_size = 0 self.blank_rects: tuple[Border, ...] = () - self.cell_ranges: list[tuple[int, int]] = [] + self.tab_extents: Sequence[TabExtent] = () self.laid_out_once = False self.apply_options() @@ -673,7 +686,7 @@ class TabBar: last_tab = data[-1] if data else None ed = ExtraData() - def draw_tab(i: int, tab: TabBarData, cell_ranges: list[tuple[int, int]], max_tab_length: int) -> None: + def draw_tab(i: int, tab: TabBarData, cell_ranges: list[TabExtent], max_tab_length: int) -> None: ed.prev_tab = data[i - 1] if i > 0 else None ed.next_tab = data[i + 1] if i + 1 < len(data) else None s.cursor.bg = as_rgb(self.draw_data.tab_bg(t)) @@ -682,7 +695,7 @@ class TabBar: before = s.cursor.x end = self.draw_func(self.draw_data, s, t, before, max_tab_length, i + 1, t is last_tab, ed) s.cursor.bg = s.cursor.fg = 0 - cell_ranges.append((before, end)) + cell_ranges.append(TabExtent(tab_id=tab.tab_id, cell_range=CellRange(before, end))) if not ed.for_layout and t is not last_tab and s.cursor.x > s.columns - max_tab_lengths[i+1]: # Stop if there is no space for next tab s.cursor.x = s.columns - 2 @@ -722,36 +735,36 @@ class TabBar: s.cursor.x = 0 s.erase_in_line(2, False) - cr: list[tuple[int, int]] = [] + cr: list[TabExtent] = [] ed.for_layout = False for i, t in enumerate(data): try: draw_tab(i, t, cr, max_tab_lengths[i]) except StopIteration: break - self.cell_ranges = cr + self.tab_extents = cr s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab self.align() update_tab_bar_edge_colors(self.os_window_id) def align_with_factor(self, factor: int = 1) -> None: - if not self.cell_ranges: + if not self.tab_extents: return - end = self.cell_ranges[-1][1] + end = self.tab_extents[-1].cell_range[1] if end < self.screen.columns - 1: shift = (self.screen.columns - end) // factor self.screen.cursor.x = 0 self.screen.insert_characters(shift) - self.cell_ranges = [(s + shift, e + shift) for (s, e) in self.cell_ranges] + self.tab_extents = tuple(te.shifted(shift) for te in self.tab_extents) def destroy(self) -> None: self.screen.reset_callbacks() del self.screen - def tab_at(self, x: int) -> int | None: + def tab_id_at(self, x: int) -> int: if self.laid_out_once: x = (x - self.window_geometry.left) // self.cell_width - for i, (a, b) in enumerate(self.cell_ranges): - if a <= x <= b: - return i - return None + for te in self.tab_extents: + if te.cell_range.start <= x <= te.cell_range.end: + return te.tab_id + return 0 diff --git a/kitty/tabs.py b/kitty/tabs.py index 5aa58e92f..2696d8e2e 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -64,7 +64,7 @@ class TabMouseEvent(NamedTuple): modifiers: int action: int at: float - tab_idx: int | None + tab_id: int = 0 class TabDict(TypedDict): @@ -926,7 +926,10 @@ class Tab: # {{{ def list_groups(self) -> list[dict[str, Any]]: return [g.as_simple_dict() for g in self.windows.groups] - def matches_query(self, field: str, query: str, active_tab_manager: Optional['TabManager'] = None) -> bool: + def matches_query( + self, field: str, query: str, active_tab_manager: Optional['TabManager'] = None, + active_session: str = '', most_recent_session: str = '' + ) -> bool: if field == 'title': return re.search(query, self.effective_title) is not None if field == 'id': @@ -962,8 +965,11 @@ class Tab: # {{{ return active_tab_manager is not None and self.tab_manager_ref() is active_tab_manager and self.os_window_id == last_focused_os_window_id() return False if field == 'session': - if query == '.': - return self.created_in_session_name == get_boss().active_session + match query: + case '.': + return self.created_in_session_name == active_session + case '~': + return self.created_in_session_name == active_session or self.created_in_session_name == most_recent_session return re.search(query, self.created_in_session_name) is not None return False @@ -1148,9 +1154,38 @@ class TabManager: # {{{ h.pop() return True + def filtered_tabs(self, filter_expression: str) -> Iterator[Tab]: + yield from get_boss().match_tabs(filter_expression, all_tabs=self) + + @property + def tabs_to_be_shown_in_tab_bar(self) -> Iterable[Tab]: + f = get_options().tab_bar_filter + if f: + at = self.active_tab + m = set(self.filtered_tabs(f)) + return (t for t in self if t is at or t in m) + return self.tabs + + @property + def tabs_to_be_shown_in_tab_bar_as_sequence(self) -> Sequence[Tab]: + f = get_options().tab_bar_filter + if f: + at = self.active_tab + m = set(self.filtered_tabs(f)) + return tuple(t for t in self if t is at or t in m) + return self.tabs + def next_tab(self, delta: int = 1) -> None: - if len(self.tabs) > 1: - self.set_active_tab_idx((self.active_tab_idx + len(self.tabs) + delta) % len(self.tabs)) + if (tabs := self.tabs_to_be_shown_in_tab_bar_as_sequence) is self.tabs: + if (num := len(tabs)) > 1: + self.set_active_tab_idx((self.active_tab_idx + num + delta) % num) + else: + num = len(tabs) + at = self.active_tab + if at is not None: + active_idx = tabs.index(at) + new_active_tab = (active_idx + num + delta) % num + self.set_active_tab(tabs[new_active_tab]) def toggle_tab(self, match_expression: str) -> None: tabs = set(get_boss().match_tabs(match_expression, all_tabs=self)) @@ -1165,32 +1200,29 @@ class TabManager: # {{{ break def tab_at_location(self, loc: str) -> Tab | None: + tabs = self.tabs_to_be_shown_in_tab_bar_as_sequence if loc == 'prev': if self.active_tab_history: - old_active_tab_id = self.active_tab_history[-1] - for idx, tab in enumerate(self.tabs): - if tab.id == old_active_tab_id: - return tab + return self.tab_for_id(self.active_tab_history[-1]) elif loc in ('left', 'right'): delta = -1 if loc == 'left' else 1 - idx = (len(self.tabs) + self.active_tab_idx + delta) % len(self.tabs) - return self.tabs[idx] + idx = (len(tabs) + self.active_tab_idx + delta) % len(tabs) + return tabs[idx] return None def goto_tab(self, tab_num: int) -> None: - if tab_num >= len(self.tabs): - tab_num = max(0, len(self.tabs) - 1) + tabs = self.tabs_to_be_shown_in_tab_bar_as_sequence + if tab_num >= len(tabs): + tab_num = max(0, len(tabs) - 1) if tab_num >= 0: - self.set_active_tab_idx(tab_num) + self.set_active_tab(tabs[tab_num]) elif self.active_tab_history: try: old_active_tab_id = self.active_tab_history[tab_num] except IndexError: old_active_tab_id = self.active_tab_history[0] - for idx, tab in enumerate(self.tabs): - if tab.id == old_active_tab_id: - self.set_active_tab_idx(idx) - break + if tab := self.tab_for_id(old_active_tab_id): + self.set_active_tab(tab) def nth_active_tab(self, n: int = 0) -> Tab | None: if n <= 0: @@ -1259,24 +1291,11 @@ class TabManager: # {{{ @property def active_tab(self) -> Tab | None: - try: - return self.tabs[self.active_tab_idx] if self.tabs else None - except Exception: - return None + return self.tabs[self.active_tab_idx] if 0 <= self.active_tab_idx < len(self.tabs) else None @property def active_window(self) -> Window | None: - t = self.active_tab - if t is not None: - return t.active_window - return None - - @property - def number_of_windows(self) -> int: - count = 0 - for tab in self: - count += len(tab) - return count + return t.active_window if (t := self.active_tab) else None def tab_for_id(self, tab_id: int) -> Tab | None: for t in self.tabs: @@ -1285,9 +1304,11 @@ class TabManager: # {{{ return None def move_tab(self, delta: int = 1) -> None: - if len(self.tabs) > 1: + tabs = self.tabs_to_be_shown_in_tab_bar_as_sequence + if len(tabs) > 1: idx = self.active_tab_idx - nidx = (idx + len(self.tabs) + delta) % len(self.tabs) + new_active_tab = tabs[(idx + len(tabs) + delta) % len(tabs)] + nidx = self.tabs.index(new_active_tab) step = 1 if idx < nidx else -1 for i in range(idx, nidx, step): self.tabs[i], self.tabs[i + step] = self.tabs[i + step], self.tabs[i] @@ -1304,7 +1325,10 @@ class TabManager: # {{{ location: str = 'last', ) -> Tab: idx = len(self.tabs) - orig_active_tab_idx = self.active_tab_idx + tabs = self.tabs_to_be_shown_in_tab_bar_as_sequence + orig_active_tab_idx = 0 + with suppress(ValueError): + orig_active_tab_idx = tabs.index(self.active_tab) session_name = '' if cwd_from is not None and (sw := cwd_from.window): session_name = sw.created_in_session_name @@ -1320,11 +1344,12 @@ class TabManager: # {{{ location = 'after' if location == 'default': location = 'last' - if len(self.tabs) > 1 and location != 'last': + if len(tabs) > 1 and location != 'last': if location == 'first': desired_idx = 0 else: desired_idx = orig_active_tab_idx + (0 if location == 'before' else 1) + desired_idx = self.tabs.index(tabs[desired_idx]) if idx != desired_idx: for i in range(idx, desired_idx, -1): self.tabs[i], self.tabs[i-1] = self.tabs[i-1], self.tabs[i] @@ -1332,64 +1357,63 @@ class TabManager: # {{{ idx = desired_idx self._set_active_tab(idx) self.mark_tab_bar_dirty() - return self.tabs[idx] + return t - def remove(self, tab: Tab) -> None: + def remove(self, removed_tab: Tab) -> None: active_tab_before_removal = self.active_tab - active_tab_needs_to_change = active_tab_before_removal is tab - self._remove_tab(tab) + tabs = self.tabs_to_be_shown_in_tab_bar_as_sequence + self._remove_tab(removed_tab) while True: try: - self.active_tab_history.remove(tab.id) + self.active_tab_history.remove(removed_tab.id) except ValueError: break - def idx_for_id(tab_id: int) -> int: - for idx, qtab in enumerate(self.tabs): - if qtab.id == tab_id: - return idx - return -1 - - def remove_from_end_of_active_history(idx: int) -> None: - while self.active_tab_history and idx_for_id(self.active_tab_history[-1]) == idx: + def remove_from_end_of_active_history(tab: Tab) -> None: + while self.active_tab_history and self.active_tab_history[-1] == tab.id: self.active_tab_history.pop() - if active_tab_needs_to_change: - next_active_tab = -1 - tss = get_options().tab_switch_strategy - if tss == 'previous': - while self.active_tab_history and next_active_tab < 0: - tab_id = self.active_tab_history.pop() - next_active_tab = idx_for_id(tab_id) - elif tss == 'left': - next_active_tab = max(0, self.active_tab_idx - 1) - remove_from_end_of_active_history(next_active_tab) - elif tss == 'right': - next_active_tab = min(self.active_tab_idx, len(self.tabs) - 1) - remove_from_end_of_active_history(next_active_tab) - elif tss == 'last': - next_active_tab = len(self.tabs) - 1 - remove_from_end_of_active_history(next_active_tab) - - if next_active_tab < 0: - next_active_tab = max(0, min(self.active_tab_idx, len(self.tabs) - 1)) - - self._set_active_tab(next_active_tab, store_in_history=False) - elif active_tab_before_removal is not None: - try: - idx = self.tabs.index(active_tab_before_removal) - except Exception: - pass + if active_tab_before_removal is removed_tab: + if len(self.tabs) == 0: + self._active_tab_idx = 0 + elif len(self.tabs) == 1: + remove_from_end_of_active_history(self.tabs[0]) + self._set_active_tab(0, store_in_history=False) else: - self._active_tab_idx = idx + next_active_tab: Tab | None = None + match get_options().tab_switch_strategy: + case 'previous': + while self.active_tab_history and next_active_tab is None: + tab_id = self.active_tab_history.pop() + next_active_tab = self.tab_for_id(tab_id) + case 'left': + next_active_tab = tabs[(tabs.index(active_tab_before_removal) - 1 + len(tabs)) % len(tabs)] + remove_from_end_of_active_history(next_active_tab) + case 'right': + next_active_tab = tabs[(tabs.index(active_tab_before_removal) + 1) % len(tabs)] + remove_from_end_of_active_history(next_active_tab) + case 'last': + next_active_tab = tabs[-1] + remove_from_end_of_active_history(next_active_tab) + if next_active_tab not in self.tabs: + next_active_tab = self.tabs[max(0, min(self.active_tab_idx, len(self.tabs) - 1))] + self._set_active_tab(self.tabs.index(next_active_tab), store_in_history=False) + else: + if len(self.tabs): + if active_tab_before_removal is None: + self._set_active_tab(0, store_in_history=False) + else: + self._set_active_tab(self.tabs.index(active_tab_before_removal), store_in_history=False) + else: + self._active_tab_idx = 0 self.mark_tab_bar_dirty() - tab.destroy() + removed_tab.destroy() @property def tab_bar_data(self) -> list[TabBarData]: at = self.active_tab ans = [] - for t in self.tabs: + for t in self.tabs_to_be_shown_in_tab_bar: title = t.name or t.title or appname needs_attention = False has_activity_since_last_focus = False @@ -1409,16 +1433,16 @@ class TabManager: # {{{ return ans def handle_click_on_tab(self, x: int, button: int, modifiers: int, action: int) -> None: - i = self.tab_bar.tab_at(x) + tab = self.tab_for_id(self.tab_bar.tab_id_at(x)) now = monotonic() - if i is None: + if tab is None: if button == GLFW_MOUSE_BUTTON_LEFT and action == GLFW_RELEASE and len(self.recent_mouse_events) > 2: ci = get_click_interval() prev, prev2 = self.recent_mouse_events[-1], self.recent_mouse_events[-2] if ( prev.button == button and prev2.button == button and prev.action == GLFW_PRESS and prev2.action == GLFW_RELEASE and - prev.tab_idx is None and prev2.tab_idx is None and + prev.tab_id == 0 and prev2.tab_id == 0 and now - prev.at <= ci and now - prev2.at <= 2 * ci ): # double click self.new_tab() @@ -1426,13 +1450,12 @@ class TabManager: # {{{ return else: if action == GLFW_PRESS and button == GLFW_MOUSE_BUTTON_LEFT: - self.set_active_tab_idx(i) + self.set_active_tab(tab) elif button == GLFW_MOUSE_BUTTON_MIDDLE and action == GLFW_RELEASE and self.recent_mouse_events: p = self.recent_mouse_events[-1] - if p.button == button and p.action == GLFW_PRESS and p.tab_idx == i: - tab = self.tabs[i] + if p.button == button and p.action == GLFW_PRESS and p.tab_id == tab.id: get_boss().close_tab(tab) - self.recent_mouse_events.append(TabMouseEvent(button, modifiers, action, now, i)) + self.recent_mouse_events.append(TabMouseEvent(button, modifiers, action, now, tab.id if tab else 0)) if len(self.recent_mouse_events) > 5: self.recent_mouse_events.popleft() diff --git a/kitty/window.py b/kitty/window.py index abbdf1b89..a8627d52a 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -839,7 +839,7 @@ class Window: def has_running_program(self) -> bool: return not self.at_prompt - def matches(self, field: str, pat: MatchPatternType, active_session: str) -> bool: + def matches(self, field: str, pat: MatchPatternType, active_session: str, most_recent_session: str) -> bool: if isinstance(pat, tuple): if field == 'env': return key_val_matcher(self.child.environ.items(), *pat) @@ -861,14 +861,19 @@ class Window: return True return False if field == 'session': - if pat.pattern == '.': - return self.created_in_session_name == active_session + match pat.pattern: + case '.': + return self.created_in_session_name == active_session + case '~': + return self.created_in_session_name == active_session or self.created_in_session_name == most_recent_session + return pat.search(self.created_in_session_name) is not None return False def matches_query( self, field: str, query: str, active_tab: TabType | None = None, - self_window: Optional['Window'] = None, active_session: str = '' + self_window: Optional['Window'] = None, active_session: str = '', + most_recent_session: str = '', ) -> bool: if field in ('num', 'recent'): if active_tab is not None: @@ -918,7 +923,7 @@ class Window: return gid is not None and t.windows.active_window_in_group_id(gid) is self pat = compile_match_query(query, field not in ('env', 'var')) - return self.matches(field, pat, active_session) + return self.matches(field, pat, active_session, most_recent_session) def set_visible_in_layout(self, val: bool) -> None: val = bool(val)