From 868626c79c08939c8ab3b3c5be78d08987a32ff8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 18 Aug 2021 07:15:03 +0530 Subject: [PATCH] Allow the user to supply a custom Python function to draw tab bar --- docs/changelog.rst | 3 +++ kitty/options/definition.py | 6 ++++- kitty/options/parse.py | 2 +- kitty/options/types.py | 2 +- kitty/tab_bar.py | 47 +++++++++++++++++++++++++++++++------ 5 files changed, 50 insertions(+), 10 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index d7ab829a0..a036d5338 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,6 +7,9 @@ To update |kitty|, :doc:`follow the instructions `. 0.23.2 [future] ---------------------- +- Allow the user to supply a custom Python function to draw tab bar. See + :option:`tab_bar_style` + - Fix a regression that caused :option:`kitty --title` to not work when opening new OS windows using :option:`kitty --single-instance` (:iss:`3893`) diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 36e082c31..3cccdc4a3 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -884,7 +884,7 @@ tab. opt('tab_bar_style', 'fade', - choices=('fade', 'hidden', 'powerline', 'separator', 'slant'), ctype='!tab_bar_style', + choices=('fade', 'hidden', 'powerline', 'separator', 'slant', 'custom'), ctype='!tab_bar_style', long_text=''' The tab bar style, can be one of: @@ -896,6 +896,10 @@ The tab bar style, can be one of: Tabs are separated by a configurable separator (see :opt:`tab_separator`) :code:`powerline` Tabs are shown as a continuous line with "fancy" separators (see :opt:`tab_powerline_style`) +:code:`custom` + A user-supplied Python function called draw_tab is loaded from the file :file:`tab_bar.py` + in the kitty config directory. For examples of how to write such a function see the functions + named :code:`draw_tab_with_*` in tab_bar.py in the kitty source code. :code:`hidden` The tab bar is hidden. If you use this, you might want to create a mapping for the :ref:`action-select_tab` action which presents you with a list diff --git a/kitty/options/parse.py b/kitty/options/parse.py index 581a25d25..43e6ab594 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -1173,7 +1173,7 @@ class Parser: raise ValueError(f"The value {val} is not a valid choice for tab_bar_style") ans["tab_bar_style"] = val - choices_for_tab_bar_style = frozenset(('fade', 'hidden', 'powerline', 'separator', 'slant')) + choices_for_tab_bar_style = frozenset(('fade', 'hidden', 'powerline', 'separator', 'slant', 'custom')) def tab_fade(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['tab_fade'] = tab_fade(val) diff --git a/kitty/options/types.py b/kitty/options/types.py index b3fb63e9d..8a66992b3 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -23,7 +23,7 @@ if typing.TYPE_CHECKING: choices_for_pointer_shape_when_dragging = typing.Literal['arrow', 'beam', 'hand'] choices_for_pointer_shape_when_grabbed = typing.Literal['arrow', 'beam', 'hand'] choices_for_strip_trailing_spaces = typing.Literal['always', 'never', 'smart'] - choices_for_tab_bar_style = typing.Literal['fade', 'hidden', 'powerline', 'separator', 'slant'] + choices_for_tab_bar_style = typing.Literal['fade', 'hidden', 'powerline', 'separator', 'slant', 'custom'] choices_for_tab_powerline_style = typing.Literal['angled', 'round', 'slanted'] choices_for_tab_switch_strategy = typing.Literal['last', 'left', 'previous', 'right'] else: diff --git a/kitty/tab_bar.py b/kitty/tab_bar.py index a0b9a0963..a0e9a2698 100644 --- a/kitty/tab_bar.py +++ b/kitty/tab_bar.py @@ -2,10 +2,14 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2018, Kovid Goyal -from functools import lru_cache -from typing import Any, Dict, List, NamedTuple, Optional, Sequence, Tuple +import os +from functools import lru_cache, wraps +from typing import ( + Any, Callable, Dict, List, NamedTuple, Optional, Sequence, Tuple +) from .config import build_ansi_color_table +from .constants import config_dir from .fast_data_types import ( DECAWM, Screen, cell_size_for_window, get_options, pt_to_px, set_tab_bar_render_data, viewport_for_window @@ -13,7 +17,7 @@ from .fast_data_types import ( from .layout.base import Rect from .rgb import Color, alpha_blend, color_as_sgr, color_from_int, to_color from .types import WindowGeometry, run_once -from .typing import PowerlineStyle, EdgeLiteral +from .typing import EdgeLiteral, PowerlineStyle from .utils import color_as_int, log_error from .window import calculate_gl_geometry @@ -165,6 +169,9 @@ def draw_title(draw_data: DrawData, screen: Screen, tab: TabBarData, index: int) screen.draw(title) +DrawTabFunc = Callable[[DrawData, Screen, TabBarData, int, int, int, bool], int] + + def draw_tab_with_slant(draw_data: DrawData, screen: Screen, tab: TabBarData, before: int, max_title_length: int, index: int, is_last: bool) -> int: left_sep, right_sep = ('', '') if draw_data.tab_bar_edge == 'top' else ('', '') tab_bg = as_rgb(color_as_int(draw_data.active_bg if tab.is_active else draw_data.inactive_bg)) @@ -325,6 +332,29 @@ def draw_tab_with_powerline(draw_data: DrawData, screen: Screen, tab: TabBarData return end +@run_once +def load_custom_draw_tab() -> DrawTabFunc: + import runpy + import traceback + try: + m = runpy.run_path(os.path.join(config_dir, 'tab_bar.py')) + func: DrawTabFunc = m['draw_tab'] + except Exception as e: + traceback.print_exc() + log_error(f'Failed to load custom draw_tab function with error: {e}') + return draw_tab_with_fade + + @wraps(func) + def draw_tab(draw_data: DrawData, screen: Screen, tab: TabBarData, before: int, max_title_length: int, index: int, is_last: bool) -> int: + try: + return func(draw_data, screen, tab, before, max_title_length, index, is_last) + except Exception as e: + log_error(f'Custom draw tab function failed with error: {e}') + return draw_tab_with_fade(draw_data, screen, tab, before, max_title_length, index, is_last) + + return draw_tab + + class TabBar: def __init__(self, os_window_id: int): @@ -374,12 +404,15 @@ class TabBar: opts.tab_powerline_style, 'top' if opts.tab_bar_edge == 1 else 'bottom' ) - if opts.tab_bar_style == 'separator': - self.draw_func = draw_tab_with_separator - elif opts.tab_bar_style == 'powerline': + ts = opts.tab_bar_style + if ts == 'separator': + self.draw_func: DrawTabFunc = draw_tab_with_separator + elif ts == 'powerline': self.draw_func = draw_tab_with_powerline - elif opts.tab_bar_style == 'slant': + elif ts == 'slant': self.draw_func = draw_tab_with_slant + elif ts == 'custom': + self.draw_func = load_custom_draw_tab() else: self.draw_func = draw_tab_with_fade