diff --git a/kitty/fonts/list.py b/kitty/fonts/list.py index b93b0488b..d141bdaf0 100644 --- a/kitty/fonts/list.py +++ b/kitty/fonts/list.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # License: GPL v3 Copyright: 2017, Kovid Goyal -from typing import Dict, List, Sequence +from typing import Any, BinaryIO, Dict, List, Optional, Sequence from kitty.constants import is_macos @@ -14,30 +14,58 @@ else: from .fontconfig import list_fonts, prune_family_group -def create_family_groups(monospaced: bool = True) -> Dict[str, List[ListedFont]]: +def create_family_groups(monospaced: bool = True, add_variable_data: bool = False) -> Dict[str, List[ListedFont]]: g: Dict[str, List[ListedFont]] = {} for f in list_fonts(): if not monospaced or f['is_monospace']: g.setdefault(f['family'], []).append(f) + if add_variable_data and f['is_variable']: + f['variable_data'] = get_variable_data_for_descriptor(f['descriptor']) # type: ignore return {k: prune_family_group(v) for k, v in g.items()} -def as_json() -> str: +def as_json(indent: Optional[int] = None) -> str: import json - groups = create_family_groups() - for g in groups.values(): - for f in g: - if f['is_variable']: - f['variable_data'] = get_variable_data_for_descriptor(f['descriptor']) - return json.dumps(groups, indent=2) + groups = create_family_groups(add_variable_data=True) + return json.dumps(groups, indent=indent) + + +exception_in_io_handler: Optional[Exception] = None + + +def handle_io(from_kitten: BinaryIO, to_kitten: BinaryIO) -> None: + import json + global exception_in_io_handler + + def send_to_kitten(x: Any) -> None: + to_kitten.write(json.dumps(x).encode()) + to_kitten.write(b'\n') + + try: + send_to_kitten(create_family_groups(add_variable_data=True)) + to_kitten.write(as_json().encode()) + for line in from_kitten: + cmd = json.loads(line) + action = cmd['action'] + if action == 'ping': + send_to_kitten({'action': 'pong'}) + except Exception as e: + exception_in_io_handler = e def main(argv: Sequence[str]) -> None: import subprocess + import sys + from threading import Thread from kitty.constants import kitten_exe argv = list(argv) if '--psnames' in argv: argv.remove('--psnames') - cp = subprocess.run([kitten_exe(), '__list_fonts__'], input=as_json().encode()) - raise SystemExit(cp.returncode) + p = subprocess.Popen([kitten_exe(), '__list_fonts__'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) + Thread(target=handle_io, args=(p.stdout, p.stdin), daemon=True).start() + try: + raise SystemExit(p.wait()) + finally: + if exception_in_io_handler is not None: + print(exception_in_io_handler, file=sys.stderr) diff --git a/tools/cmd/list_fonts/main.go b/tools/cmd/list_fonts/main.go index f911d0db0..2935f7c21 100644 --- a/tools/cmd/list_fonts/main.go +++ b/tools/cmd/list_fonts/main.go @@ -12,18 +12,15 @@ import ( var _ = fmt.Print var debugprintln = tty.DebugPrintln +var json_decoder *json.Decoder func main() (rc int, err error) { - d := json.NewDecoder(os.Stdin) - var fonts map[string][]ListedFont - if err = d.Decode(&fonts); err != nil { - return 1, err - } + json_decoder = json.NewDecoder(os.Stdin) lp, err := loop.New() if err != nil { return 1, err } - h := &handler{lp: lp, fonts: fonts} + h := &handler{lp: lp} lp.OnInitialize = func() (string, error) { lp.AllowLineWrapping(false) lp.SetWindowTitle(`Choose a font for kitty`) diff --git a/tools/cmd/list_fonts/ui.go b/tools/cmd/list_fonts/ui.go index d5d2977c3..f574b80b3 100644 --- a/tools/cmd/list_fonts/ui.go +++ b/tools/cmd/list_fonts/ui.go @@ -3,6 +3,7 @@ package list_fonts import ( "fmt" "strings" + "sync" "kitty/tools/tui/loop" "kitty/tools/tui/readline" @@ -16,20 +17,35 @@ var _ = fmt.Print type State int const ( - LISTING_FAMILIES State = iota + SCANNING_FAMILIES State = iota + LISTING_FAMILIES CHOOSING_FACES ) type handler struct { - lp *loop.Loop - fonts map[string][]ListedFont - state State + lp *loop.Loop + fonts map[string][]ListedFont + state State + err_mutex sync.Mutex + err_in_worker_thread error // Listing rl *readline.Readline family_list FamilyList } +func (h *handler) set_worker_error(err error) { + h.err_mutex.Lock() + defer h.err_mutex.Unlock() + h.err_in_worker_thread = err +} + +func (h *handler) get_worker_error() error { + h.err_mutex.Lock() + defer h.err_mutex.Unlock() + return h.err_in_worker_thread +} + // Listing families {{{ func (h *handler) draw_search_bar() { h.lp.SetCursorVisible(true) @@ -103,9 +119,15 @@ func (h *handler) next(delta int, allow_wrapping bool) { } func (h *handler) handle_listing_key_event(event *loop.KeyEvent) (err error) { - if event.MatchesPressOrRepeat("ctrl+c") || event.MatchesPressOrRepeat("esc") { - h.lp.Quit(1) + if event.MatchesPressOrRepeat("esc") { event.Handled = true + if h.rl.AllText() != "" { + h.rl.ResetText() + h.update_family_search() + h.draw_screen() + } else { + h.lp.Quit(1) + } return } ev := event @@ -162,9 +184,12 @@ func (h *handler) handle_listing_text(text string, from_key_event bool, in_brack // Events {{{ func (h *handler) initialize() { h.lp.SetCursorVisible(false) - h.family_list.UpdateFamilies(utils.StableSortWithKey(maps.Keys(h.fonts), strings.ToLower)) h.rl = readline.New(h.lp, readline.RlInit{DontMarkPrompts: true, Prompt: "Family: "}) h.draw_screen() + go func() { + h.set_worker_error(json_decoder.Decode(&h.fonts)) + h.lp.WakeupMainThread() + }() } func (h *handler) finalize() { @@ -178,6 +203,9 @@ func (h *handler) draw_screen() (err error) { h.lp.ClearScreen() h.lp.AllowLineWrapping(false) switch h.state { + case SCANNING_FAMILIES: + h.lp.Println("Scanning system for fonts, please wait...") + return nil case LISTING_FAMILIES: return h.draw_listing_screen() } @@ -185,10 +213,24 @@ func (h *handler) draw_screen() (err error) { } func (h *handler) on_wakeup() (err error) { + if err = h.get_worker_error(); err != nil { + return + } + switch h.state { + case SCANNING_FAMILIES: + h.state = LISTING_FAMILIES + h.family_list.UpdateFamilies(utils.StableSortWithKey(maps.Keys(h.fonts), strings.ToLower)) + return h.draw_screen() + } return } func (h *handler) on_key_event(event *loop.KeyEvent) (err error) { + if event.MatchesPressOrRepeat("ctrl+c") { + event.Handled = true + h.lp.Quit(1) + return nil + } switch h.state { case LISTING_FAMILIES: return h.handle_listing_key_event(event)