More work on the themes kitten

This commit is contained in:
Kovid Goyal 2021-08-04 18:43:26 +05:30
parent e50c26d1b9
commit 92a9b71f21
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 103 additions and 9 deletions

View File

@ -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

View File

@ -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
View File

@ -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: