From 926dfd7ba109981d4616676f1960ac26eaeac7c7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 6 May 2024 12:17:25 +0530 Subject: [PATCH] Replace list_fonts with choose-fonts kitten --- kittens/choose_fonts/__init__.py | 0 kittens/choose_fonts/backend.go | 167 ++++++++++++++++++ .../choose_fonts}/family_list.go | 2 +- kittens/choose_fonts/main.go | 75 ++++++++ kittens/choose_fonts/main.py | 0 .../choose_fonts}/styles.go | 2 +- .../choose_fonts}/types.go | 4 +- .../list_fonts => kittens/choose_fonts}/ui.go | 4 +- tools/cmd/list_fonts/main.go | 101 ----------- tools/cmd/tool/main.go | 6 +- 10 files changed, 251 insertions(+), 110 deletions(-) create mode 100644 kittens/choose_fonts/__init__.py create mode 100644 kittens/choose_fonts/backend.go rename {tools/cmd/list_fonts => kittens/choose_fonts}/family_list.go (99%) create mode 100644 kittens/choose_fonts/main.go create mode 100644 kittens/choose_fonts/main.py rename {tools/cmd/list_fonts => kittens/choose_fonts}/styles.go (99%) rename {tools/cmd/list_fonts => kittens/choose_fonts}/types.go (96%) rename {tools/cmd/list_fonts => kittens/choose_fonts}/ui.go (98%) delete mode 100644 tools/cmd/list_fonts/main.go diff --git a/kittens/choose_fonts/__init__.py b/kittens/choose_fonts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/kittens/choose_fonts/backend.go b/kittens/choose_fonts/backend.go new file mode 100644 index 000000000..0a3c5c372 --- /dev/null +++ b/kittens/choose_fonts/backend.go @@ -0,0 +1,167 @@ +package choose_fonts + +import ( + "encoding/json" + "fmt" + "io" + "os" + "os/exec" + "strings" + "sync" + "time" + + "kitty/tools/utils" +) + +var _ = fmt.Print + +type kitty_font_backend_type struct { + from io.ReadCloser + to io.WriteCloser + json_decoder *json.Decoder + cmd *exec.Cmd + stderr strings.Builder + lock sync.Mutex + r io.ReadCloser + w io.WriteCloser + wait_for_exit chan error + started, exited, failed bool + timeout time.Duration +} + +func (k *kitty_font_backend_type) start() (err error) { + exe := utils.KittyExe() + if exe == "" { + exe = utils.Which("kitty") + } + if exe == "" { + return fmt.Errorf("Failed to find the kitty executable, this kitten requires the kitty executable to be present. You can use the environment variable KITTY_PATH_TO_KITTY_EXE to specify the path to the kitty executable") + } + + k.cmd = exec.Command(exe, "+runpy", "from kittens.choose_fonts.backend import main; main()") + k.cmd.Stderr = &k.stderr + + if k.r, k.to, err = os.Pipe(); err != nil { + return err + } + k.cmd.Stdin = k.r + if k.from, k.w, err = os.Pipe(); err != nil { + return err + } + k.cmd.Stdout = k.w + k.json_decoder = json.NewDecoder(k.from) + if err = k.cmd.Start(); err != nil { + return err + } + k.started = true + k.timeout = 60 * time.Second + k.wait_for_exit = make(chan error) + go func() { + k.wait_for_exit <- k.cmd.Wait() + }() + return +} + +var kitty_font_backend kitty_font_backend_type + +func (k *kitty_font_backend_type) send(v any) error { + data, err := json.Marshal(v) + if err != nil { + return fmt.Errorf("Could not encode message to kitty with error: %w", err) + } + c := make(chan error) + go func() { + if _, err = k.to.Write(data); err != nil { + c <- fmt.Errorf("Failed to send message to kitty with I/O error: %w", err) + return + } + if _, err = k.to.Write([]byte{'\n'}); err != nil { + c <- fmt.Errorf("Failed to send message to kitty with I/O error: %w", err) + return + } + c <- nil + }() + select { + case err := <-c: + return err + case <-time.After(k.timeout): + return fmt.Errorf("Timed out waiting to write to kitty font backend after %v", k.timeout) + case err := <-k.wait_for_exit: + k.exited = true + if err == nil { + err = fmt.Errorf("kitty font backend exited with no error while waiting for a response from it") + } else { + k.failed = true + } + return err + } +} + +func (k *kitty_font_backend_type) query(action string, cmd map[string]any, result any) error { + k.lock.Lock() + defer k.lock.Unlock() + if cmd == nil { + cmd = make(map[string]any) + } + cmd["action"] = action + if err := k.send(cmd); err != nil { + return err + } + c := make(chan error) + go func() { + if err := k.json_decoder.Decode(result); err != nil { + c <- fmt.Errorf("Failed to decode JSON from kitty with error: %w", err) + } + c <- nil + }() + select { + case err := <-c: + return err + case <-time.After(k.timeout): + return fmt.Errorf("Timed out waiting for response from kitty font backend after %v", k.timeout) + case err := <-k.wait_for_exit: + k.exited = true + if err == nil { + err = fmt.Errorf("kitty font backed exited with no error while waiting for a response from it") + } else { + k.failed = true + } + return err + } +} + +func (k *kitty_font_backend_type) release() (err error) { + if k.r != nil { + k.r.Close() + k.r = nil + } + if k.to != nil { + k.to.Close() + k.to = nil + } + if k.w != nil { + k.w.Close() + k.w = nil + } + if k.from != nil { + k.from.Close() + k.from = nil + } + if k.started && !k.exited { + timeout := 2 * time.Second + select { + case err = <-k.wait_for_exit: + k.exited = true + if err != nil { + k.failed = true + } + case <-time.After(timeout): + k.failed = true + err = fmt.Errorf("Timed out waiting for kitty font backend to exit for %v", timeout) + } + } + if k.failed { + os.Stderr.WriteString(k.stderr.String()) + } + return +} diff --git a/tools/cmd/list_fonts/family_list.go b/kittens/choose_fonts/family_list.go similarity index 99% rename from tools/cmd/list_fonts/family_list.go rename to kittens/choose_fonts/family_list.go index 79ef4499f..c39d133d8 100644 --- a/tools/cmd/list_fonts/family_list.go +++ b/kittens/choose_fonts/family_list.go @@ -1,4 +1,4 @@ -package list_fonts +package choose_fonts import ( "fmt" diff --git a/kittens/choose_fonts/main.go b/kittens/choose_fonts/main.go new file mode 100644 index 000000000..a56cf21e3 --- /dev/null +++ b/kittens/choose_fonts/main.go @@ -0,0 +1,75 @@ +package choose_fonts + +import ( + "fmt" + + "kitty/tools/cli" + "kitty/tools/tty" + "kitty/tools/tui/loop" +) + +var _ = fmt.Print +var debugprintln = tty.DebugPrintln + +func main() (rc int, err error) { + if err = kitty_font_backend.start(); err != nil { + return 1, err + } + defer func() { + if werr := kitty_font_backend.release(); werr != nil { + if err == nil { + err = werr + } + if rc == 0 { + rc = 1 + } + } + }() + lp, err := loop.New() + if err != nil { + return 1, err + } + lp.MouseTrackingMode(loop.FULL_MOUSE_TRACKING) + h := &handler{lp: lp} + lp.OnInitialize = func() (string, error) { + lp.AllowLineWrapping(false) + lp.SetWindowTitle(`Choose a font for kitty`) + h.initialize() + return "", nil + } + lp.OnWakeup = h.on_wakeup + lp.OnFinalize = func() string { + h.finalize() + lp.SetCursorVisible(true) + return `` + } + lp.OnMouseEvent = h.on_mouse_event + lp.OnResize = func(_, _ loop.ScreenSize) error { + return h.draw_screen() + } + lp.OnKeyEvent = h.on_key_event + lp.OnText = h.on_text + err = lp.Run() + if err != nil { + return 1, err + } + ds := lp.DeathSignalName() + if ds != "" { + fmt.Println("Killed by signal: ", ds) + lp.KillIfSignalled() + return 1, nil + } + return lp.ExitCode(), nil +} + +func EntryPoint(root *cli.Command) { + ans := root.AddSubCommand(&cli.Command{ + Name: "choose-fonts", + Run: func(cmd *cli.Command, args []string) (rc int, err error) { + return main() + }, + }) + clone := root.AddClone(ans.Group, ans) + clone.Hidden = false + clone.Name = "choose_fonts" +} diff --git a/kittens/choose_fonts/main.py b/kittens/choose_fonts/main.py new file mode 100644 index 000000000..e69de29bb diff --git a/tools/cmd/list_fonts/styles.go b/kittens/choose_fonts/styles.go similarity index 99% rename from tools/cmd/list_fonts/styles.go rename to kittens/choose_fonts/styles.go index 301c3536a..a8706ee3d 100644 --- a/tools/cmd/list_fonts/styles.go +++ b/kittens/choose_fonts/styles.go @@ -1,4 +1,4 @@ -package list_fonts +package choose_fonts import ( "fmt" diff --git a/tools/cmd/list_fonts/types.go b/kittens/choose_fonts/types.go similarity index 96% rename from tools/cmd/list_fonts/types.go rename to kittens/choose_fonts/types.go index 420274f7c..af8374011 100644 --- a/tools/cmd/list_fonts/types.go +++ b/kittens/choose_fonts/types.go @@ -1,4 +1,4 @@ -package list_fonts +package choose_fonts import ( "fmt" @@ -95,7 +95,7 @@ func ensure_variable_data_for_fonts(fonts ...ListedFont) error { } variable_data_cache_mutex.Unlock() var data []VariableData - if err := query_kitty("read_variable_data", map[string]any{"descriptors": descriptors}, &data); err != nil { + if err := kitty_font_backend.query("read_variable_data", map[string]any{"descriptors": descriptors}, &data); err != nil { return err } variable_data_cache_mutex.Lock() diff --git a/tools/cmd/list_fonts/ui.go b/kittens/choose_fonts/ui.go similarity index 98% rename from tools/cmd/list_fonts/ui.go rename to kittens/choose_fonts/ui.go index 15fa572ef..c5f15f496 100644 --- a/tools/cmd/list_fonts/ui.go +++ b/kittens/choose_fonts/ui.go @@ -1,4 +1,4 @@ -package list_fonts +package choose_fonts import ( "fmt" @@ -241,7 +241,7 @@ func (h *handler) initialize() { h.draw_screen() initialize_variable_data_cache() go func() { - h.set_worker_error(query_kitty("", nil, &h.fonts)) + h.set_worker_error(kitty_font_backend.query("list_all_fonts", nil, &h.fonts)) h.lp.WakeupMainThread() }() } diff --git a/tools/cmd/list_fonts/main.go b/tools/cmd/list_fonts/main.go deleted file mode 100644 index 513c91623..000000000 --- a/tools/cmd/list_fonts/main.go +++ /dev/null @@ -1,101 +0,0 @@ -package list_fonts - -import ( - "encoding/json" - "fmt" - "os" - "sync" - - "kitty/tools/cli" - "kitty/tools/tty" - "kitty/tools/tui/loop" -) - -var _ = fmt.Print -var debugprintln = tty.DebugPrintln - -var json_decoder *json.Decoder - -func json_decode(v any) error { - if err := json_decoder.Decode(v); err != nil { - return fmt.Errorf("Failed to decode JSON from kitty with error: %w", err) - } - return nil -} - -func to_kitty(v any) error { - data, err := json.Marshal(v) - if err != nil { - return fmt.Errorf("Could not encode message to kitty with error: %w", err) - } - if _, err = os.Stdout.Write(data); err != nil { - return fmt.Errorf("Failed to send message to kitty with I/O error: %w", err) - } - if _, err = os.Stdout.WriteString("\n"); err != nil { - return fmt.Errorf("Failed to send message to kitty with I/O error: %w", err) - } - return nil -} - -var query_kitty_lock sync.Mutex - -func query_kitty(action string, cmd map[string]any, result any) error { - query_kitty_lock.Lock() - defer query_kitty_lock.Unlock() - if action != "" { - cmd["action"] = action - if err := to_kitty(cmd); err != nil { - return err - } - } - return json_decode(result) -} - -func main() (rc int, err error) { - json_decoder = json.NewDecoder(os.Stdin) - lp, err := loop.New() - if err != nil { - return 1, err - } - lp.MouseTrackingMode(loop.FULL_MOUSE_TRACKING) - h := &handler{lp: lp} - lp.OnInitialize = func() (string, error) { - lp.AllowLineWrapping(false) - lp.SetWindowTitle(`Choose a font for kitty`) - h.initialize() - return "", nil - } - lp.OnWakeup = h.on_wakeup - lp.OnFinalize = func() string { - h.finalize() - lp.SetCursorVisible(true) - return `` - } - lp.OnMouseEvent = h.on_mouse_event - lp.OnResize = func(_, _ loop.ScreenSize) error { - return h.draw_screen() - } - lp.OnKeyEvent = h.on_key_event - lp.OnText = h.on_text - err = lp.Run() - if err != nil { - return 1, err - } - ds := lp.DeathSignalName() - if ds != "" { - fmt.Println("Killed by signal: ", ds) - lp.KillIfSignalled() - return 1, nil - } - return lp.ExitCode(), nil -} - -func EntryPoint(root *cli.Command) { - root = root.AddSubCommand(&cli.Command{ - Name: "__list_fonts__", - Hidden: true, - Run: func(cmd *cli.Command, args []string) (rc int, err error) { - return main() - }, - }) -} diff --git a/tools/cmd/tool/main.go b/tools/cmd/tool/main.go index c226fd06e..413a9fa18 100644 --- a/tools/cmd/tool/main.go +++ b/tools/cmd/tool/main.go @@ -6,6 +6,7 @@ import ( "fmt" "kitty/kittens/ask" + "kitty/kittens/choose_fonts" "kitty/kittens/clipboard" "kitty/kittens/diff" "kitty/kittens/hints" @@ -20,7 +21,6 @@ import ( "kitty/tools/cmd/at" "kitty/tools/cmd/benchmark" "kitty/tools/cmd/edit_in_kitty" - "kitty/tools/cmd/list_fonts" "kitty/tools/cmd/mouse_demo" "kitty/tools/cmd/pytest" "kitty/tools/cmd/run_shell" @@ -77,10 +77,10 @@ func KittyToolEntryPoints(root *cli.Command) { run_shell.EntryPoint(root) // show_error show_error.EntryPoint(root) + // choose-fonts + choose_fonts.EntryPoint(root) // __pytest__ pytest.EntryPoint(root) - // __list_fonts__ - list_fonts.EntryPoint(root) // __hold_till_enter__ root.AddSubCommand(&cli.Command{ Name: "__hold_till_enter__",