Allow the user to supply a custom Python function to draw tab bar

This commit is contained in:
Kovid Goyal 2021-08-18 07:15:03 +05:30
parent e4cc8bf828
commit 868626c79c
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 50 additions and 10 deletions

View File

@ -7,6 +7,9 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
0.23.2 [future] 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 - Fix a regression that caused :option:`kitty --title` to not work when
opening new OS windows using :option:`kitty --single-instance` (:iss:`3893`) opening new OS windows using :option:`kitty --single-instance` (:iss:`3893`)

View File

@ -884,7 +884,7 @@ tab.
opt('tab_bar_style', 'fade', 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=''' long_text='''
The tab bar style, can be one of: 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`) Tabs are separated by a configurable separator (see :opt:`tab_separator`)
:code:`powerline` :code:`powerline`
Tabs are shown as a continuous line with "fancy" separators (see :opt:`tab_powerline_style`) 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` :code:`hidden`
The tab bar is hidden. If you use this, you might want to create a mapping 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 for the :ref:`action-select_tab` action which presents you with a list

View File

@ -1173,7 +1173,7 @@ class Parser:
raise ValueError(f"The value {val} is not a valid choice for tab_bar_style") raise ValueError(f"The value {val} is not a valid choice for tab_bar_style")
ans["tab_bar_style"] = val 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: def tab_fade(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['tab_fade'] = tab_fade(val) ans['tab_fade'] = tab_fade(val)

View File

@ -23,7 +23,7 @@ if typing.TYPE_CHECKING:
choices_for_pointer_shape_when_dragging = typing.Literal['arrow', 'beam', 'hand'] choices_for_pointer_shape_when_dragging = typing.Literal['arrow', 'beam', 'hand']
choices_for_pointer_shape_when_grabbed = 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_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_powerline_style = typing.Literal['angled', 'round', 'slanted']
choices_for_tab_switch_strategy = typing.Literal['last', 'left', 'previous', 'right'] choices_for_tab_switch_strategy = typing.Literal['last', 'left', 'previous', 'right']
else: else:

View File

@ -2,10 +2,14 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
from functools import lru_cache import os
from typing import Any, Dict, List, NamedTuple, Optional, Sequence, Tuple 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 .config import build_ansi_color_table
from .constants import config_dir
from .fast_data_types import ( from .fast_data_types import (
DECAWM, Screen, cell_size_for_window, get_options, pt_to_px, DECAWM, Screen, cell_size_for_window, get_options, pt_to_px,
set_tab_bar_render_data, viewport_for_window set_tab_bar_render_data, viewport_for_window
@ -13,7 +17,7 @@ from .fast_data_types import (
from .layout.base import Rect from .layout.base import Rect
from .rgb import Color, alpha_blend, color_as_sgr, color_from_int, to_color from .rgb import Color, alpha_blend, color_as_sgr, color_from_int, to_color
from .types import WindowGeometry, run_once 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 .utils import color_as_int, log_error
from .window import calculate_gl_geometry 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) 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: 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 ('', '') 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)) 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 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: class TabBar:
def __init__(self, os_window_id: int): def __init__(self, os_window_id: int):
@ -374,12 +404,15 @@ class TabBar:
opts.tab_powerline_style, opts.tab_powerline_style,
'top' if opts.tab_bar_edge == 1 else 'bottom' 'top' if opts.tab_bar_edge == 1 else 'bottom'
) )
if opts.tab_bar_style == 'separator': ts = opts.tab_bar_style
self.draw_func = draw_tab_with_separator if ts == 'separator':
elif opts.tab_bar_style == 'powerline': self.draw_func: DrawTabFunc = draw_tab_with_separator
elif ts == 'powerline':
self.draw_func = draw_tab_with_powerline self.draw_func = draw_tab_with_powerline
elif opts.tab_bar_style == 'slant': elif ts == 'slant':
self.draw_func = draw_tab_with_slant self.draw_func = draw_tab_with_slant
elif ts == 'custom':
self.draw_func = load_custom_draw_tab()
else: else:
self.draw_func = draw_tab_with_fade self.draw_func = draw_tab_with_fade