kitty/kittens/choose_fonts/backend.go
Kovid Goyal c861259e3b
Rename go module from kitty -> github.com/kovidgoyal/kitty
Makes the code more easily re-useable in other projects
2025-05-16 08:43:39 +05:30

168 lines
3.9 KiB
Go

package choose_fonts
import (
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"strings"
"sync"
"time"
"github.com/kovidgoyal/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 {
if k.to == nil {
return fmt.Errorf("Trying to send data when to pipe is nil")
}
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)
}
}
os.Stderr.WriteString(k.stderr.String())
return
}