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.
This commit is contained in:
parent
7bd912c093
commit
cbca4d6593
10 changed files with 192 additions and 110 deletions
|
|
@ -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
|
||||
---------------------
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@
|
|||
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 @@ def match_windows(self, match: str, self_window: Optional['Window'] = None, all_
|
|||
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 @@ def get_matches(location: str, query: str, candidates: set[int]) -> set[int]:
|
|||
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 @@ def match_tabs(self, match: str, all_tabs: Iterable[Tab] | None = None) -> Itera
|
|||
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 @@ def get_matches(location: str, query: str, candidates: set[int]) -> set[int]:
|
|||
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, (
|
||||
|
|
|
|||
|
|
@ -1386,6 +1386,18 @@
|
|||
'''
|
||||
)
|
||||
|
||||
opt('tab_bar_filter', '', long_text='''
|
||||
A :ref:`search expression <search_syntax>`. 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 <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='''
|
||||
|
|
|
|||
3
kitty/options/parse.py
generated
3
kitty/options/parse.py
generated
|
|
@ -1270,6 +1270,9 @@ def tab_bar_background(self, val: str, ans: dict[str, typing.Any]) -> None:
|
|||
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)
|
||||
|
||||
|
|
|
|||
2
kitty/options/types.py
generated
2
kitty/options/types.py
generated
|
|
@ -432,6 +432,7 @@
|
|||
'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
|
||||
|
|
|
|||
|
|
@ -114,7 +114,8 @@ def __call__(self, key: str, opt_name: str | None = None, missing: Any = None) -
|
|||
|
||||
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 @@ def __call__(self, key: str, opt_name: str | None = None, missing: Any = None) -
|
|||
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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -512,6 +512,19 @@ def draw_tab(
|
|||
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 @@ def __init__(self, os_window_id: int):
|
|||
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 @@ def update(self, data: Sequence[TabBarData]) -> None:
|
|||
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 @@ def draw_tab(i: int, tab: TabBarData, cell_ranges: list[tuple[int, int]], max_ta
|
|||
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 @@ def draw_tab(i: int, tab: TabBarData, cell_ranges: list[tuple[int, int]], max_ta
|
|||
|
||||
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
|
||||
|
|
|
|||
197
kitty/tabs.py
197
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 @@ def list_windows(self, self_window: Window | None = None, window_filter: Callabl
|
|||
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 @@ def matches_query(self, field: str, query: str, active_tab_manager: Optional['Ta
|
|||
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 @@ def set_active_tab(self, tab: Tab, for_keep_focus: Tab | None = None) -> bool:
|
|||
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 @@ def toggle_tab(self, match_expression: str) -> None:
|
|||
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 @@ def skey(tab: Tab) -> int:
|
|||
|
||||
@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 @@ def tab_for_id(self, tab_id: int) -> Tab | None:
|
|||
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 @@ def new_tab(
|
|||
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 @@ def new_tab(
|
|||
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 @@ def new_tab(
|
|||
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 @@ def tab_bar_data(self) -> list[TabBarData]:
|
|||
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 @@ def handle_click_on_tab(self, x: int, button: int, modifiers: int, action: int)
|
|||
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()
|
||||
|
||||
|
|
|
|||
|
|
@ -839,7 +839,7 @@ def at_prompt(self) -> bool:
|
|||
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 @@ def matches(self, field: str, pat: MatchPatternType, active_session: str) -> boo
|
|||
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 @@ def matches_query(
|
|||
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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue