From 647b18d345152e8d12db91ac6f22f656fb6ca1a5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 3 Oct 2022 13:24:21 +0530 Subject: [PATCH] Tab bar: Improve empty space management when some tabs have short titles, allocate the saved space to the active tab Fixes #5548 --- docs/changelog.rst | 2 ++ kitty/tab_bar.py | 59 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 80a32b92b..5920195bc 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -44,6 +44,8 @@ Detailed list of changes - X11: Fix a regression in the previous release that caused pasting from GTK based applications to have extra newlines (:iss:`5528`) +- Tab bar: Improve empty space management when some tabs have short titles, allocate the saved space to the active tab (:iss:`5548`) + 0.26.3 [2022-09-22] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/kitty/tab_bar.py b/kitty/tab_bar.py index a09e64b93..8620a4a9d 100644 --- a/kitty/tab_bar.py +++ b/kitty/tab_bar.py @@ -5,7 +5,7 @@ import os from functools import lru_cache, partial, wraps from string import Formatter as StringFormatter from typing import ( - Any, Callable, Dict, List, NamedTuple, Optional, Sequence, Tuple, Union + Any, Callable, Dict, List, NamedTuple, Optional, Sequence, Tuple, Union, ) from .borders import Border, BorderColor @@ -13,7 +13,7 @@ from .config import build_ansi_color_table from .constants import config_dir from .fast_data_types import ( DECAWM, Color, Region, Screen, cell_size_for_window, get_boss, get_options, - pt_to_px, set_tab_bar_render_data, viewport_for_window + pt_to_px, set_tab_bar_render_data, viewport_for_window, ) from .rgb import alpha_blend, color_as_sgr, color_from_int, to_color from .types import WindowGeometry, run_once @@ -151,6 +151,7 @@ class SupSub: class ExtraData: prev_tab: Optional[TabBarData] = None next_tab: Optional[TabBarData] = None + for_layout: bool = False def draw_attributed_string(title: str, screen: Screen) -> None: @@ -435,6 +436,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.laid_out_once = False self.apply_options() @@ -571,31 +573,66 @@ class TabBar: if not self.laid_out_once: return s = self.screen - s.cursor.x = 0 - s.erase_in_line(2, False) - max_title_length = max(1, (self.screen.columns // max(1, len(data))) - 1) - cr = [] last_tab = data[-1] if data else None ed = ExtraData() - for i, t in enumerate(data): + def draw_tab(i: int, tab: TabBarData, cell_ranges: List[Tuple[int, int]], max_tab_length: int, check_overflow: bool = True) -> 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)) s.cursor.fg = as_rgb(self.draw_data.tab_fg(t)) s.cursor.bold, s.cursor.italic = self.active_font_style if t.is_active else self.inactive_font_style before = s.cursor.x - end = self.draw_func(self.draw_data, s, t, before, max_title_length, i + 1, t is last_tab, ed) + 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 - cr.append((before, end)) - if s.cursor.x > s.columns - max_title_length and t is not last_tab: + cell_ranges.append((before, end)) + if check_overflow and s.cursor.x > s.columns - max_tab_length and t is not last_tab: s.cursor.x = s.columns - 2 s.cursor.bg = as_rgb(color_as_int(self.draw_data.default_bg)) s.cursor.fg = as_rgb(0xff0000) s.draw(' …') + raise StopIteration() + + unconstrained_tab_length = max(1, s.columns - 2) + ideal_tab_lengths = [i for i in range(len(data))] + default_max_tab_length = max(1, (s.columns // max(1, len(data))) - 1) + max_tab_lengths = [default_max_tab_length for _ in range(len(data))] + active_idx = 0 + extra = 0 + ed.for_layout = True + for i, t in enumerate(data): + s.cursor.x = 0 + draw_tab(i, t, [], unconstrained_tab_length, check_overflow=False) + ideal_tab_lengths[i] = tl = max(1, s.cursor.x) + if t.is_active: + active_idx = i + if tl < default_max_tab_length: + max_tab_lengths[i] = tl + extra += default_max_tab_length - tl + if extra > 0: + if ideal_tab_lengths[active_idx] > max_tab_lengths[active_idx]: + d = min(extra, ideal_tab_lengths[active_idx] - max_tab_lengths[active_idx]) + max_tab_lengths[active_idx] += d + extra -= d + if extra > 0: + over_achievers = tuple(i for i in range(len(data)) if ideal_tab_lengths[i] > max_tab_lengths[i]) + if over_achievers: + amt_per_over_achiever = extra // len(over_achievers) + if amt_per_over_achiever > 0: + for i in over_achievers: + max_tab_lengths[i] += amt_per_over_achiever + + s.cursor.x = 0 + s.erase_in_line(2, False) + cr: List[Tuple[int, int]] = [] + ed.for_layout = False + for i, t in enumerate(data): + try: + draw_tab(i, t, cr, max_tab_lengths[i]) + except StopIteration: break - s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab self.cell_ranges = cr + s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab self.align() def align_with_factor(self, factor: int = 1) -> None: