diff --git a/docs/changelog.rst b/docs/changelog.rst index c5d54121b..7dd8e57de 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -79,6 +79,9 @@ Detailed list of changes - macOS: Allow opening text files, images and directories with kitty when launched using "Open with" in Finder (:iss:`4460`) +- Allow using templates with text formatting for :opt:`tab_activity_symbol` + (:pull:`4507`) + - macOS: Persist "Secure Keyboard Entry" across restarts to match the behavior of Terminal.app (:iss:`4471`) diff --git a/kitty/options/definition.py b/kitty/options/definition.py index a250f0583..1e85c2512 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -1054,7 +1054,8 @@ long_text=''' Some text or a unicode symbol to show on the tab if a window in the tab that does not have focus has some activity. If you want to use leading or trailing spaces -surround the text with quotes. +surround the text with quotes. You can also use text formatting via the same templating +system as for :opt:`tab_title_template`. ''' ) diff --git a/kitty/tab_bar.py b/kitty/tab_bar.py index 4ee05c12e..995c65108 100644 --- a/kitty/tab_bar.py +++ b/kitty/tab_bar.py @@ -4,7 +4,7 @@ import os from functools import lru_cache, partial, wraps from typing import ( - Any, Callable, Dict, List, NamedTuple, Optional, Sequence, Tuple, Union + Any, Callable, Dict, List, NamedTuple, Optional, Sequence, Tuple, Union, TYPE_CHECKING ) from .borders import Border, BorderColor @@ -20,6 +20,10 @@ from .utils import color_as_int, log_error +if TYPE_CHECKING: + import re + + class TabBarData(NamedTuple): title: str is_active: bool @@ -152,48 +156,15 @@ class ExtraData: next_tab: Optional[TabBarData] = None -def draw_title(draw_data: DrawData, screen: Screen, tab: TabBarData, index: int) -> None: - if tab.needs_attention and draw_data.bell_on_tab: - fg = screen.cursor.fg - screen.cursor.fg = draw_data.bell_fg - screen.draw('🔔 ') - screen.cursor.fg = fg - if tab.has_activity_since_last_focus and draw_data.tab_activity_symbol: - fg = screen.cursor.fg - screen.cursor.fg = draw_data.bell_fg - screen.draw(draw_data.tab_activity_symbol) - screen.cursor.fg = fg +@run_once +def attributed_string_pat() -> 're.Pattern[str]': + import re + return re.compile('(\x1b\\[[^m]*m)') - template = draw_data.title_template - if tab.is_active and draw_data.active_title_template is not None: - template = draw_data.active_title_template - try: - data = { - 'index': index, - 'layout_name': tab.layout_name, - 'num_windows': tab.num_windows, - 'num_window_groups': tab.num_window_groups, - 'title': tab.title, - } - ColorFormatter.draw_data = draw_data - ColorFormatter.tab_data = tab - eval_locals = { - 'index': index, - 'layout_name': tab.layout_name, - 'num_windows': tab.num_windows, - 'num_window_groups': tab.num_window_groups, - 'title': tab.title, - 'fmt': Formatter, - 'sup': SupSub(data), - 'sub': SupSub(data, True), - } - title = eval(compile_template(template), {'__builtins__': {}}, eval_locals) - except Exception as e: - report_template_failure(template, str(e)) - title = tab.title + +def draw_attributed_string(title: str, screen: Screen) -> None: if '\x1b' in title: - import re - for x in re.split('(\x1b\\[[^m]*m)', title): + for x in attributed_string_pat().split(title): if x.startswith('\x1b') and x.endswith('m'): screen.apply_sgr(x[2:-1]) else: @@ -202,6 +173,51 @@ def draw_title(draw_data: DrawData, screen: Screen, tab: TabBarData, index: int) screen.draw(title) +def draw_title(draw_data: DrawData, screen: Screen, tab: TabBarData, index: int) -> None: + data = { + 'index': index, + 'layout_name': tab.layout_name, + 'num_windows': tab.num_windows, + 'num_window_groups': tab.num_window_groups, + 'title': tab.title, + } + ColorFormatter.draw_data = draw_data + ColorFormatter.tab_data = tab + eval_locals = { + 'index': index, + 'layout_name': tab.layout_name, + 'num_windows': tab.num_windows, + 'num_window_groups': tab.num_window_groups, + 'title': tab.title, + 'fmt': Formatter, + 'sup': SupSub(data), + 'sub': SupSub(data, True), + } + if tab.needs_attention and draw_data.bell_on_tab: + fg = screen.cursor.fg + screen.cursor.fg = draw_data.bell_fg + screen.draw('🔔 ') + screen.cursor.fg = fg + if tab.has_activity_since_last_focus and draw_data.tab_activity_symbol: + template = draw_data.tab_activity_symbol + try: + text = eval(compile_template(template), {'__builtins__': {}}, eval_locals) + except Exception as e: + report_template_failure(template, str(e)) + else: + draw_attributed_string(text, screen) + + template = draw_data.title_template + if tab.is_active and draw_data.active_title_template is not None: + template = draw_data.active_title_template + try: + title = eval(compile_template(template), {'__builtins__': {}}, eval_locals) + except Exception as e: + report_template_failure(template, str(e)) + title = tab.title + draw_attributed_string(title, screen) + + DrawTabFunc = Callable[[DrawData, Screen, TabBarData, int, int, int, bool, ExtraData], int]