Finish porting clipboard kitten to Go
This commit is contained in:
parent
63a08dc6cc
commit
a721ffeb7d
@ -292,23 +292,29 @@ def wrapped_kittens() -> Sequence[str]:
|
||||
def kitten_clis() -> None:
|
||||
for kitten in wrapped_kittens():
|
||||
with replace_if_needed(f'tools/cmd/{kitten}/cli_generated.go'):
|
||||
od = []
|
||||
kcd = kitten_cli_docs(kitten)
|
||||
has_underscore = '_' in kitten
|
||||
print(f'package {kitten}')
|
||||
print('import "kitty/tools/cli"')
|
||||
print('func create_cmd(root *cli.Command, run_func cli.RunFunc) {')
|
||||
print('func create_cmd(root *cli.Command, run_func func(*cli.Command, *Options, []string)(int, error)) {')
|
||||
print('ans := root.AddSubCommand(&cli.Command{')
|
||||
print(f'Name: "{kitten}",')
|
||||
print(f'ShortDescription: "{serialize_as_go_string(kcd["short_desc"])}",')
|
||||
print(f'Usage: "{serialize_as_go_string(kcd["usage"])}",')
|
||||
print(f'HelpText: "{serialize_as_go_string(kcd["help_text"])}",')
|
||||
print('Run: run_func,')
|
||||
print('Run: func(cmd *cli.Command, args []string) (int, error) {')
|
||||
print('opts := Options{}')
|
||||
print('err := cmd.GetOptionValues(&opts)')
|
||||
print('if err != nil { return 1, err }')
|
||||
print('return run_func(cmd, &opts, args)},')
|
||||
if has_underscore:
|
||||
print('Hidden: true,')
|
||||
print('})')
|
||||
gopts, ac = go_options_for_kitten(kitten)
|
||||
for opt in gopts:
|
||||
print(opt.as_option('ans'))
|
||||
od.append(opt.struct_declaration())
|
||||
if ac is not None:
|
||||
print(''.join(ac.as_go_code('ans.ArgCompleter', ' = ')))
|
||||
if has_underscore:
|
||||
@ -316,6 +322,10 @@ def kitten_clis() -> None:
|
||||
print('clone.Hidden = false')
|
||||
print(f'clone.Name = "{serialize_as_go_string(kitten.replace("_", "-"))}"')
|
||||
print('}')
|
||||
print('type Options struct {')
|
||||
print('\n'.join(od))
|
||||
print('}')
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
|
||||
194
tools/cmd/clipboard/legacy.go
Normal file
194
tools/cmd/clipboard/legacy.go
Normal file
@ -0,0 +1,194 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package clipboard
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"kitty/tools/tty"
|
||||
"kitty/tools/tui/loop"
|
||||
"kitty/tools/utils"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func encode_read_from_clipboard(use_primary bool) string {
|
||||
dest := "c"
|
||||
if use_primary {
|
||||
dest = "p"
|
||||
}
|
||||
return fmt.Sprintf("\x1b]52;%s;?\x1b\\", dest)
|
||||
}
|
||||
|
||||
type base64_streaming_enc struct {
|
||||
output func(string)
|
||||
}
|
||||
|
||||
func (self *base64_streaming_enc) Write(p []byte) (int, error) {
|
||||
if len(p) > 0 {
|
||||
self.output(string(p))
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func run_plain_text_loop(opts *Options) (err error) {
|
||||
lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
stdin_chan := make(chan string, 1)
|
||||
err_chan := make(chan error, 1)
|
||||
dest := "c"
|
||||
if opts.UsePrimary {
|
||||
dest = "p"
|
||||
}
|
||||
|
||||
send_to_loop := func(data string) {
|
||||
select {
|
||||
case stdin_chan <- data:
|
||||
lp.WakeupMainThread()
|
||||
return
|
||||
default:
|
||||
lp.WakeupMainThread()
|
||||
}
|
||||
stdin_chan <- data
|
||||
}
|
||||
|
||||
read_from_stdin := func() {
|
||||
if !tty.IsTerminal(os.Stdin.Fd()) {
|
||||
var buf [8192]byte
|
||||
enc := base64.NewEncoder(base64.StdEncoding, &base64_streaming_enc{send_to_loop})
|
||||
header_written := false
|
||||
for {
|
||||
n, err := os.Stdin.Read(buf[:])
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
enc.Close()
|
||||
send_to_loop("\a")
|
||||
close(stdin_chan)
|
||||
os.Stdin.Close()
|
||||
break
|
||||
}
|
||||
err_chan <- fmt.Errorf("Failed to read from STDIN with error: %w", err)
|
||||
lp.WakeupMainThread()
|
||||
break
|
||||
}
|
||||
if n > 0 {
|
||||
if !header_written {
|
||||
header_written = true
|
||||
send_to_loop(fmt.Sprintf("\x1b]52;%s;", dest))
|
||||
}
|
||||
enc.Write(buf[:n])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
close(stdin_chan)
|
||||
lp.WakeupMainThread()
|
||||
}
|
||||
}
|
||||
|
||||
transmitting := true
|
||||
|
||||
lp.OnWakeup = func() error {
|
||||
for transmitting {
|
||||
select {
|
||||
case p, more := <-stdin_chan:
|
||||
if more {
|
||||
lp.QueueWriteString(p)
|
||||
} else {
|
||||
transmitting = false
|
||||
if opts.GetClipboard {
|
||||
lp.QueueWriteString(encode_read_from_clipboard(opts.UsePrimary))
|
||||
} else if opts.WaitForCompletion {
|
||||
lp.QueueWriteString("\x1bP+q544e\x1b\\")
|
||||
} else {
|
||||
lp.Quit(0)
|
||||
}
|
||||
}
|
||||
case err := <-err_chan:
|
||||
return err
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
lp.OnInitialize = func() (string, error) {
|
||||
go read_from_stdin()
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var clipboard_contents []byte
|
||||
|
||||
lp.OnEscapeCode = func(etype loop.EscapeCodeType, data []byte) (err error) {
|
||||
switch etype {
|
||||
case loop.DCS:
|
||||
if strings.HasPrefix(utils.UnsafeBytesToString(data), "1+r") {
|
||||
lp.Quit(0)
|
||||
}
|
||||
case loop.OSC:
|
||||
q := utils.UnsafeBytesToString(data)
|
||||
if strings.HasPrefix(q, "52;") {
|
||||
parts := strings.SplitN(q, ";", 3)
|
||||
if len(parts) < 3 {
|
||||
lp.Quit(0)
|
||||
return
|
||||
}
|
||||
data, err := base64.StdEncoding.DecodeString(parts[2])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid base64 encoded data from terminal with error: %w", err)
|
||||
}
|
||||
clipboard_contents = data
|
||||
lp.Quit(0)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
esc_count := 0
|
||||
lp.OnKeyEvent = func(event *loop.KeyEvent) error {
|
||||
if event.MatchesPressOrRepeat("ctrl+c") || event.MatchesPressOrRepeat("esc") {
|
||||
if transmitting {
|
||||
return nil
|
||||
}
|
||||
event.Handled = true
|
||||
esc_count++
|
||||
if esc_count < 2 {
|
||||
key := "esc"
|
||||
if event.MatchesPressOrRepeat("ctrl+c") {
|
||||
key = "Ctrl+C"
|
||||
}
|
||||
lp.QueueWriteString(fmt.Sprintf("Waiting for response from terminal, press %s again to abort. This could cause garbage to be spewed to the screen.\r\n", key))
|
||||
} else {
|
||||
return fmt.Errorf("Aborted by user!")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err = lp.Run()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ds := lp.DeathSignalName()
|
||||
if ds != "" {
|
||||
fmt.Println("Killed by signal: ", ds)
|
||||
lp.KillIfSignalled()
|
||||
return
|
||||
}
|
||||
if len(clipboard_contents) > 0 {
|
||||
_, err = os.Stdout.Write(clipboard_contents)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Failed to write to STDOUT with error: %w", err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -4,13 +4,16 @@ package clipboard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"kitty/tools/cli"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
func clipboard_main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) {
|
||||
if len(args) > 0 {
|
||||
return 1, fmt.Errorf("Unrecognized extra command line arguments")
|
||||
}
|
||||
|
||||
func clipboard_main(cmd *cli.Command, args []string) (int, error) {
|
||||
return 0, nil
|
||||
return 0, run_plain_text_loop(opts)
|
||||
}
|
||||
|
||||
func EntryPoint(parent *cli.Command) {
|
||||
|
||||
@ -20,6 +20,16 @@ type ScreenSize struct {
|
||||
|
||||
type IdType uint64
|
||||
type TimerCallback func(timer_id IdType) error
|
||||
type EscapeCodeType int
|
||||
|
||||
const (
|
||||
CSI EscapeCodeType = iota
|
||||
DCS
|
||||
OSC
|
||||
APC
|
||||
SOS
|
||||
PM
|
||||
)
|
||||
|
||||
type timer struct {
|
||||
interval time.Duration
|
||||
@ -47,6 +57,8 @@ type Loop struct {
|
||||
pending_writes []*write_msg
|
||||
on_SIGTSTP func() error
|
||||
|
||||
// Send strings to this channel to queue writes in a thread safe way
|
||||
|
||||
// Callbacks
|
||||
|
||||
// Called when the terminal has been fully setup. Any string returned is sent to
|
||||
@ -76,6 +88,9 @@ type Loop struct {
|
||||
// Called when any input from tty is received
|
||||
OnReceivedData func(data []byte) error
|
||||
|
||||
// Called when an escape code is received that is not handled by any other handler
|
||||
OnEscapeCode func(EscapeCodeType, []byte) error
|
||||
|
||||
// Called when resuming from a SIGTSTP or Ctrl-z
|
||||
OnResumeFromStop func() error
|
||||
|
||||
|
||||
@ -72,6 +72,9 @@ func (self *Loop) handle_csi(raw []byte) error {
|
||||
if ke != nil {
|
||||
return self.handle_key_event(ke)
|
||||
}
|
||||
if self.OnEscapeCode != nil {
|
||||
return self.OnEscapeCode(CSI, raw)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -100,6 +103,9 @@ func (self *Loop) handle_key_event(ev *KeyEvent) error {
|
||||
}
|
||||
|
||||
func (self *Loop) handle_osc(raw []byte) error {
|
||||
if self.OnEscapeCode != nil {
|
||||
return self.OnEscapeCode(OSC, raw)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -107,18 +113,30 @@ func (self *Loop) handle_dcs(raw []byte) error {
|
||||
if self.OnRCResponse != nil && bytes.HasPrefix(raw, []byte("@kitty-cmd")) {
|
||||
return self.OnRCResponse(raw[len("@kitty-cmd"):])
|
||||
}
|
||||
if self.OnEscapeCode != nil {
|
||||
return self.OnEscapeCode(DCS, raw)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Loop) handle_apc(raw []byte) error {
|
||||
if self.OnEscapeCode != nil {
|
||||
return self.OnEscapeCode(APC, raw)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Loop) handle_sos(raw []byte) error {
|
||||
if self.OnEscapeCode != nil {
|
||||
return self.OnEscapeCode(SOS, raw)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Loop) handle_pm(raw []byte) error {
|
||||
if self.OnEscapeCode != nil {
|
||||
return self.OnEscapeCode(PM, raw)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user