From 399354cbe53c726dbf19ecc246f59de8738b4b34 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 12 Dec 2025 17:11:43 +0530 Subject: [PATCH] Fix tab_bar_min_tabs not tab_bar_filter Fixes #9278 --- docs/changelog.rst | 2 ++ kitty/child-monitor.c | 4 ++-- kitty/fast_data_types.pyi | 2 +- kitty/options/definition.py | 5 ++--- kitty/options/to-c-generated.h | 15 --------------- kitty/state.c | 9 +++++---- kitty/state.h | 2 +- kitty/tabs.py | 31 +++++++++++++++++++------------ 8 files changed, 32 insertions(+), 38 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 79a8cf1e9..c65560734 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -190,6 +190,8 @@ Detailed list of changes - Linux: Fix a bug causing colors to occasionally all go black when using mesa >= 25.3.0 with nouveau GPU driver (:iss:`9235`) +- Fix :opt:`tab_bar_min_tabs` not respecting :opt:`tab_bar_filter` (:iss:`9278`) + 0.44.0 [2025-11-03] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index d1f1fe805..9f3312a1f 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -720,7 +720,7 @@ prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int * !global_state.supports_framebuffer_srgb || effective_os_window_alpha(os_window) < 1.f || os_window->live_resize.in_progress || (os_window->bgimage && os_window->bgimage->texture_id > 0) ); - if (TD.screen && os_window->num_tabs >= OPT(tab_bar_min_tabs)) { + if (TD.screen && os_window->num_tabs && !os_window->has_too_few_tabs) { if (!os_window->tab_bar_data_updated) { call_boss(update_tab_bar_data, "K", os_window->id); os_window->tab_bar_data_updated = true; @@ -817,7 +817,7 @@ render_prepared_os_window(OSWindow *os_window, unsigned int active_window_id, co BorderRects *br = &tab->border_rects; draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, active_window_bg, num_visible_windows, all_windows_have_same_bg, os_window); br->is_dirty = false; - if (TD.screen && os_window->num_tabs >= OPT(tab_bar_min_tabs)) draw_cells(&TD, os_window, true, true, false, NULL); + if (TD.screen && os_window->num_tabs && !os_window->has_too_few_tabs) draw_cells(&TD, os_window, true, true, false, NULL); unsigned int num_of_visible_windows = 0; Window *active_window = NULL; for (unsigned int i = 0; i < tab->num_windows; i++) { if (tab->windows[i].visible) num_of_visible_windows++; } diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index b3681c2ba..b1a68cf54 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1065,7 +1065,7 @@ def next_window_id() -> int: pass -def mark_tab_bar_dirty(os_window_id: int) -> None: +def mark_tab_bar_dirty(os_window_id: int, should_be_shown: bool) -> None: pass diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 29e2ef2b7..09b68ce6b 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -1498,10 +1498,9 @@ The horizontal alignment of the tab bar, can be one of: :code:`left`, ''' ) -opt('tab_bar_min_tabs', '2', - option_type='tab_bar_min_tabs', ctype='uint', +opt('tab_bar_min_tabs', '2', option_type='tab_bar_min_tabs', long_text='The minimum number of tabs that must exist before the tab bar is shown.' - ) +) opt('tab_switch_strategy', 'previous', choices=('last', 'left', 'previous', 'right'), diff --git a/kitty/options/to-c-generated.h b/kitty/options/to-c-generated.h index f29d21b92..a50442051 100644 --- a/kitty/options/to-c-generated.h +++ b/kitty/options/to-c-generated.h @@ -980,19 +980,6 @@ convert_from_opts_tab_bar_style(PyObject *py_opts, Options *opts) { Py_DECREF(ret); } -static void -convert_from_python_tab_bar_min_tabs(PyObject *val, Options *opts) { - opts->tab_bar_min_tabs = PyLong_AsUnsignedLong(val); -} - -static void -convert_from_opts_tab_bar_min_tabs(PyObject *py_opts, Options *opts) { - PyObject *ret = PyObject_GetAttrString(py_opts, "tab_bar_min_tabs"); - if (ret == NULL) return; - convert_from_python_tab_bar_min_tabs(ret, opts); - Py_DECREF(ret); -} - static void convert_from_python_tab_bar_background(PyObject *val, Options *opts) { opts->tab_bar_background = color_or_none_as_int(val); @@ -1509,8 +1496,6 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) { if (PyErr_Occurred()) return false; convert_from_opts_tab_bar_style(py_opts, opts); if (PyErr_Occurred()) return false; - convert_from_opts_tab_bar_min_tabs(py_opts, opts); - if (PyErr_Occurred()) return false; convert_from_opts_tab_bar_background(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_tab_bar_margin_color(py_opts, opts); diff --git a/kitty/state.c b/kitty/state.c index bc0ebb6d4..34b032e8a 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -597,7 +597,7 @@ pyset_borders_rects(PyObject *self UNUSED, PyObject *args) { void os_window_regions(OSWindow *os_window, Region *central, Region *tab_bar) { - if (!OPT(tab_bar_hidden) && os_window->num_tabs >= OPT(tab_bar_min_tabs)) { + if (!OPT(tab_bar_hidden) && os_window->num_tabs && !os_window->has_too_few_tabs) { long margin_outer = pt_to_px_for_os_window(OPT(tab_bar_margin_height.outer), os_window); long margin_inner = pt_to_px_for_os_window(OPT(tab_bar_margin_height.inner), os_window); central->left = 0; central->right = os_window->viewport_width; @@ -950,9 +950,10 @@ PYWRAP1(set_os_window_chrome) { } PYWRAP1(mark_tab_bar_dirty) { - id_type os_window_id = PyLong_AsUnsignedLongLong(args); - if (PyErr_Occurred()) return NULL; + id_type os_window_id; int should_be_shown; + PA("Kp", &os_window_id, &should_be_shown); WITH_OS_WINDOW(os_window_id) + os_window->has_too_few_tabs = !should_be_shown; os_window->tab_bar_data_updated = false; END_WITH_OS_WINDOW Py_RETURN_NONE; @@ -1519,7 +1520,7 @@ static PyMethodDef module_methods[] = { MW(current_application_quit_request, METH_NOARGS), MW(set_os_window_chrome, METH_VARARGS), MW(focus_os_window, METH_VARARGS), - MW(mark_tab_bar_dirty, METH_O), + MW(mark_tab_bar_dirty, METH_VARARGS), MW(run_with_activation_token, METH_O), MW(change_background_opacity, METH_VARARGS), MW(background_opacity_of, METH_O), diff --git a/kitty/state.h b/kitty/state.h index a6f0d6f88..fa74a1f6d 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -91,7 +91,6 @@ typedef struct Options { bool dynamic_background_opacity; float inactive_text_alpha; Edge tab_bar_edge; - unsigned long tab_bar_min_tabs; DisableLigature disable_ligatures; bool force_ltr; bool resize_in_steps; @@ -323,6 +322,7 @@ typedef struct OSWindow { bool has_received_cursor_pos_event; double mouse_x, mouse_y; bool mouse_button_pressed[32]; + bool has_too_few_tabs; PyObject *window_title; bool disallow_title_changes, title_is_overriden; bool viewport_size_dirty, viewport_updated_at_least_once; diff --git a/kitty/tabs.py b/kitty/tabs.py index cc0c3b630..df51f9011 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -1138,7 +1138,14 @@ class TabManager: # {{{ @property def tab_bar_should_be_visible(self) -> bool: - return len(self.tabs) >= get_options().tab_bar_min_tabs + count = get_options().tab_bar_min_tabs + if count < 1: + return True + for t in self.tabs_to_be_shown_in_tab_bar: + count -= 1 + if count < 1: + return True + return count < 1 def _add_tab(self, tab: Tab) -> None: visible_before = self.tab_bar_should_be_visible @@ -1160,9 +1167,14 @@ class TabManager: # {{{ self._active_tab_idx = idx set_active_tab(self.os_window_id, idx) + def layout_tab_bar(self) -> None: + # set tab_bar_should_be_visible so that tab_bar.layout() gets correct dimensions + self.mark_tab_bar_dirty() + self.tab_bar.layout() + def tabbar_visibility_changed(self) -> None: if not self.tab_bar_hidden: - self.tab_bar.layout() + self.layout_tab_bar() self.resize(only_tabs=True) @property @@ -1173,8 +1185,8 @@ class TabManager: # {{{ return None def mark_tab_bar_dirty(self) -> None: - if self.tab_bar_should_be_visible and not self.tab_bar_hidden: - mark_tab_bar_dirty(self.os_window_id) + should_be_shown = self.tab_bar_should_be_visible and not self.tab_bar_hidden + mark_tab_bar_dirty(self.os_window_id, should_be_shown) w = self.active_window or self.any_window if w is not None: data = {'tab_manager': self} @@ -1193,8 +1205,7 @@ class TabManager: # {{{ def resize(self, only_tabs: bool = False) -> None: if not only_tabs: if not self.tab_bar_hidden: - self.tab_bar.layout() - self.mark_tab_bar_dirty() + self.layout_tab_bar() for tab in self.tabs: tab.relayout() @@ -1217,15 +1228,12 @@ 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)) + m = set(get_boss().match_tabs(f, all_tabs=self)) return (t for t in self if t is at or t in m) return self.tabs @@ -1561,6 +1569,5 @@ class TabManager: # {{{ self.tab_bar_hidden = get_options().tab_bar_style == 'hidden' self.tab_bar.apply_options() self.update_tab_bar_data() - self.mark_tab_bar_dirty() - self.tab_bar.layout() + self.layout_tab_bar() # }}}