pagedown 83f505e209
Update known textual mimes to Python
Also add default aliases for JavaScript and XML in Go.
2023-01-25 16:42:15 +08:00

232 lines
5.8 KiB
Go

// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
package clipboard
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"kitty/tools/tui/loop"
"kitty/tools/utils"
)
var _ = fmt.Print
type Input struct {
src *os.File
arg string
ext string
is_stream bool
mime_type string
extra_mime_types []string
}
var known_textual_mimes = map[string]bool{
"application/x-sh": true,
"application/x-csh": true,
"application/x-shellscript": true,
"application/javascript": true,
"application/json": true,
"application/xml": true,
"application/x-yaml": true,
"application/yaml": true,
"application/x-toml": true,
"application/toml": true,
"application/rss+xml": true,
"application/xhtml+xml": true,
}
func is_textual_mime(x string) bool {
return strings.HasPrefix(x, "text/") || known_textual_mimes[x]
}
func is_text_plain_mime(x string) bool {
return x == "text/plain"
}
func (self *Input) has_mime_matching(predicate func(string) bool) bool {
if predicate(self.mime_type) {
return true
}
for _, i := range self.extra_mime_types {
if predicate(i) {
return true
}
}
return false
}
func write_loop(inputs []*Input, opts *Options) (err error) {
lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking)
if err != nil {
return err
}
var waiting_for_write loop.IdType
var buf [4096]byte
aliases, aerr := parse_aliases(opts.Alias)
if aerr != nil {
return aerr
}
num_text_mimes := 0
has_text_plain := false
for _, i := range inputs {
i.extra_mime_types = aliases[i.mime_type]
if i.has_mime_matching(is_textual_mime) {
num_text_mimes++
if !has_text_plain && i.has_mime_matching(is_text_plain_mime) {
has_text_plain = true
}
}
}
if num_text_mimes > 0 && !has_text_plain {
for _, i := range inputs {
if i.has_mime_matching(is_textual_mime) {
i.extra_mime_types = append(i.extra_mime_types, "text/plain")
break
}
}
}
make_metadata := func(ptype, mime string) map[string]string {
ans := map[string]string{"type": ptype}
if opts.UsePrimary {
ans["loc"] = "primary"
}
if mime != "" {
ans["mime"] = mime
}
return ans
}
lp.OnInitialize = func() (string, error) {
waiting_for_write = lp.QueueWriteString(encode(make_metadata("write", ""), ""))
return "", nil
}
write_chunk := func() error {
if len(inputs) == 0 {
return nil
}
i := inputs[0]
n, err := i.src.Read(buf[:])
if n > 0 {
waiting_for_write = lp.QueueWriteString(encode_bytes(make_metadata("wdata", i.mime_type), buf[:n]))
}
if err != nil {
if errors.Is(err, io.EOF) {
if len(i.extra_mime_types) > 0 {
lp.QueueWriteString(encode(make_metadata("walias", i.mime_type), strings.Join(i.extra_mime_types, " ")))
}
inputs = inputs[1:]
if len(inputs) == 0 {
lp.QueueWriteString(encode(make_metadata("wdata", ""), ""))
waiting_for_write = 0
}
return lp.OnWriteComplete(waiting_for_write)
}
return fmt.Errorf("Failed to read from %s with error: %w", i.arg, err)
}
return nil
}
lp.OnWriteComplete = func(msg_id loop.IdType) error {
if waiting_for_write == msg_id {
return write_chunk()
}
return nil
}
lp.OnEscapeCode = func(etype loop.EscapeCodeType, data []byte) (err error) {
metadata, _, err := parse_escape_code(etype, data)
if err != nil {
return err
}
if metadata != nil && metadata["type"] == "write" {
switch metadata["status"] {
case "DONE":
lp.Quit(0)
case "EIO":
return fmt.Errorf("Could not write to clipboard an I/O error occurred while the terminal was processing the data")
case "EINVAL":
return fmt.Errorf("Could not write to clipboard base64 encoding invalid")
case "ENOSYS":
return fmt.Errorf("Could not write to primary selection as the system does not support it")
case "EPERM":
return fmt.Errorf("Could not write to clipboard as permission was denied")
case "EBUSY":
return fmt.Errorf("Could not write to clipboard, a temporary error occurred, try again later.")
default:
return fmt.Errorf("Could not write to clipboard unknowns status returned from terminal: %#v", metadata["status"])
}
}
return
}
esc_count := 0
lp.OnKeyEvent = func(event *loop.KeyEvent) error {
if event.MatchesPressOrRepeat("ctrl+c") || event.MatchesPressOrRepeat("esc") {
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
}
return
}
func run_set_loop(opts *Options, args []string) (err error) {
inputs := make([]*Input, len(args))
to_process := make([]*Input, len(args))
defer func() {
for _, i := range inputs {
if i.src != nil {
i.src.Close()
}
}
}()
for i, arg := range args {
f, err := os.Open(arg)
if err != nil {
return fmt.Errorf("Failed to open %s with error: %w", arg, err)
}
inputs[i] = &Input{arg: arg, src: f, ext: filepath.Ext(arg), is_stream: arg == "/dev/stdin"}
if i < len(opts.Mime) {
inputs[i].mime_type = opts.Mime[i]
} else if inputs[i].is_stream {
inputs[i].mime_type = "text/plain"
} else if inputs[i].ext != "" {
inputs[i].mime_type = utils.GuessMimeType(inputs[i].arg)
}
if inputs[i].mime_type == "" {
return fmt.Errorf("Could not guess MIME type for %s use the --mime option to specify a MIME type", arg)
}
to_process[i] = inputs[i]
}
return write_loop(to_process, opts)
}