More work on the themes kitten
This commit is contained in:
parent
e50c26d1b9
commit
92a9b71f21
@ -11,12 +11,13 @@ import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
from contextlib import suppress
|
||||
from typing import Any, Callable, Dict, Iterator, Match, Optional
|
||||
from typing import Any, Callable, Dict, Iterator, Match, Optional, Tuple, Union
|
||||
from urllib.error import HTTPError
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
from kitty.config import parse_config
|
||||
from kitty.constants import cache_dir, config_dir
|
||||
from kitty.options.types import Options as KittyOptions
|
||||
from kitty.rgb import Color
|
||||
|
||||
from ..choose.main import match
|
||||
@ -171,6 +172,7 @@ class Theme:
|
||||
def __init__(self, loader: Callable[[], str]):
|
||||
self._loader = loader
|
||||
self._raw: Optional[str] = None
|
||||
self._opts: Optional[KittyOptions] = None
|
||||
|
||||
@property
|
||||
def raw(self) -> str:
|
||||
@ -178,11 +180,18 @@ class Theme:
|
||||
self._raw = self._loader()
|
||||
return self._raw
|
||||
|
||||
@property
|
||||
def kitty_opts(self) -> KittyOptions:
|
||||
if self._opts is None:
|
||||
self._opts = KittyOptions(options_dict=parse_config(self.raw.splitlines()))
|
||||
return self._opts
|
||||
|
||||
|
||||
class Themes:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.themes: Dict[str, Theme] = {}
|
||||
self.index_map: Tuple[str, ...] = ()
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.themes)
|
||||
@ -190,6 +199,13 @@ class Themes:
|
||||
def __iter__(self) -> Iterator[Theme]:
|
||||
return iter(self.themes.values())
|
||||
|
||||
def __getitem__(self, key: Union[int, str]) -> Theme:
|
||||
if isinstance(key, str):
|
||||
return self.themes[key]
|
||||
if key < 0:
|
||||
key += len(self.index_map)
|
||||
return self.themes[self.index_map[key]]
|
||||
|
||||
def load_from_zip(self, path_to_zip: str) -> None:
|
||||
with zipfile.ZipFile(path_to_zip, 'r') as zf:
|
||||
for name in zf.namelist():
|
||||
@ -225,7 +241,18 @@ class Themes:
|
||||
|
||||
def filtered(self, is_ok: Callable[[Theme], bool]) -> 'Themes':
|
||||
ans = Themes()
|
||||
ans.themes = {k: v for k, v in self.themes.items() if is_ok(v)}
|
||||
|
||||
def sort_key(k: Tuple[str, Theme]) -> str:
|
||||
return k[1].name.lower()
|
||||
|
||||
ans.themes = {k: v for k, v in sorted(self.themes.items(), key=sort_key) if is_ok(v)}
|
||||
ans.index_map = tuple(ans.themes)
|
||||
return ans
|
||||
|
||||
def copy(self) -> 'Themes':
|
||||
ans = Themes()
|
||||
ans.themes = self.themes.copy()
|
||||
ans.index_map = self.index_map
|
||||
return ans
|
||||
|
||||
def apply_search(self, expression: str, mark_before: str = '\033[32m', mark_after: str = '\033[39m') -> Iterator[str]:
|
||||
@ -235,10 +262,12 @@ class Themes:
|
||||
yield result[0]
|
||||
else:
|
||||
del self.themes[k]
|
||||
self.index_map = tuple(x for x in self.index_map if x != k)
|
||||
|
||||
|
||||
def load_themes() -> Themes:
|
||||
ans = Themes()
|
||||
ans.load_from_zip(fetch_themes())
|
||||
ans.load_from_dir(os.path.join(config_dir, 'themes'))
|
||||
ans.index_map = tuple(ans.themes)
|
||||
return ans
|
||||
|
||||
@ -10,14 +10,16 @@ from typing import (
|
||||
Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Union
|
||||
)
|
||||
|
||||
from kitty.cli import create_default_opts
|
||||
from kitty.config import cached_values_for
|
||||
from kitty.fast_data_types import wcswidth
|
||||
from kitty.rgb import color_as_sharp, color_from_int
|
||||
from kitty.typing import KeyEventType
|
||||
from kitty.utils import ScreenSize
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import styled
|
||||
from ..tui.operations import styled, color_code
|
||||
from .collection import Theme, Themes, load_themes
|
||||
|
||||
|
||||
@ -61,20 +63,28 @@ class ThemesList:
|
||||
self.max_width = 0
|
||||
self.current_idx = 0
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return bool(self.display_strings)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.themes)
|
||||
|
||||
def update_themes(self, themes: Themes) -> None:
|
||||
self.themes = themes
|
||||
self.themes = self.all_themes = themes
|
||||
if self.current_search:
|
||||
self.themes = self.all_themes.copy()
|
||||
self.display_strings = tuple(self.themes.apply_search(self.current_search))
|
||||
else:
|
||||
self.display_strings = tuple(t.name for t in self.themes)
|
||||
self.widths = tuple(map(wcswidth, self.display_strings))
|
||||
self.max_width = max(self.widths) if self.widths else 0
|
||||
self.current_idx = 0
|
||||
|
||||
def update_search(self, search: str = '') -> None:
|
||||
if search == self.current_search:
|
||||
return
|
||||
self.current_search = search
|
||||
self.update_themes(self.themes)
|
||||
self.update_themes(self.all_themes)
|
||||
|
||||
def lines(self, num_rows: int) -> Iterator[str]:
|
||||
if num_rows < 1:
|
||||
@ -87,6 +97,10 @@ class ThemesList:
|
||||
line = styled(line, reverse=True)
|
||||
yield line
|
||||
|
||||
@property
|
||||
def current_theme(self) -> Theme:
|
||||
return self.themes[self.current_idx]
|
||||
|
||||
|
||||
class ThemesHandler(Handler):
|
||||
|
||||
@ -99,6 +113,7 @@ class ThemesHandler(Handler):
|
||||
'recent': create_recent_filter(self.cached_values.get('recent', ()))
|
||||
}
|
||||
self.themes_list = ThemesList()
|
||||
self.colors_set_once = False
|
||||
|
||||
def enforce_cursor_state(self) -> None:
|
||||
self.cmd.set_cursor_visible(self.state == State.fetching)
|
||||
@ -131,8 +146,27 @@ class ThemesHandler(Handler):
|
||||
cat = 'all'
|
||||
self.cached_values['category'] = cat
|
||||
|
||||
def set_colors_to_current_theme(self) -> bool:
|
||||
if not self.themes_list and self.colors_set_once:
|
||||
return False
|
||||
self.colors_set_once = True
|
||||
if self.themes_list:
|
||||
o = self.themes_list.current_theme.kitty_opts
|
||||
else:
|
||||
o = create_default_opts()
|
||||
self.cmd.set_default_colors(
|
||||
fg=o.foreground, bg=o.background, cursor=o.cursor, select_bg=o.selection_background, select_fg=o.selection_foreground
|
||||
)
|
||||
self.current_opts = o
|
||||
cmds = []
|
||||
for i in range(256):
|
||||
col = color_as_sharp(color_from_int(o.color_table[i]))
|
||||
cmds.append(f'{i};{col}')
|
||||
self.print(end='\033]4;' + ';'.join(cmds) + '\033\\')
|
||||
|
||||
def redraw_after_category_change(self) -> None:
|
||||
self.themes_list.update_themes(self.all_themes.filtered(self.filter_map[self.current_category]))
|
||||
self.set_colors_to_current_theme()
|
||||
self.draw_screen()
|
||||
|
||||
# Theme fetching {{{
|
||||
@ -168,7 +202,32 @@ class ThemesHandler(Handler):
|
||||
|
||||
# Theme browsing {{{
|
||||
def draw_tab_bar(self) -> None:
|
||||
pass
|
||||
bar_bg = self.current_opts.tab_bar_background or self.current_opts.background
|
||||
self.cmd.sgr(color_code(bar_bg, base=40))
|
||||
self.print(' ' * self.screen_size.cols, end='\r')
|
||||
|
||||
def draw_tab(text: str, name: str, sc: str) -> None:
|
||||
is_active = name == self.current_category
|
||||
text = f'{sc}: {text}'
|
||||
if is_active:
|
||||
text = f'{text} ({len(self.themes_list)})'
|
||||
fg, bg = self.current_opts.active_tab_foreground, self.current_opts.active_tab_background
|
||||
else:
|
||||
fg, bg = self.current_opts.inactive_tab_foreground, self.current_opts.inactive_tab_background
|
||||
|
||||
def draw_sep(which: str) -> None:
|
||||
self.write(styled(which, fg=bar_bg, bg=bg))
|
||||
self.cmd.sgr(color_code(fg), color_code(bg, base=40))
|
||||
|
||||
draw_sep('')
|
||||
self.write(f' {text} ')
|
||||
draw_sep('')
|
||||
|
||||
draw_tab('All', 'all', 'F1')
|
||||
draw_tab('Dark', 'dark', 'F2')
|
||||
draw_tab('Light', 'light', 'F3')
|
||||
draw_tab('Recent', 'recent', 'F4')
|
||||
self.cmd.sgr('39', '49') # reset fg/bg
|
||||
|
||||
def draw_browsing_screen(self) -> None:
|
||||
self.draw_tab_bar()
|
||||
|
||||
12
kitty/rgb.py
generated
12
kitty/rgb.py
generated
@ -4,7 +4,7 @@
|
||||
|
||||
import re
|
||||
from contextlib import suppress
|
||||
from typing import Optional, NamedTuple
|
||||
from typing import NamedTuple, Optional, Tuple
|
||||
|
||||
|
||||
class Color(NamedTuple):
|
||||
@ -12,6 +12,12 @@ class Color(NamedTuple):
|
||||
green: int = 0
|
||||
blue: int = 0
|
||||
|
||||
def __truediv__(self, denom: float) -> Tuple[float, float, float]:
|
||||
return self.red / denom, self.green / denom, self.blue / denom
|
||||
|
||||
def as_sgr(self) -> str:
|
||||
return ':2:{}:{}:{}'.format(*self)
|
||||
|
||||
def luminance(self) -> float:
|
||||
return 0.299 * self.red + 0.587 * self.green + 0.114 * self.blue
|
||||
|
||||
@ -77,7 +83,7 @@ def color_as_sharp(x: Color) -> str:
|
||||
|
||||
|
||||
def color_as_sgr(x: Color) -> str:
|
||||
return ':2:{}:{}:{}'.format(*x)
|
||||
return x.as_sgr()
|
||||
|
||||
|
||||
def to_color(raw: str, validate: bool = False) -> Optional[Color]:
|
||||
@ -856,8 +862,8 @@ color_names = {
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Read RGB color table from specified rgb.txt file
|
||||
import sys
|
||||
import pprint
|
||||
import sys
|
||||
data = {}
|
||||
with open(sys.argv[-1]) as f:
|
||||
for line in f:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user