mirror of
https://github.com/kovidgoyal/kitty.git
synced 2024-11-13 12:09:35 +03:00
parent
8187ec2cef
commit
5afb16ab8d
@ -22,6 +22,9 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
- Allow the user to supply a custom Python function to draw tab bar. See
|
||||
:opt:`tab_bar_style`
|
||||
|
||||
- A new remote control command to :program:`change the tab color <kitty @
|
||||
set-tab-color>` (:iss:`1287`)
|
||||
|
||||
- Add support for reporting mouse events with pixel co-ordinates using the
|
||||
``SGR_PIXEL_PROTOCOL`` introduced in xterm 359
|
||||
|
||||
|
82
kitty/rc/set_tab_color.py
Normal file
82
kitty/rc/set_tab_color.py
Normal file
@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING, Dict, Optional
|
||||
|
||||
from kitty.rgb import to_color
|
||||
|
||||
from .base import (
|
||||
MATCH_TAB_OPTION, ArgsType, Boss, ParsingOfArgsFailed, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import SetTabColorRCOptions as CLIOptions
|
||||
|
||||
|
||||
valid_color_names = frozenset('active_fg active_bg inactive_fg inactive_bg'.split())
|
||||
|
||||
|
||||
def parse_colors(args: ArgsType) -> Dict[str, Optional[int]]:
|
||||
ans: Dict[str, Optional[int]] = {}
|
||||
for spec in args:
|
||||
key, val = spec.split('=', 1)
|
||||
key = key.lower()
|
||||
if key.lower() not in valid_color_names:
|
||||
raise KeyError(f'{key} is not a valid color name')
|
||||
if val.lower() == 'none':
|
||||
col: Optional[int] = None
|
||||
else:
|
||||
q = to_color(val, validate=True)
|
||||
if q is not None:
|
||||
col = int(q)
|
||||
ans[key.lower()] = col
|
||||
return ans
|
||||
|
||||
|
||||
class SetTabColor(RemoteCommand):
|
||||
|
||||
'''
|
||||
colors+: An object mapping names to colors as 24-bit RGB integers. A color value of null indicates it should be unset.
|
||||
match: Which tab to change the color of
|
||||
self: Boolean indicating whether to use the tab of the window the command is run in
|
||||
'''
|
||||
|
||||
short_desc = 'Change the color of the specified tab(s) in the tab bar'
|
||||
desc = f'''
|
||||
{short_desc}
|
||||
|
||||
The foreground and background colors when active and inactive can be overridden using this command. \
|
||||
The syntax for specifying colors is: active_fg=color active_bg=color inactive_fg=color \
|
||||
inactive_bg=color. Where color can be either a color name or a value of the form #rrggbb or \
|
||||
the keyword NONE to revert to using the default colors.
|
||||
'''
|
||||
options_spec = MATCH_TAB_OPTION + '''\n
|
||||
--self
|
||||
type=bool-set
|
||||
If specified close the tab this command is run in, rather than the active tab.
|
||||
'''
|
||||
argspec = 'COLORS'
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
try:
|
||||
colors = parse_colors(args)
|
||||
except Exception as err:
|
||||
raise ParsingOfArgsFailed(str(err)) from err
|
||||
if not colors:
|
||||
raise ParsingOfArgsFailed('No colors specified')
|
||||
return {'match': opts.match, 'self': opts.self, 'colors': colors}
|
||||
|
||||
def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType:
|
||||
colors = payload_get('colors')
|
||||
s = {k: None if colors[k] is None else int(colors[k]) for k in valid_color_names if k in colors}
|
||||
for tab in self.tabs_for_match_payload(boss, window, payload_get):
|
||||
if tab:
|
||||
for k, v in s.items():
|
||||
setattr(tab, k, v)
|
||||
tab.mark_tab_bar_dirty()
|
||||
|
||||
|
||||
set_tab_color = SetTabColor()
|
@ -30,6 +30,10 @@ class TabBarData(NamedTuple):
|
||||
num_window_groups: int
|
||||
layout_name: str
|
||||
has_activity_since_last_focus: bool
|
||||
active_fg: Optional[int]
|
||||
active_bg: Optional[int]
|
||||
inactive_fg: Optional[int]
|
||||
inactive_bg: Optional[int]
|
||||
|
||||
|
||||
class DrawData(NamedTuple):
|
||||
@ -50,6 +54,24 @@ class DrawData(NamedTuple):
|
||||
powerline_style: PowerlineStyle
|
||||
tab_bar_edge: EdgeLiteral
|
||||
|
||||
def tab_fg(self, tab: TabBarData) -> int:
|
||||
if tab.is_active:
|
||||
if tab.active_fg is not None:
|
||||
return tab.active_fg
|
||||
return int(self.active_fg)
|
||||
if tab.inactive_fg is not None:
|
||||
return tab.inactive_fg
|
||||
return int(self.inactive_fg)
|
||||
|
||||
def tab_bg(self, tab: TabBarData) -> int:
|
||||
if tab.is_active:
|
||||
if tab.active_bg is not None:
|
||||
return tab.active_bg
|
||||
return int(self.active_bg)
|
||||
if tab.inactive_bg is not None:
|
||||
return tab.inactive_bg
|
||||
return int(self.inactive_bg)
|
||||
|
||||
|
||||
def as_rgb(x: int) -> int:
|
||||
return (x << 8) | 2
|
||||
@ -122,7 +144,8 @@ class SupSub:
|
||||
|
||||
|
||||
class ExtraData:
|
||||
pass
|
||||
prev_tab: Optional[TabBarData] = None
|
||||
next_tab: Optional[TabBarData] = None
|
||||
|
||||
|
||||
def draw_title(draw_data: DrawData, screen: Screen, tab: TabBarData, index: int) -> None:
|
||||
@ -183,7 +206,7 @@ def draw_tab_with_slant(
|
||||
) -> int:
|
||||
orig_fg = screen.cursor.fg
|
||||
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 = screen.cursor.bg
|
||||
slant_fg = as_rgb(color_as_int(draw_data.default_bg))
|
||||
|
||||
def draw_sep(which: str) -> None:
|
||||
@ -224,8 +247,6 @@ def draw_tab_with_separator(
|
||||
before: int, max_title_length: int, index: int, is_last: bool,
|
||||
extra_data: ExtraData
|
||||
) -> int:
|
||||
tab_bg = draw_data.active_bg if tab.is_active else draw_data.inactive_bg
|
||||
screen.cursor.bg = as_rgb(color_as_int(tab_bg))
|
||||
if draw_data.leading_spaces:
|
||||
screen.draw(' ' * draw_data.leading_spaces)
|
||||
draw_title(draw_data, screen, tab, index)
|
||||
@ -252,12 +273,13 @@ def draw_tab_with_fade(
|
||||
before: int, max_title_length: int, index: int, is_last: bool,
|
||||
extra_data: ExtraData
|
||||
) -> int:
|
||||
tab_bg = draw_data.active_bg if tab.is_active else draw_data.inactive_bg
|
||||
orig_bg = screen.cursor.bg
|
||||
tab_bg = color_from_int(orig_bg >> 8)
|
||||
fade_colors = [as_rgb(color_as_int(alpha_blend(tab_bg, draw_data.default_bg, alpha))) for alpha in draw_data.alpha]
|
||||
for bg in fade_colors:
|
||||
screen.cursor.bg = bg
|
||||
screen.draw(' ')
|
||||
screen.cursor.bg = as_rgb(color_as_int(tab_bg))
|
||||
screen.cursor.bg = orig_bg
|
||||
draw_title(draw_data, screen, tab, index)
|
||||
extra = screen.cursor.x - before - max_title_length
|
||||
if extra > 0:
|
||||
@ -290,29 +312,21 @@ def draw_tab_with_powerline(
|
||||
before: int, max_title_length: int, index: int, is_last: bool,
|
||||
extra_data: ExtraData
|
||||
) -> int:
|
||||
tab_bg = as_rgb(color_as_int(draw_data.active_bg if tab.is_active else draw_data.inactive_bg))
|
||||
tab_fg = as_rgb(color_as_int(draw_data.active_fg if tab.is_active else draw_data.inactive_fg))
|
||||
inactive_bg = as_rgb(color_as_int(draw_data.inactive_bg))
|
||||
default_bg = as_rgb(color_as_int(draw_data.default_bg))
|
||||
tab_bg = screen.cursor.bg
|
||||
tab_fg = screen.cursor.fg
|
||||
default_bg = as_rgb(int(draw_data.default_bg))
|
||||
if extra_data.next_tab:
|
||||
next_tab_bg = as_rgb(draw_data.tab_bg(extra_data.next_tab))
|
||||
needs_soft_separator = next_tab_bg == tab_bg
|
||||
else:
|
||||
next_tab_bg = default_bg
|
||||
needs_soft_separator = False
|
||||
|
||||
separator_symbol, separator_alt_symbol = powerline_symbols.get(draw_data.powerline_style, ('', ''))
|
||||
separator_symbol, soft_separator_symbol = powerline_symbols.get(draw_data.powerline_style, ('', ''))
|
||||
min_title_length = 1 + 2
|
||||
|
||||
if screen.cursor.x + min_title_length >= screen.columns:
|
||||
screen.cursor.x -= 2
|
||||
screen.cursor.bg = default_bg
|
||||
screen.cursor.fg = inactive_bg
|
||||
screen.draw(f'{separator_symbol} ')
|
||||
return screen.cursor.x
|
||||
|
||||
start_draw = 2
|
||||
if tab.is_active and screen.cursor.x >= 2:
|
||||
screen.cursor.x -= 2
|
||||
screen.cursor.fg = inactive_bg
|
||||
screen.cursor.bg = tab_bg
|
||||
screen.draw(f'{separator_symbol} ')
|
||||
screen.cursor.fg = tab_fg
|
||||
elif screen.cursor.x == 0:
|
||||
|
||||
if screen.cursor.x == 0:
|
||||
screen.cursor.bg = tab_bg
|
||||
screen.draw(' ')
|
||||
start_draw = 1
|
||||
@ -327,13 +341,10 @@ def draw_tab_with_powerline(
|
||||
screen.cursor.x -= extra + 1
|
||||
screen.draw('…')
|
||||
|
||||
if tab.is_active or is_last:
|
||||
if not needs_soft_separator:
|
||||
screen.draw(' ')
|
||||
screen.cursor.fg = tab_bg
|
||||
if is_last:
|
||||
screen.cursor.bg = default_bg
|
||||
else:
|
||||
screen.cursor.bg = inactive_bg
|
||||
screen.cursor.bg = next_tab_bg
|
||||
screen.draw(separator_symbol)
|
||||
else:
|
||||
prev_fg = screen.cursor.fg
|
||||
@ -344,7 +355,7 @@ def draw_tab_with_powerline(
|
||||
c2 = draw_data.inactive_bg.contrast(draw_data.inactive_fg)
|
||||
if c1 < c2:
|
||||
screen.cursor.fg = default_bg
|
||||
screen.draw(f' {separator_alt_symbol}')
|
||||
screen.draw(f' {soft_separator_symbol}')
|
||||
screen.cursor.fg = prev_fg
|
||||
|
||||
end = screen.cursor.x
|
||||
@ -516,8 +527,10 @@ class TabBar:
|
||||
ed = ExtraData()
|
||||
|
||||
for i, t in enumerate(data):
|
||||
s.cursor.bg = self.active_bg if t.is_active else 0
|
||||
s.cursor.fg = self.active_fg if t.is_active else 0
|
||||
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)
|
||||
|
@ -77,6 +77,11 @@ def add_active_id_to_history(items: Deque[int], item_id: int, maxlen: int = 64)
|
||||
|
||||
class Tab: # {{{
|
||||
|
||||
active_fg: Optional[int] = None
|
||||
active_bg: Optional[int] = None
|
||||
inactive_fg: Optional[int] = None
|
||||
inactive_bg: Optional[int] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tab_manager: 'TabManager',
|
||||
@ -922,7 +927,8 @@ class TabManager: # {{{
|
||||
ans.append(TabBarData(
|
||||
title, t is at, needs_attention,
|
||||
len(t), t.num_window_groups, t.current_layout.name or '',
|
||||
has_activity_since_last_focus
|
||||
has_activity_since_last_focus, t.active_fg, t.active_bg,
|
||||
t.inactive_fg, t.inactive_bg
|
||||
))
|
||||
return ans
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user