Replies: 37 comments 68 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.
-
Very similar to @ssnailed & @megalithic (thanks!). I modified those scripts to get this: If anyone's wondering, the full code for the config, tab_bar, and gruvbox themes is here https://codeberg.org/0rphee/dotfiles/src/branch/main/.config/kitty The tab_bar.py looks like this
from datetime import datetime
from kitty.boss import get_boss
from kitty.fast_data_types import Screen, add_timer, get_options
from kitty.rgb import Color
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.background))
icon_bg = as_rgb(color_as_int(opts.color6))
date_fgcolor = as_rgb(color_as_int(opts.tab_bar_background))
# date_bgcolor = as_rgb(color_as_int(opts.color9))
date_bgcolor = as_rgb(color_as_int(Color(251, 74, 52)))
separator_fg = as_rgb(color_as_int(opts.color9))
bat_text_color = as_rgb(color_as_int(opts.color15))
SEPARATOR_SYMBOL, SOFT_SEPARATOR_SYMBOL = ("", "")
RIGHT_MARGIN = 0
ICON = " "
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.cursor.fg = prev_fg # separator_fg
screen.draw(" " + SOFT_SEPARATOR_SYMBOL)
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 bgColor, fgColor, status in cells:
screen.cursor.fg = fgColor
screen.cursor.bg = bgColor
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()
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 right_status_length
date = datetime.now().strftime(" %d.%m.%Y")
cells = [(date_bgcolor, date_fgcolor, date)]
right_status_length = RIGHT_MARGIN
for cell in cells:
right_status_length += len(str(cell[2]))
_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
|
Beta Was this translation helpful? Give feedback.
-
Very nice guys! @ssnailed would be possible to share your kitty config? |
Beta Was this translation helpful? Give feedback.
-
Is there any way to capture the names of windows within a tab and how to put them in the bar? |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
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)
from kitty.utils import color_as_int
opts = get_options()
CLOCK_FG = as_rgb(int("ffffff", 16))
CLOCK_BG = as_rgb(color_as_int(opts.color4))
DATE_FG = as_rgb(int("ffffff", 16))
DATE_BG = as_rgb(color_as_int(opts.color4))
def _draw_right_status(screen: Screen, is_last: bool) -> int:
if not is_last:
return screen.cursor.x
cells = [
(CLOCK_BG, screen.cursor.bg, ""),
(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:
end = draw_tab_with_powerline(
draw_data, screen, tab, before, max_title_length, index, is_last, extra_data
)
_draw_right_status(
screen,
is_last,
)
return end and with config
|
Beta Was this translation helpful? Give feedback.
-
anyone know how to how to keep only the first letter of a folder in the middle of the path? like like |
Beta Was this translation helpful? Give feedback.
-
I also want to share mine here: My goals were:
This is the result (sorry, screenshot wouldn't work with tpipeline) tabbar.mp4tab_bar.py import os
import os.path
from dataclasses import dataclass
from typing import List, Optional
from kitty.fast_data_types import Screen, get_boss, get_options
from kitty.tab_bar import DrawData, ExtraData, TabBarData, as_rgb
from kitty.tabs import Tab
from kitty.utils import color_as_int
# TODOS:
# - With 2 kitty windows, the left one has vim open, the right one doesn't, Selecting the right one still shows the statusbar
# --> We could get the current kitty os id of the one hat has neovim running and set that id as the title name. So we could figure out if we want to draw the Vim statusbar
# - When multiple tabs are open, all except for the last one have 3 red dots in the very right of the tabbar
##### Kitty settings #####
options = get_options()
# colors
DEFAULT_FG = as_rgb(color_as_int(options.foreground))
DEFAULT_BG = as_rgb(color_as_int(options.background))
ACTIVE_TAB_FG = as_rgb(color_as_int(options.active_tab_foreground))
ACTIVE_TAB_BG = as_rgb(color_as_int(options.active_tab_background))
INACTIVE_TAB_FG = as_rgb(color_as_int(options.inactive_tab_foreground))
INACTIVE_TAB_BG = as_rgb(color_as_int(options.inactive_tab_background))
##### Components ######
@dataclass
class ComponentOptions:
fg: int
bg: int
bold: bool = False
blink: bool = False
italic: bool = False
@dataclass
class Component:
text: str
options: ComponentOptions
def __len__(self):
return len(self.text)
def space_component(size: int) -> Component:
return Component(" " * size, ComponentOptions(DEFAULT_FG, DEFAULT_BG))
def tab_left_components(is_active: bool) -> List[Component]:
fg = ACTIVE_TAB_BG if is_active else INACTIVE_TAB_BG
bg = ACTIVE_TAB_FG if is_active else INACTIVE_TAB_FG
return [
Component("", ComponentOptions(bg, fg)),
Component("", ComponentOptions(fg, bg)),
Component(" ", ComponentOptions(bg, fg)),
]
def tab_right_components(is_active: bool) -> List[Component]:
fg = ACTIVE_TAB_BG if is_active else INACTIVE_TAB_BG
bg = ACTIVE_TAB_FG if is_active else INACTIVE_TAB_FG
return [
Component(" ", ComponentOptions(bg, fg)),
Component("", ComponentOptions(fg, bg)),
Component("", ComponentOptions(bg, fg)),
]
def single_tab_components(tab: Tab, is_active: bool) -> List[Component]:
fg = ACTIVE_TAB_FG if is_active else INACTIVE_TAB_FG
bg = ACTIVE_TAB_BG if is_active else INACTIVE_TAB_BG
return (
tab_left_components(is_active)
+ [Component(tab.title.split().pop(), ComponentOptions(fg, bg))]
+ tab_right_components(is_active)
)
def all_tab_components(
screen: Screen,
text_size_before: int,
text_size_after: int,
active_tab_id: int,
) -> List[Component]:
max_text_size = max(text_size_before, text_size_after)
available_space_in_center = screen.columns - (2 * max_text_size)
# inserting as much tabs as possible fit without overlapping
tab_components = [space_component(1)]
tabs_length = simple_components_len(tab_components)
for tab in get_boss().all_tabs:
next_tab_components = single_tab_components(tab, tab.id == active_tab_id)
next_tab_components.append(space_component(1))
next_tab_length = simple_components_len(next_tab_components)
if tabs_length + next_tab_length >= available_space_in_center:
break
tab_components += next_tab_components
tabs_length += next_tab_length
total_padding = available_space_in_center - tabs_length
padding_left = total_padding // 2
padding_right = total_padding - padding_left
tab_components.insert(
0, space_component(padding_left + max_text_size - text_size_before)
)
tab_components.append(
space_component(padding_right + max_text_size - text_size_after)
)
# debugging
# print(
# {
# "total": screen.columns,
# "before": text_size_before,
# "after": text_size_after,
# "total_padding": total_padding,
# "padleft": padding_left,
# "padright": padding_right,
# "tabs_length": tabs_length,
# "simple_length": simple_components_len(tab_components),
# "draw_lenth": drawing_components_len(screen, tab_components),
# }
# )
return tab_components
# this is not always correct, since some symbols take multiple columns but their `len` is only 1
def simple_components_len(components: List[Component]):
length = 0
for component in components:
length += len(component)
return length
# this is always correct but may require more resources and might produce bugs
def drawing_components_len(screen: Screen, components: List[Component]):
old_cursor_x = screen.cursor.x
draw_components(screen, components)
new_cursor_x = screen.cursor.x
screen.cursor.x = old_cursor_x
return new_cursor_x - old_cursor_x
def draw_components(screen: Screen, components: List[Component]) -> None:
for component in components:
screen.cursor.fg = component.options.fg
screen.cursor.bg = component.options.bg
screen.cursor.bold = component.options.bold
screen.cursor.blink = component.options.blink
screen.cursor.italic = component.options.italic
screen.draw(component.text)
##### TPIPELINE #####
def get_tpipeline_str(path: str) -> str:
if os.path.exists(path):
return open(path).readline()
else:
return ""
def parse_statusline_str(statusline_str) -> List[Component]:
stl_items = statusline_str.split("#[")
stl_items.pop(0)
parsed_statusline = []
for item in stl_items:
[format, text] = item.split("]", 1)
fg = DEFAULT_FG
bg = DEFAULT_BG
bold = False
blink = False
italic = False
for item_format in format.split(","):
if item_format.startswith("fg="):
if item_format != "fg=default":
fg = as_rgb(int(item_format[4:10], 16))
elif item_format.startswith("bg="):
if item_format != "bg=default":
bg = as_rgb(int(item_format[4:10], 16))
elif item_format == "bold":
bold = True
elif item_format == "blink":
blink = True
elif item_format == "italics":
italic = True
parsed_statusline.append(
Component(text, ComponentOptions(fg, bg, bold, blink, italic))
)
return parsed_statusline
def left_statusline_components() -> List[Component]:
return parse_statusline_str(
get_tpipeline_str("/tmp/tmux-" + str(os.getuid()) + "/default-$0-vimbridge")
)
def right_statusline_components() -> List[Component]:
return parse_statusline_str(
get_tpipeline_str("/tmp/tmux-" + str(os.getuid()) + "/default-$0-vimbridge-R")
)
##### Kitty API #####
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:
if tab.is_active:
left_statusline = left_statusline_components()
right_statusline = right_statusline_components()
draw_components(screen, left_statusline)
left_text_size = screen.cursor.x
draw_components(screen, right_statusline)
right_text_size = screen.cursor.x - left_text_size
screen.cursor.x = left_text_size
tabs = all_tab_components(screen, left_text_size, right_text_size, tab.tab_id)
draw_components(screen, tabs)
draw_components(screen, right_statusline)
return screen.cursor.x kitty.conf
tpipeline config
I have one remaining question: |
Beta Was this translation helpful? Give feedback.
-
Thanks to your contributions, I was able to create mine
|
Beta Was this translation helpful? Give feedback.
-
Hi, I'm using https://github.com/typicode/bg.nvim to set the background color of kitty to match my neovim theme. Can I somehow read the current background color of the tab? I want to change the tab bar colors depending on the background of each tab. :) |
Beta Was this translation helpful? Give feedback.
-
On Thu, Mar 14, 2024 at 03:20:19AM -0700, Jen Stehlik wrote:
Hi, I'm using https://github.com/typicode/bg.nvim to set the background color of kitty to match my neovim theme. Can I somehow read the current background color of the tab? I want to change the tab bar colors depending on the background of each tab. :)
Tabs dont have background colors, ever window in a tab can have a different background color.
The best you can do is the background color of the active window in the tab, which you can get
from the tab_id. Use the boss object to get the tab object, gets its
active window and get the active window's background color.
get_boss().all_tabs + tab_id to get tab
then,
tab.active_window.screen.color_profile.default_bg
|
Beta Was this translation helpful? Give feedback.
-
I recently switched from tmux+Kitty to just Kitty, so my goal was to emulate the the most important parts of my tmux setup. I display all windows in the currently active tab, as I use kitty tabs almost like tmux sessions and kitty windows like tmux windows/panes (toggling between stack/tall layouts). I also have a project switcher set up, (originally from kitty-meow) to make opening projects faster. Many thanks to @wochap, @0rphee & everyone for sharing your setups! tab_bar.py"""draw kitty tab"""
# pyright: reportMissingImports=false
# pylint: disable=E0401,C0116,C0103,W0603,R0913
from kitty.boss import get_boss
from kitty.fast_data_types import Screen, get_options
from kitty.tab_bar import (
DrawData,
ExtraData,
TabBarData,
as_rgb,
draw_title,
)
from kitty.utils import color_as_int
opts = get_options()
# colors
# MAGENTA_1 = as_rgb(color_as_int(opts.color5))
TABBAR_BG = as_rgb(color_as_int(opts.tab_bar_background or opts.color0))
ACTIVE_BG = as_rgb(color_as_int(opts.active_tab_background or opts.color8))
ACTIVE_FG = as_rgb(color_as_int(opts.active_tab_foreground or opts.color4))
INACTIVE_BG = as_rgb(color_as_int(
opts.inactive_tab_background or opts.color12))
INACTIVE_FG = as_rgb(color_as_int(opts.inactive_tab_foreground or opts.color7))
ACTIVE_WINDOW_BG = as_rgb(color_as_int(opts.color6))
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_left_status(
draw_data, screen, tab, before, max_title_length, index, is_last, extra_data
)
if is_last:
_draw_right_status(screen)
return screen.cursor.x
def _draw_right_status(screen: Screen) -> int:
tab_manager = get_boss().active_tab_manager
cells = []
LOWER_RIGHT_TRIANGLE = ''
FORWARD_SLASH = ''
if tab_manager is not None:
windows = tab_manager.active_tab.windows.all_windows
if windows is not None:
for i, window in enumerate(windows):
is_active = window.id == tab_manager.active_window.id
is_first = i == 0
is_prev_active = windows[i -
1].id == tab_manager.active_window.id if not is_first else False
sup = to_sup(str(i + 1))
window_fg = ACTIVE_FG if is_active else INACTIVE_FG
window_bg = ACTIVE_WINDOW_BG if is_active else INACTIVE_BG
if is_first:
sep = LOWER_RIGHT_TRIANGLE
sep_bg = TABBAR_BG
sep_fg = INACTIVE_BG if not is_active else ACTIVE_WINDOW_BG
elif is_active:
sep = LOWER_RIGHT_TRIANGLE
sep_bg = INACTIVE_BG
sep_fg = ACTIVE_WINDOW_BG
elif is_prev_active:
sep = LOWER_RIGHT_TRIANGLE
sep_bg = ACTIVE_WINDOW_BG
sep_fg = INACTIVE_BG
else:
sep = FORWARD_SLASH
sep_bg = INACTIVE_BG
sep_fg = INACTIVE_FG
cells.insert(
i*2, (window_fg, window_bg, f" {sup} {window.title} "))
cells.insert(
i*2, (sep_fg, sep_bg, sep))
# calculate leading spaces to separate tabs from right status
right_status_length = 0
for _, _, cell in cells:
right_status_length += len(cell)
# calculate leading spaces
leading_spaces = 0
leading_spaces = screen.columns - screen.cursor.x - right_status_length
# draw leading spaces
if leading_spaces > 0:
screen.draw(" " * leading_spaces)
# draw right status
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
# update cursor position
screen.cursor.x = max(
screen.cursor.x, screen.columns - right_status_length)
return screen.cursor.x
SEPARATOR_SYMBOL, SOFT_SEPARATOR_SYMBOL = ("", "")
ICON = " "
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.cursor.fg = prev_fg # separator_fg
screen.draw(" " + SOFT_SEPARATOR_SYMBOL)
end = screen.cursor.x
return end
def to_sup(s):
sups = {u'0': u'\u2070',
u'1': u'\xb9',
u'2': u'\xb2',
u'3': u'\xb3',
u'4': u'\u2074',
u'5': u'\u2075',
u'6': u'\u2076',
u'7': u'\u2077',
u'8': u'\u2078',
u'9': u'\u2079'}
return ''.join(sups.get(char, char) for char in s) kitty.conf
|
Beta Was this translation helpful? Give feedback.
-
A powerline based custom tab bar, icon-centric, requires Nerd fonts. It draws:
Note that original titles (except the Neovim's) are not altered at the shell level, so they look usual in other terminal emulators. |
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.
-
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