From ec22e9b474f595aa4ecbe0157522416265d7c09f Mon Sep 17 00:00:00 2001 From: Vincent Manapat Date: Thu, 24 Sep 2026 16:01:07 +0200 Subject: [PATCH] Vertical tabs: Fix rendering of the powerline tab bar style draw_tab_with_powerline draws the separator right after the title and blends it into the background of the next tab, which is correct for a horizontal tab bar. In a vertical tab bar each tab has its own row, pre-filled with the tab background, so the separator ended up in the middle of the row, coloured as the tab on the row below, and tabs of the same colour got a stray soft separator. For vertical tab bars, pad the title with the tab background up to the right edge and draw the separator there, blending into the tab bar background, as is already done for the fade style. --- docs/changelog.rst | 2 ++ kitty/tab_bar.py | 9 +++++++++ kitty_tests/tab_bar.py | 38 +++++++++++++++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index baaff1006..51ab660d1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -208,6 +208,8 @@ Detailed list of changes - Fix a memory leak when loading a corrupt PNG image that has an embedded ICC color profile +- Vertical tabs: Improve rendering of tabs when using the ``powerline`` :opt:`tab_bar_style` + 0.49.1 [2026-09-24] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/kitty/tab_bar.py b/kitty/tab_bar.py index 7de0b6e96..dc0d915d0 100644 --- a/kitty/tab_bar.py +++ b/kitty/tab_bar.py @@ -624,6 +624,15 @@ def draw_tab_with_powerline( screen.cursor.x -= extra + 1 screen.draw('…') + if draw_data.tab_bar_edge in ('left', 'right'): + # Clear anything left after a truncation ellipsis. + screen.cursor.bg = tab_bg + screen.draw(' ' * (screen.columns - 1 - screen.cursor.x)) + screen.cursor.fg = tab_bg + screen.cursor.bg = default_bg + screen.draw(separator_symbol) + return screen.cursor.x + if not needs_soft_separator: screen.draw(' ') screen.cursor.fg = tab_bg diff --git a/kitty_tests/tab_bar.py b/kitty_tests/tab_bar.py index 30da8812d..ce79fd73d 100644 --- a/kitty_tests/tab_bar.py +++ b/kitty_tests/tab_bar.py @@ -5,7 +5,8 @@ from unittest.mock import patch from kitty.fast_data_types import LEFT_EDGE, Color, Region from kitty.options.utils import tab_title_wrap -from kitty.tab_bar import CellRange, TabBar, TabBarData, truncate_line, wrap_title +from kitty.tab_bar import CellRange, TabBar, TabBarData, as_rgb, powerline_symbols, truncate_line, wrap_title +from kitty.utils import color_as_int from .base import BaseTest @@ -413,3 +414,38 @@ class TestTabBar(BaseTest): # Issue 2: no background-coloured separator after the trailing fades. self.ae(bg(11), fade_bg) # col 11, last trailing fade, must not be default_bg + + def test_vertical_tab_bar_powerline_style(self) -> None: + tb = self.vertical_tab_bar( + num_tabs=3, + tab_bar_style='powerline', + tab_powerline_style='slanted', + active_tab_background=Color(200, 0, 0), + inactive_tab_background=Color(0, 200, 0), + tab_bar_background=Color(0, 0, 100), + ) + s = tb.screen + separator, soft_separator = powerline_symbols['slanted'] + active_bg = as_rgb(color_as_int(Color(200, 0, 0))) + inactive_bg = as_rgb(color_as_int(Color(0, 200, 0))) + tab_bar_bg = as_rgb(color_as_int(Color(0, 0, 100))) + for row, tab_bg in ((0, active_bg), (2, inactive_bg), (4, inactive_bg)): + line = s.line(row) + text = str(line) + self.assertNotIn(soft_separator, text) + self.ae(text.index(separator), s.columns - 1) + self.ae(int(line.cursor_from(3).bg), tab_bg) + sep = line.cursor_from(s.columns - 1) + self.ae((int(sep.fg), int(sep.bg)), (tab_bg, tab_bar_bg)) + + tb = self.vertical_tab_bar(num_tabs=1, tab_bar_style='powerline', tab_powerline_style='slanted', tab_title_template='{title}-a-long-title') + self.ae(str(tb.screen.line(0)), f' t0-a-lon… {separator}') + + tb = self.vertical_tab_bar(num_tabs=1, tab_bar_style='powerline', tab_powerline_style='slanted', tab_title_template='abcdef', tab_title_max_length=3) + self.ae(str(tb.screen.line(0)), f' ab… {separator}') + + tb = self.vertical_tab_bar( + num_tabs=1, tab_bar_style='powerline', tab_title_template='{fmt.fg.red}{title}{fmt.reset}', active_tab_background=Color(200, 0, 0) + ) + line = tb.screen.line(0) + self.ae({int(line.cursor_from(x).bg) for x in range(tb.screen.columns - 1)}, {active_bg})