Replies: 49 comments 77 replies
-
Thanks for sharing. You can use typo: lenght -> length Previous Discussion: how to implement tab drawing in python? # 4366 |
Beta Was this translation helpful? Give feedback.
-
kitty.conf
tar_bar.pyfrom kitty.fast_data_types import Screen
from kitty.tab_bar import DrawData, ExtraData, TabBarData, draw_title
def draw_tab(
draw_data: DrawData, screen: Screen, tab: TabBarData,
before: int, max_title_length: int, index: int, is_last: bool,
extra_data: ExtraData
) -> int:
orig_fg = screen.cursor.fg
orig_bg = screen.cursor.bg
left_sep, right_sep = ('', '')
def draw_sep(which: str) -> None:
screen.cursor.bg = draw_data.default_bg
screen.cursor.fg = orig_bg
screen.draw(which)
screen.cursor.bg = orig_bg
screen.cursor.fg = orig_fg
if max_title_length <= 1:
screen.draw('…')
elif max_title_length == 2:
screen.draw('…|')
elif max_title_length < 6:
draw_sep(left_sep)
screen.draw((' ' if max_title_length == 5 else '') + '…' + (' ' if max_title_length >= 4 else ''))
draw_sep(right_sep)
else:
draw_sep(left_sep)
screen.draw(' ')
draw_title(draw_data, screen, tab, index)
extra = screen.cursor.x - before - max_title_length
print("extra:%d" %(extra))
if extra >= 0:
screen.cursor.x -= extra + 3
screen.draw('…')
elif extra == -1:
screen.cursor.x -= 2
screen.draw('…')
screen.draw(' ')
draw_sep(right_sep)
draw_sep(' ')
return screen.cursor.x Diff Relative to
|
Beta Was this translation helpful? Give feedback.
-
Hi, is there a way to update the time in the tab bar when there's no activity in the terminal ? |
Beta Was this translation helpful? Give feedback.
-
On Mon, Jan 10, 2022 at 06:40:53AM -0800, Pascal Hubrecht wrote:
Hi, is there a way to update the time in the tab bar when there's no activity in the terminal ?
The tab bar is redrawn only when a tab title is changed or the
number of tabs is changed. So in your custom python function you can use
add_timer to force a redraw of it.
the timer would need to call a function like
def redraw_tab_bar():
tm = get_boss().active_tab_manager
if tm is not None:
tm.mark_tab_bar_dirty()
|
Beta Was this translation helpful? Give feedback.
-
I'm having a play with extending the tab bar to include some small status notifications on the right of the tab bar, and I'm trying to reuse the [Edit: Sorry, I just used you as a rubber duck! Now I have the screenshots side by side, I can clearly see that they are different fonts! The new Eg, the original powerline: When rendered by my custom tab function:
See post below for my finished tab config. |
Beta Was this translation helpful? Give feedback.
-
My tabs, based on powerline rounded: The code: import datetime
import json
import subprocess
from collections import defaultdict
from kitty.boss import get_boss
from kitty.fast_data_types import Screen, add_timer
from kitty.tab_bar import (
DrawData,
ExtraData,
Formatter,
TabBarData,
as_rgb,
draw_attributed_string,
draw_tab_with_powerline,
)
timer_id = None
def draw_tab(
draw_data: DrawData,
screen: Screen,
tab: TabBarData,
before: int,
max_title_length: int,
index: int,
is_last: bool,
extra_data: ExtraData,
) -> int:
global timer_id
# if timer_id is None:
# timer_id = add_timer(_redraw_tab_bar, 2.0, True)
draw_tab_with_powerline(
draw_data, screen, tab, before, max_title_length, index, is_last, extra_data
)
if is_last:
draw_right_status(draw_data, screen)
return screen.cursor.x
def draw_right_status(draw_data: DrawData, screen: Screen) -> None:
# The tabs may have left some formats enabled. Disable them now.
draw_attributed_string(Formatter.reset, screen)
cells = create_cells()
# Drop cells that wont fit
while True:
if not cells:
return
padding = screen.columns - screen.cursor.x - sum(len(c) + 3 for c in cells)
if padding >= 0:
break
cells = cells[1:]
if padding:
screen.draw(" " * padding)
tab_bg = as_rgb(int(draw_data.inactive_bg))
tab_fg = as_rgb(int(draw_data.inactive_fg))
default_bg = as_rgb(int(draw_data.default_bg))
for cell in cells:
# Draw the separator
if cell == cells[0]:
screen.cursor.fg = tab_bg
screen.draw("")
else:
screen.cursor.fg = default_bg
screen.cursor.bg = tab_bg
screen.draw("")
screen.cursor.fg = tab_fg
screen.cursor.bg = tab_bg
screen.draw(f" {cell} ")
def create_cells() -> list[str]:
now = datetime.datetime.now()
return [
currently_playing(),
get_headphone_battery_status(),
now.strftime("%d %b"),
now.strftime("%H:%M"),
]
def get_headphone_battery_status():
try:
battery_pct = int(subprocess.getoutput("headsetcontrol -b -c"))
except Exception:
status = ""
else:
if battery_pct < 0:
status = ""
else:
status = f"{battery_pct}% {''[battery_pct // 10]}"
return f" {status}"
STATE = defaultdict(lambda: "", {"Paused": "", "Playing": ""})
def currently_playing():
# TODO: Work out how to add python libraries so that I can query dbus directly
# For now, implemented in a separate python project: dbus-player-status
status = " "
data = {}
try:
data = json.loads(subprocess.getoutput("dbus-player-status"))
except ValueError:
pass
if data:
if "state" in data:
status = f"{status} {STATE[data['state']]}"
if "title" in data:
status = f"{status} {data['title']}"
if "artist" in data:
status = f"{status} - {data['artist']}"
else:
status = ""
return status
def _redraw_tab_bar(timer_id):
for tm in get_boss().all_tab_managers:
tm.mark_tab_bar_dirty()
## Tab bar
tab_bar_edge bottom
tab_bar_margin_height 5.0 0.0
tab_bar_style custom
tab_powerline_style round
tab_bar_background #003747
tab_title_template "{fmt.fg.default}{index}"
|
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Taking some of the solid work by others, I'm replicating my tmux statusline configs and migrating to kitty (hoping to move to kitty and ditch tmux at some point).
Here's my full kitty config: I'm definitely still working through a ton of stuff; really hoping to build out "sessions" of some sort to flip between (really they correspond to different projects that I'm working on for work or personal). In addition, I'm adding support for kitty overlays to a lot of my tmux-popup based fzf scripts (bin/ftm and bin/slack are the two main ones -- also for weechat, updating weechat-fzf to support kitty as well). |
Beta Was this translation helpful? Give feedback.
-
Is it possible to get the current directory of a given tab in the tab_bar.py? The following doesn't work for a number of reasons. The title changes, based on what is open in the tab, though the cwd does show up in the title occasionally so it must be available def get_active_branch_name(tab: TabBarData):
...
return tab.title.split(...)[1].split(...) # This doesn't work obviously Is there something like Thanks for the work on Kitty and the previous posts examples of their tab bars! |
Beta Was this translation helpful? Give feedback.
-
I've only done some basics tab bar rendering similar to those shared in this thread (adding a clock, etc). Is there any way to hook clicking in the tab bar? I'd like to create some nerd-font/emoji "widgets" or per-tab close/other action buttons. I seem to recall kitty being pretty hard wired for that, but this thread seems a good place to ask before diving in my own implementation or filing an enhancement Issue. |
Beta Was this translation helpful? Give feedback.
-
Replace |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
What I changedI began my tab_bar.py by copying @megalithic , but didn't like the fact that color values were hardcoded into the script. Instead, I now read colors directly from the kitty.conf file. How to configure this thingThe default configuration assumes that your terminal is configured to use Nerd Font patched fonts. The codeThis code assumes you have Kitty configured to use Nerd Fonts and have color16 set in your kitty.conf # pyright: reportMissingImports=false
from datetime import datetime
from kitty.boss import get_boss
from kitty.fast_data_types import Screen, add_timer, get_options
from kitty.utils import color_as_int
from kitty.tab_bar import (
DrawData,
ExtraData,
Formatter,
TabBarData,
as_rgb,
draw_attributed_string,
draw_title,
)
opts = get_options()
icon_fg = as_rgb(color_as_int(opts.color16))
icon_bg = as_rgb(color_as_int(opts.color8))
bat_text_color = as_rgb(color_as_int(opts.color15))
clock_color = as_rgb(color_as_int(opts.color15))
date_color = as_rgb(color_as_int(opts.color8))
SEPARATOR_SYMBOL, SOFT_SEPARATOR_SYMBOL = ("", "")
RIGHT_MARGIN = 1
REFRESH_TIME = 1
ICON = " "
UNPLUGGED_ICONS = {
10: "",
20: "",
30: "",
40: "",
50: "",
60: "",
70: "",
80: "",
90: "",
100: "",
}
PLUGGED_ICONS = {
1: "",
}
UNPLUGGED_COLORS = {
15: as_rgb(color_as_int(opts.color1)),
16: as_rgb(color_as_int(opts.color15)),
}
PLUGGED_COLORS = {
15: as_rgb(color_as_int(opts.color1)),
16: as_rgb(color_as_int(opts.color6)),
99: as_rgb(color_as_int(opts.color6)),
100: as_rgb(color_as_int(opts.color2)),
}
def _draw_icon(screen: Screen, index: int) -> int:
if index != 1:
return 0
fg, bg = screen.cursor.fg, screen.cursor.bg
screen.cursor.fg = icon_fg
screen.cursor.bg = icon_bg
screen.draw(ICON)
screen.cursor.fg, screen.cursor.bg = fg, bg
screen.cursor.x = len(ICON)
return screen.cursor.x
def _draw_left_status(
draw_data: DrawData,
screen: Screen,
tab: TabBarData,
before: int,
max_title_length: int,
index: int,
is_last: bool,
extra_data: ExtraData,
) -> int:
if screen.cursor.x >= screen.columns - right_status_length:
return screen.cursor.x
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
if screen.cursor.x <= len(ICON):
screen.cursor.x = len(ICON)
screen.draw(" ")
screen.cursor.bg = tab_bg
draw_title(draw_data, screen, tab, index)
if not needs_soft_separator:
screen.draw(" ")
screen.cursor.fg = tab_bg
screen.cursor.bg = next_tab_bg
screen.draw(SEPARATOR_SYMBOL)
else:
prev_fg = screen.cursor.fg
if tab_bg == tab_fg:
screen.cursor.fg = default_bg
elif tab_bg != default_bg:
c1 = draw_data.inactive_bg.contrast(draw_data.default_bg)
c2 = draw_data.inactive_bg.contrast(draw_data.inactive_fg)
if c1 < c2:
screen.cursor.fg = default_bg
screen.draw(" " + SOFT_SEPARATOR_SYMBOL)
screen.cursor.fg = prev_fg
end = screen.cursor.x
return end
def _draw_right_status(screen: Screen, is_last: bool, cells: list) -> int:
if not is_last:
return 0
draw_attributed_string(Formatter.reset, screen)
screen.cursor.x = screen.columns - right_status_length
screen.cursor.fg = 0
for color, status in cells:
screen.cursor.fg = color
screen.draw(status)
screen.cursor.bg = 0
return screen.cursor.x
def _redraw_tab_bar(_):
tm = get_boss().active_tab_manager
if tm is not None:
tm.mark_tab_bar_dirty()
def get_battery_cells() -> list:
try:
with open("/sys/class/power_supply/BAT0/status", "r") as f:
status = f.read()
with open("/sys/class/power_supply/BAT0/capacity", "r") as f:
percent = int(f.read())
if status == "Discharging\n":
# TODO: declare the lambda once and don't repeat the code
icon_color = UNPLUGGED_COLORS[
min(UNPLUGGED_COLORS.keys(), key=lambda x: abs(x - percent))
]
icon = UNPLUGGED_ICONS[
min(UNPLUGGED_ICONS.keys(), key=lambda x: abs(x - percent))
]
elif status == "Not charging\n":
icon_color = UNPLUGGED_COLORS[
min(UNPLUGGED_COLORS.keys(), key=lambda x: abs(x - percent))
]
icon = PLUGGED_ICONS[
min(PLUGGED_ICONS.keys(), key=lambda x: abs(x - percent))
]
else:
icon_color = PLUGGED_COLORS[
min(PLUGGED_COLORS.keys(), key=lambda x: abs(x - percent))
]
icon = PLUGGED_ICONS[
min(PLUGGED_ICONS.keys(), key=lambda x: abs(x - percent))
]
percent_cell = (bat_text_color, str(percent) + "% ")
icon_cell = (icon_color, icon)
return [percent_cell, icon_cell]
except FileNotFoundError:
return []
timer_id = None
right_status_length = -1
def draw_tab(
draw_data: DrawData,
screen: Screen,
tab: TabBarData,
before: int,
max_title_length: int,
index: int,
is_last: bool,
extra_data: ExtraData,
) -> int:
global timer_id
global right_status_length
if timer_id is None:
timer_id = add_timer(_redraw_tab_bar, REFRESH_TIME, True)
clock = datetime.now().strftime(" %H:%M")
date = datetime.now().strftime(" %d.%m.%Y")
cells = get_battery_cells()
cells.append((clock_color, clock))
cells.append((date_color, date))
right_status_length = RIGHT_MARGIN
for cell in cells:
right_status_length += len(str(cell[1]))
_draw_icon(screen, index)
_draw_left_status(
draw_data,
screen,
tab,
before,
max_title_length,
index,
is_last,
extra_data,
)
_draw_right_status(
screen,
is_last,
cells,
)
return screen.cursor.x Relevant kitty.conf changes
|
Beta Was this translation helpful? Give feedback.
-
Wow... All of these looks so good. I am trying to implement equal width tabs on mine , but I have absolutely no idea how to even begin doing it .. anyone has any ideas on how i should go about doing it . Any help would be great... |
Beta Was this translation helpful? Give feedback.
-
I am met with the error message: Errors in kitty.conf when trying to use the tab bar of @megalithic. I have put tab_bar.py in /home/user/.config/kitty. Am I supposed to move the file somewhere else, or am I doing something else wrong? |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
import datetime
import json
import subprocess
from collections import defaultdict
from kitty.boss import get_boss
from kitty.fast_data_types import Screen, add_timer, get_options
from kitty.rgb import to_color
from kitty.tab_bar import (
DrawData,
ExtraData,
Formatter,
TabBarData,
as_rgb,
draw_attributed_string,
draw_tab_with_powerline,
)
timer_id = None
def draw_tab(
draw_data: DrawData,
screen: Screen,
tab: TabBarData,
before: int,
max_title_length: int,
index: int,
is_last: bool,
extra_data: ExtraData,
) -> int:
global timer_id
if timer_id is None:
timer_id = add_timer(_redraw_tab_bar, 2.0, True)
draw_tab_with_powerline(
draw_data, screen, tab, before, max_title_length, index, is_last, extra_data
)
if is_last: draw_right_status(draw_data, screen)
return screen.cursor.x
def draw_right_status(draw_data: DrawData, screen: Screen) -> None:
# The tabs may have left some formats enabled. Disable them now.
draw_attributed_string(Formatter.reset, screen)
tab_bg = as_rgb(int(draw_data.inactive_bg))
tab_fg = as_rgb(int(draw_data.inactive_fg))
default_bg = as_rgb(int(draw_data.default_bg))
cells = create_cells()
# Drop cells that wont fit
while True:
if not cells:
return
padding = screen.columns - screen.cursor.x - sum(len(" ".join([c.get("icon", ""), c["text"]])) + 2 for c in cells)
if padding >= 0:
break
cells = cells[1:]
if padding: screen.draw(" " * padding)
for c in cells:
screen.cursor.bg = default_bg
icon = c.get("icon")
if icon:
fg = to_color(c.get("color")) if c.get("color") else tab_fg
screen.cursor.fg = as_rgb(int(fg))
screen.draw(f" {icon}")
screen.cursor.fg = tab_fg
text = c["text"]
screen.draw(f" {text} ")
def create_cells():
cells = [
get_todo(),
get_date(),
get_time()
]
return [c for c in cells if c is not None]
def get_time():
now = datetime.datetime.now().strftime("%H:%M")
return { "icon": " ", "color": "#669bbc", "text": now }
def get_date():
today = datetime.date.today()
if today.weekday() < 5:
return { "icon": " ", "color": "#2a9d8f", "text": today.strftime("%b %e") }
else:
return { "icon": " ", "color": "#f2e8cf", "text": today.strftime("%b %e") }
def get_todo():
out = subprocess.getoutput("/opt/homebrew/bin/rg -m 1 --pcre2 -N '^(?!@done).*@today' ~/workspace/doc/main.taskpaper |sed 's:^.*- ::;s:@today::'")
if len(out) > 0:
return { "icon": " ", "color": "#e76f51", "text": out }
else:
return None
def _redraw_tab_bar(timer_id):
for tm in get_boss().all_tab_managers:
tm.mark_tab_bar_dirty() kitty.conf
|
Beta Was this translation helpful? Give feedback.
-
👀 Here is a Pac-Man styled tab bar! You guys can take it from 👻 here. |
Beta Was this translation helpful? Give feedback.
-
@kovidgoyal so look at this: λ ~/.config/kitty/ main* tree
.
├── current-theme.conf
├── kitty.conf
└── tab_bar.py
1 directory, 3 files but my custom tab bar is not activated? kitty.conf:
tab_bar.py: """draw kitty tab"""
# pyright: reportMissingImports=false
# pylint: disable=E0401,C0116,C0103,W0603,R0913
import datetime
from kitty.fast_data_types import Screen, get_options
from kitty.tab_bar import (DrawData, ExtraData, TabBarData, as_rgb,
draw_tab_with_powerline, draw_title)
from kitty.utils import color_as_int
opts = get_options()
ICON: str = " LEI "
ICON_LENGTH: int = len(ICON)
ICON_FG: int = as_rgb(color_as_int(opts.color16))
ICON_BG: int = as_rgb(color_as_int(opts.color8))
CLOCK_FG = 0
CLOCK_BG = as_rgb(color_as_int(opts.color15))
DATE_FG = 0
DATE_BG = as_rgb(color_as_int(opts.color8))
def _draw_icon(screen: Screen, index: int) -> int:
if index != 1:
return screen.cursor.x
fg, bg, bold, italic = (
screen.cursor.fg,
screen.cursor.bg,
screen.cursor.bold,
screen.cursor.italic,
)
screen.cursor.bold, screen.cursor.italic, screen.cursor.fg, screen.cursor.bg = (
True,
False,
ICON_FG,
ICON_BG,
)
screen.draw(ICON)
# set cursor position
screen.cursor.x = ICON_LENGTH
# restore color style
screen.cursor.fg, screen.cursor.bg, screen.cursor.bold, screen.cursor.italic = (
fg,
bg,
bold,
italic,
)
return screen.cursor.x
def _draw_left_status(
draw_data: DrawData,
screen: Screen,
tab: TabBarData,
before: int,
max_title_length: int,
index: int,
is_last: bool,
extra_data: ExtraData,
use_kitty_render_function: bool = False,
) -> int:
if use_kitty_render_function:
# Use `kitty` function render tab
end = draw_tab_with_powerline(
draw_data, screen, tab, before, max_title_length, index, is_last, extra_data
)
return end
if draw_data.leading_spaces:
screen.draw(" " * draw_data.leading_spaces)
# draw tab title
draw_title(draw_data, screen, tab, index)
trailing_spaces = min(max_title_length - 1, draw_data.trailing_spaces)
max_title_length -= trailing_spaces
extra = screen.cursor.x - before - max_title_length
if extra > 0:
screen.cursor.x -= extra + 1
# Don't change `ICON`
screen.cursor.x = max(screen.cursor.x, ICON_LENGTH)
screen.draw("…")
if trailing_spaces:
screen.draw(" " * trailing_spaces)
screen.cursor.bold = screen.cursor.italic = False
screen.cursor.fg = 0
if not is_last:
screen.cursor.bg = as_rgb(color_as_int(draw_data.inactive_bg))
screen.draw(draw_data.sep)
screen.cursor.bg = 0
return screen.cursor.x
def _draw_right_status(screen: Screen, is_last: bool) -> int:
if not is_last:
return screen.cursor.x
cells = [
(CLOCK_FG, CLOCK_BG, datetime.datetime.now().strftime(" %H:%M ")),
(DATE_FG, DATE_BG, datetime.datetime.now().strftime(" %Y/%m/%d ")),
]
right_status_length = 0
for _, _, cell in cells:
right_status_length += len(cell)
draw_spaces = screen.columns - screen.cursor.x - right_status_length
if draw_spaces > 0:
screen.draw(" " * draw_spaces)
for fg, bg, cell in cells:
screen.cursor.fg = fg
screen.cursor.bg = bg
screen.draw(cell)
screen.cursor.fg = 0
screen.cursor.bg = 0
screen.cursor.x = max(screen.cursor.x, screen.columns - right_status_length)
return screen.cursor.x
def draw_tab(
draw_data: DrawData,
screen: Screen,
tab: TabBarData,
before: int,
max_title_length: int,
index: int,
is_last: bool,
extra_data: ExtraData,
) -> int:
_draw_icon(screen, index)
# Set cursor to where `left_status` ends, instead `right_status`,
# to enable `open new tab` feature
end = _draw_left_status(
draw_data,
screen,
tab,
before,
max_title_length,
index,
is_last,
extra_data,
use_kitty_render_function=False,
)
_draw_right_status(
screen,
is_last,
)
return end |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Hello. |
Beta Was this translation helpful? Give feedback.
-
Make Kitty tab bar theme match my VIM's and TMUX's, it looks better 😄 kitty.conf
tab_bar.py from kitty.rgb import Color
from kitty.utils import color_as_int
from kitty.fast_data_types import Screen
from kitty.tab_bar import (
as_rgb,
draw_title,
DrawData,
ExtraData,
Formatter,
TabBarData
)
ICON = " "
ICON_FG = as_rgb(color_as_int(Color(78, 81, 82)))
ICON_BG = as_rgb(color_as_int(Color(157, 205, 105)))
LEFT_SEP = ""
RIGHT_SEP = ""
ICON_SEP_COLOR_FG = as_rgb(color_as_int(Color(157, 205, 105)))
ICON_SEP_COLOR_BG = as_rgb(color_as_int(Color(16, 16, 16)))
def __draw_icon(screen: Screen, index: int) -> int:
if index != 1:
return 0
icon_fg, icon_bg = screen.cursor.fg, screen.cursor.bg
screen.cursor.fg = ICON_FG
screen.cursor.bg = ICON_BG
screen.draw(ICON)
screen.cursor.fg = ICON_SEP_COLOR_FG
screen.cursor.bg = ICON_SEP_COLOR_BG
screen.draw(RIGHT_SEP)
screen.cursor.fg, screen.cursor.bg = icon_fg, icon_bg
end = screen.cursor.x
return end
def __draw_tab(
draw_data: DrawData,
screen: Screen,
tab: TabBarData,
max_tab_length: int,
index: int,
extra_data: ExtraData
) -> int:
tab_bg = screen.cursor.bg
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))
else:
next_tab_bg = default_bg
screen.cursor.fg = default_bg
screen.draw(RIGHT_SEP)
draw_title(draw_data, screen, tab, index, max_tab_length)
screen.cursor.fg = tab_bg
screen.cursor.bg = default_bg
screen.draw(RIGHT_SEP)
screen.cursor.fg = tab_bg
screen.cursor.bg = next_tab_bg
end = screen.cursor.x
return end
def draw_tab(
draw_data: DrawData,
screen: Screen,
tab: TabBarData,
before: int,
max_title_length: int,
index: int,
is_last: bool,
extra_data: ExtraData,
) -> int:
__draw_icon(screen, index)
end = __draw_tab(draw_data, screen, tab, max_title_length, index, extra_data)
return end |
Beta Was this translation helpful? Give feedback.
-
On Fri, Aug 23, 2024 at 02:45:34PM -0700, Alex S. wrote:
Sorry, I don't think, I fully understand you. In my experience, when I have a default tabs config in _kitty.conf_, the empty space inside the tab-bar indeed follows the theme bg-color, but, say, the color of the active tab (the button with the title of the tab) always stays the same: _rgb: 238, 238, 238_.
That color is controlled by active_tab_foreground and
active_tab_background both of which can be set by color themes.
|
Beta Was this translation helpful? Give feedback.
-
On Fri, Aug 23, 2024 at 07:41:32PM -0700, Alex S. wrote:
You mean, `active_tab_foreground` and `active_tab_background` can be set within the theme file?
yes
|
Beta Was this translation helpful? Give feedback.
-
Hello... Simple and pretty... from kitty.fast_data_types import Screen
from kitty.tab_bar import (DrawData, ExtraData, TabBarData, draw_title)
def draw_tab(
draw_data: DrawData,
screen: Screen,
tab: TabBarData,
before: int,
max_title_length: int,
index: int,
is_last: bool,
extra_data: ExtraData,
) -> int:
BG_TRANSPARENT = 0
BG_TAB = screen.cursor.bg
screen.cursor.bg = BG_TRANSPARENT
screen.cursor.fg = BG_TAB
if index == 1:
screen.draw(" ")
screen.draw("")
screen.cursor.bg = BG_TAB
draw_title(draw_data, screen, tab, index)
screen.cursor.bg = BG_TRANSPARENT
screen.cursor.fg = BG_TAB
screen.draw("")
if not is_last:
screen.draw(" ")
screen.cursor.bg = BG_TAB
return screen.cursor.x |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
I can't stand tabs that adjust to their content, and coming from Terminator where full-width tabs are a simple option I felt a bit nostalgic. So I made my own, over-engineered and configurable full-width tab bar, initially adapted from the powerline code but now it's almost entirely its own thing.
As it is configurable, I'm showing off some variations it supports with the relevant options. The base config with slanted tabs, as above:
A different predefined "angled" style, with a separator for the tab status zone:
The tab separator can be customized, e.g. if you don't like the predefined "rounded" style you can make your own:
The tab separator can even use multiple characters and wide characters (incl. emojis):
And now, the """
Provides a full-width tab bar, with evenly-sized tabs that don't vary based on
their content.
The tabs' title is split into a status zone aligned to the left, and the title
zone which gets centered if space permits.
COMPATIBILITY
This plugins requires python >= 3.12, as it relies on some improvements to
f-strings handling that were introduced with PEP 701, notably quote reuse in
nested f-strings.
CONFIGURATION
tab_bar_style custom
tab_separator simple|dashed|angled|slanted|rounded|"<1-char>"|"<2n-chars>"
tab_title_template "<d><status><d><separator><d><title>"
Where:
<1-char> is a character displayed on a single column.
<2n-char> is a string displayed on an even number of columns and that
can be split in half, with the first half being used for the
"hard" tab separator (on the active tab), and the second
half for the "soft" tab separator (between background tabs).
<d> is a delimiter that does not appear in the status, separator
or title templates.
<status> is a template used for the status zone.
<separator> is a template displayed after the status if the status zone
is not empty.
<title> is a template used for the tab's title.
"""
from functools import lru_cache
from kitty.fast_data_types import Screen, get_boss, get_options, wcswidth
from kitty.tab_bar import DrawData, ExtraData, TabBarData, as_rgb
from kitty.tab_bar import draw_title as kitty_draw_title
from kitty.tab_bar import safe_builtins
# dlen: display length
safe_builtins['dlen'] = lru_cache(wcswidth)
separator_symbols: dict[str, tuple[str, str]] = {
'simple': ('▌', '│'),
'dashed': ('▌', '┊'),
'angled': ('', ''),
'slanted': ('', '╱'),
'rounded': ('', ''),
}
def title_length(
screen: Screen,
index: int, # 1-based
sep_width: int,
) -> int:
ntabs = len(get_boss().active_tab_manager.tabs)
ncols = screen.columns - (ntabs - 1) * sep_width
width = ncols // ntabs
width += 1 if (index <= ncols % ntabs) else 0
return width
@lru_cache
def wsplit(s: str, n: int) -> tuple[str, str]:
if n < 0:
return ('', s)
for i in range(len(s) + 1):
l = wcswidth(s[:i])
if l < n:
continue
elif l > n:
c = wsplit(s[i-1:], 2)[0]
raise Exception(f"Column {n} in '{s}' splits the wide character {c}.")
for i in range(i, len(s)):
l = wcswidth(s[:i+1])
if l > n:
return (s[:i], s[i:])
break
return (s, '')
def cfg_separator(
draw_data: DrawData,
screen: Screen,
extra_data: ExtraData
) -> tuple[str, bool]:
sep_cfg = get_options().tab_separator
sep_hard, sep_soft = separator_symbols.get(sep_cfg) or (
('▌', sep_cfg) if (l := wcswidth(sep_cfg)) == 1
else wsplit(sep_cfg, l // 2) if l % 2 == 0
else separator_symbols.get('simple')
)
tab_bg = screen.cursor.bg
next_tab_bg = as_rgb(draw_data.tab_bg(extra_data.next_tab)) if extra_data.next_tab else None
sep = sep_hard if next_tab_bg and next_tab_bg != tab_bg else sep_soft
return (sep is sep_hard, sep, wcswidth(sep), next_tab_bg)
def draw_title(
draw_data: DrawData,
screen: Screen,
tab: TabBarData,
index: int,
title_length: int = 0
) -> None:
@lru_cache
def make_title(tpl: str) -> str:
if tpl is None:
return None
(_, status, sep, title, *_) = tpl.split(tpl[0], 5)
status = 'f"""' + status + '"""'
sep = 'f"""' + sep + '"""'
title = 'f"""' + title + '"""'
status = f'{{(_s := (_s := {status}) + ({sep} if _s else ""))}}'
max_length = 'max_title_length - (dlen(_t) - len(_t))'
title = f'{{(_t := {title}).center(({max_length}) - dlen(_s) * 2)}}'
nocompat = '{"" and bell_symbol and activity_symbol}'
return nocompat + status + title
draw_data = draw_data._replace(
title_template = make_title(draw_data.title_template),
active_title_template = make_title(draw_data.active_title_template),
)
before = screen.cursor.x
kitty_draw_title(draw_data, screen, tab, index, title_length)
extra = screen.cursor.x - before - title_length
if extra < 0:
screen.draw(' ' * -extra)
elif extra > 0 and extra + 1 < screen.cursor.x:
screen.cursor.x -= extra + 1
screen.draw('…')
def draw_tab(
draw_data: DrawData,
screen: Screen,
tab: TabBarData,
before: int,
max_tab_length: int,
index: int,
is_last: bool,
extra_data: ExtraData
) -> int:
(sep_is_hard, sep, sep_width, next_tab_bg) = cfg_separator(
draw_data, screen, extra_data)
max_title_length = title_length(screen, index, sep_width)
max_tab_length = max_title_length + (sep_width if not is_last else 0)
if extra_data.for_layout:
screen.cursor.x += max_tab_length
return screen.cursor.x
tab_bg = screen.cursor.bg
tab_fg = screen.cursor.fg
default_bg = as_rgb(int(draw_data.default_bg))
if max_title_length <= 3:
screen.draw(' … ')
else:
screen.draw(' ')
draw_title(draw_data, screen, tab, index, max_title_length - 2)
screen.draw(' ')
if is_last:
if (e := screen.columns - screen.cursor.x) > 0:
screen.draw(' ' * e)
elif sep_is_hard:
screen.cursor.fg = tab_bg
screen.cursor.bg = next_tab_bg
screen.draw(sep)
else:
prev_fg = screen.cursor.fg
if tab_bg == tab_fg:
screen.cursor.fg = default_bg
elif tab_bg != default_bg:
c1 = draw_data.inactive_bg.contrast(draw_data.default_bg)
c2 = draw_data.inactive_bg.contrast(draw_data.inactive_fg)
if c1 < c2:
screen.cursor.fg = default_bg
screen.draw(sep)
screen.cursor.fg = prev_fg
return screen.cursor.x You can find the commented code in my dotfiles repo. The weirdest thing this code does is monkey patching kitty's |
Beta Was this translation helpful? Give feedback.
-
It's boring but I like it.
"""draw kitty tab"""
# pyright: reportMissingImports=false
# pylint: disable=E0401,C0116,C0103,W0603,R0913
import datetime
import subprocess
from kitty.fast_data_types import Screen, get_options, Color
from kitty.tab_bar import (DrawData, ExtraData, TabBarData, as_rgb, draw_title)
from kitty.utils import color_as_int
opts = get_options()
BG = as_rgb(color_as_int(opts.color241))
CLOCK_FG = as_rgb(color_as_int(opts.color220))
DATE_FG = as_rgb(color_as_int(Color(221, 221, 221)))
HOST_FG = DATE_FG
def _draw_left_status(
draw_data: DrawData,
screen: Screen,
tab: TabBarData,
before: int,
max_title_length: int,
index: int,
is_last: bool,
extra_data: ExtraData,
) -> None:
if draw_data.leading_spaces:
screen.draw(" " * draw_data.leading_spaces)
# draw tab title
draw_title(draw_data, screen, tab, index)
trailing_spaces = min(max_title_length - 1, draw_data.trailing_spaces)
max_title_length -= trailing_spaces
extra = screen.cursor.x - before - max_title_length
if extra > 0:
screen.cursor.x -= extra + 1
screen.cursor.x = screen.cursor.x
screen.draw("…")
if trailing_spaces:
screen.cursor.bg = BG
screen.draw(" " * trailing_spaces)
screen.cursor.bold = screen.cursor.italic = False
screen.cursor.fg = 0
if not is_last:
screen.cursor.bg = as_rgb(color_as_int(draw_data.inactive_bg))
screen.draw(draw_data.sep)
screen.cursor.bg = 0
def _draw_right_status(screen: Screen, is_last: bool) -> None:
if not is_last:
return screen.cursor.x
cells = [
(
HOST_FG,
BG,
False,
subprocess.run(['hostname'], stdout=subprocess.PIPE).stdout.strip().decode() + ' '
),
(DATE_FG, BG, True, datetime.datetime.now().strftime("%m/%d")),
(CLOCK_FG, BG, True, datetime.datetime.now().strftime(" %H:%M")),
]
right_status_length = 0
for _, _, _, cell in cells:
right_status_length += len(cell)
draw_spaces = screen.columns - screen.cursor.x - right_status_length
if draw_spaces > 0:
screen.cursor.bg = BG
screen.draw(" " * draw_spaces)
for fg, bg, bold, cell in cells:
screen.cursor.bold = bold
screen.cursor.fg = fg
screen.cursor.bg = bg
screen.draw(cell)
screen.cursor.bold = False
screen.cursor.fg = 0
screen.cursor.bg = 0
screen.cursor.x = max(screen.cursor.x, screen.columns - right_status_length)
def draw_tab(
draw_data: DrawData,
screen: Screen,
tab: TabBarData,
before: int,
max_title_length: int,
index: int,
is_last: bool,
extra_data: ExtraData,
) -> None:
_draw_left_status(
draw_data,
screen,
tab,
before,
max_title_length,
index,
is_last,
extra_data,
)
_draw_right_status(
screen,
is_last,
) |
Beta Was this translation helpful? Give feedback.
-
I really like the tmux status bar with the Catppuccin theme, so I tried configuring Kitty's tab bar to match it.
from datetime import datetime
from kitty.boss import get_boss
from kitty.fast_data_types import Screen, add_timer
from kitty.rgb import to_color
from kitty.tab_bar import (
DrawData,
ExtraData,
Formatter,
TabBarData,
as_rgb,
draw_attributed_string,
draw_tab_with_powerline,
)
LEFT_HALF_CIRCLE = ""
CHARGING_ICON = " "
UNPLUGGED_ICONS = {
10: " ",
20: " ",
30: " ",
40: " ",
50: " ",
60: " ",
70: " ",
80: " ",
90: " ",
100: " ",
}
CALENDAR_CLOCK_ICON = " "
TERMINAL_ICON = " "
REFRESH_TIME = 1
def _get_active_process_name_cell() -> dict:
cell = {"icon": TERMINAL_ICON, "icon_bg_color": "#a8e4a4", "text": ""}
boss = get_boss()
# Error 1: No boss instance found.
if not boss:
cell["text"] = "Err 1"
return cell
active_window = boss.active_window
# Error 2: No active window found
if not active_window:
cell["text"] = "Err 2"
return cell
# Error 3: No process is associated with the active window.
if not active_window.child:
cell["text"] = "Err 3"
return cell
foreground_processes = active_window.child.foreground_processes
# Error 4: No foreground process found.
if not foreground_processes or not foreground_processes[0]["cmdline"]:
cell["text"] = "Err 4"
return cell
long_process_name = foreground_processes[0]["cmdline"][0]
cell["text"] = long_process_name.rsplit("/", 1)[-1]
return cell
def _get_datetime_cell() -> dict:
now = datetime.now().strftime("%d-%m-%Y %H:%M")
return {"icon": CALENDAR_CLOCK_ICON, "icon_bg_color": "#90b4fc", "text": now}
def _get_battery_cell() -> dict:
cell = {"icon": "", "icon_bg_color": "#f9e2af", "text": ""}
try:
with open("/sys/class/power_supply/BAT0/status", "r") as f:
status = f.read()
with open("/sys/class/power_supply/BAT0/capacity", "r") as f:
percent = int(f.read())
if status == "Charging\n":
cell["icon"] = CHARGING_ICON
else:
cell["icon"] = UNPLUGGED_ICONS[
min(UNPLUGGED_ICONS.keys(), key=lambda x: abs(percent - x))
]
cell["text"] = str(percent) + "%"
except FileNotFoundError:
cell["text"] = "Err"
return cell
def _create_cells() -> list[dict]:
return [_get_battery_cell(), _get_active_process_name_cell(), _get_datetime_cell()]
def _draw_right_status(screen: Screen, is_last: bool, draw_data: DrawData) -> int:
if not is_last:
return 0
draw_attributed_string(Formatter.reset, screen)
cells = _create_cells()
right_status_length = 0
for c in cells:
right_status_length += 3 + len(c["icon"]) + len(c["text"])
screen.cursor.x = screen.columns - right_status_length
default_bg = as_rgb(int(draw_data.default_bg))
tab_fg = as_rgb(int(draw_data.inactive_fg))
screen.cursor.bg = default_bg
for c in cells:
icon_bg_color = as_rgb(int(to_color(c["icon_bg_color"])))
screen.cursor.fg = icon_bg_color
screen.draw(LEFT_HALF_CIRCLE)
screen.cursor.bg = icon_bg_color
screen.cursor.fg = 1
screen.draw(c["icon"])
screen.cursor.bg = as_rgb(int(to_color("#383444")))
screen.cursor.fg = tab_fg
screen.draw(f" {c['text']} ")
return screen.cursor.x
def _redraw_tab_bar(_) -> None:
tm = get_boss().active_tab_manager
if tm is not None:
tm.mark_tab_bar_dirty()
timer_id = None
def draw_tab(
draw_data: DrawData,
screen: Screen,
tab: TabBarData,
before: int,
max_title_length: int,
index: int,
is_last: bool,
extra_data: ExtraData,
) -> int:
global timer_id
if timer_id is None:
timer_id = add_timer(_redraw_tab_bar, REFRESH_TIME, True)
draw_tab_with_powerline(
draw_data, screen, tab, before, max_title_length, index, is_last, extra_data
)
_draw_right_status(screen, is_last, draw_data)
return screen.cursor.x |
Beta Was this translation helpful? Give feedback.
-
This is my 👇
screenshot:
kitty.conf:
tar_bar.py:
Old
detail
This is my 👇screenshot:
kitty.conf:
tar_bar.py:
Beta Was this translation helpful? Give feedback.
All reactions