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:
|
def kitten_clis() -> None:
|
||||||
for kitten in wrapped_kittens():
|
for kitten in wrapped_kittens():
|
||||||
with replace_if_needed(f'tools/cmd/{kitten}/cli_generated.go'):
|
with replace_if_needed(f'tools/cmd/{kitten}/cli_generated.go'):
|
||||||
|
od = []
|
||||||
kcd = kitten_cli_docs(kitten)
|
kcd = kitten_cli_docs(kitten)
|
||||||
has_underscore = '_' in kitten
|
has_underscore = '_' in kitten
|
||||||
print(f'package {kitten}')
|
print(f'package {kitten}')
|
||||||
print('import "kitty/tools/cli"')
|
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('ans := root.AddSubCommand(&cli.Command{')
|
||||||
print(f'Name: "{kitten}",')
|
print(f'Name: "{kitten}",')
|
||||||
print(f'ShortDescription: "{serialize_as_go_string(kcd["short_desc"])}",')
|
print(f'ShortDescription: "{serialize_as_go_string(kcd["short_desc"])}",')
|
||||||
print(f'Usage: "{serialize_as_go_string(kcd["usage"])}",')
|
print(f'Usage: "{serialize_as_go_string(kcd["usage"])}",')
|
||||||
print(f'HelpText: "{serialize_as_go_string(kcd["help_text"])}",')
|
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:
|
if has_underscore:
|
||||||
print('Hidden: true,')
|
print('Hidden: true,')
|
||||||
print('})')
|
print('})')
|
||||||
gopts, ac = go_options_for_kitten(kitten)
|
gopts, ac = go_options_for_kitten(kitten)
|
||||||
for opt in gopts:
|
for opt in gopts:
|
||||||
print(opt.as_option('ans'))
|
print(opt.as_option('ans'))
|
||||||
|
od.append(opt.struct_declaration())
|
||||||
if ac is not None:
|
if ac is not None:
|
||||||
print(''.join(ac.as_go_code('ans.ArgCompleter', ' = ')))
|
print(''.join(ac.as_go_code('ans.ArgCompleter', ' = ')))
|
||||||
if has_underscore:
|
if has_underscore:
|
||||||
@ -316,6 +322,10 @@ def kitten_clis() -> None:
|
|||||||
print('clone.Hidden = false')
|
print('clone.Hidden = false')
|
||||||
print(f'clone.Name = "{serialize_as_go_string(kitten.replace("_", "-"))}"')
|
print(f'clone.Name = "{serialize_as_go_string(kitten.replace("_", "-"))}"')
|
||||||
print('}')
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"kitty/tools/cli"
|
"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, run_plain_text_loop(opts)
|
||||||
return 0, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func EntryPoint(parent *cli.Command) {
|
func EntryPoint(parent *cli.Command) {
|
||||||
|
|||||||
@ -20,6 +20,16 @@ type ScreenSize struct {
|
|||||||
|
|
||||||
type IdType uint64
|
type IdType uint64
|
||||||
type TimerCallback func(timer_id IdType) error
|
type TimerCallback func(timer_id IdType) error
|
||||||
|
type EscapeCodeType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
CSI EscapeCodeType = iota
|
||||||
|
DCS
|
||||||
|
OSC
|
||||||
|
APC
|
||||||
|
SOS
|
||||||
|
PM
|
||||||
|
)
|
||||||
|
|
||||||
type timer struct {
|
type timer struct {
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
@ -47,6 +57,8 @@ type Loop struct {
|
|||||||
pending_writes []*write_msg
|
pending_writes []*write_msg
|
||||||
on_SIGTSTP func() error
|
on_SIGTSTP func() error
|
||||||
|
|
||||||
|
// Send strings to this channel to queue writes in a thread safe way
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
|
|
||||||
// Called when the terminal has been fully setup. Any string returned is sent to
|
// 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
|
// Called when any input from tty is received
|
||||||
OnReceivedData func(data []byte) error
|
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
|
// Called when resuming from a SIGTSTP or Ctrl-z
|
||||||
OnResumeFromStop func() error
|
OnResumeFromStop func() error
|
||||||
|
|
||||||
|
|||||||
@ -72,6 +72,9 @@ func (self *Loop) handle_csi(raw []byte) error {
|
|||||||
if ke != nil {
|
if ke != nil {
|
||||||
return self.handle_key_event(ke)
|
return self.handle_key_event(ke)
|
||||||
}
|
}
|
||||||
|
if self.OnEscapeCode != nil {
|
||||||
|
return self.OnEscapeCode(CSI, raw)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,6 +103,9 @@ func (self *Loop) handle_key_event(ev *KeyEvent) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *Loop) handle_osc(raw []byte) error {
|
func (self *Loop) handle_osc(raw []byte) error {
|
||||||
|
if self.OnEscapeCode != nil {
|
||||||
|
return self.OnEscapeCode(OSC, raw)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,18 +113,30 @@ func (self *Loop) handle_dcs(raw []byte) error {
|
|||||||
if self.OnRCResponse != nil && bytes.HasPrefix(raw, []byte("@kitty-cmd")) {
|
if self.OnRCResponse != nil && bytes.HasPrefix(raw, []byte("@kitty-cmd")) {
|
||||||
return self.OnRCResponse(raw[len("@kitty-cmd"):])
|
return self.OnRCResponse(raw[len("@kitty-cmd"):])
|
||||||
}
|
}
|
||||||
|
if self.OnEscapeCode != nil {
|
||||||
|
return self.OnEscapeCode(DCS, raw)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Loop) handle_apc(raw []byte) error {
|
func (self *Loop) handle_apc(raw []byte) error {
|
||||||
|
if self.OnEscapeCode != nil {
|
||||||
|
return self.OnEscapeCode(APC, raw)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Loop) handle_sos(raw []byte) error {
|
func (self *Loop) handle_sos(raw []byte) error {
|
||||||
|
if self.OnEscapeCode != nil {
|
||||||
|
return self.OnEscapeCode(SOS, raw)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Loop) handle_pm(raw []byte) error {
|
func (self *Loop) handle_pm(raw []byte) error {
|
||||||
|
if self.OnEscapeCode != nil {
|
||||||
|
return self.OnEscapeCode(PM, raw)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user