Implement MIME aliases for clipboard

This commit is contained in:
Kovid Goyal 2022-12-03 12:20:03 +05:30
parent a622a149f6
commit 6422b323c6
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 62 additions and 7 deletions

View File

@ -25,6 +25,16 @@ can be detected. If more than one file is specified, this option should be speci
times, once for each specified file. times, once for each specified file.
--alias -a
type=list
Specify aliases for MIME types. Aliased MIME types are considered equivalent.
When copying to clipboard both the original and alias are made available on the
clipboard. When copying from clipboard if the original is not found, the alias
is used, as a fallback. Can be specified multiple times to create multiple
aliases. For example: :code:`--alias text/plain=text/x-rst` makes :code:`text/plain` an alias
of :code:`text/rst`. Aliases are not used in filter mode.
--wait-for-completion --wait-for-completion
type=bool-set type=bool-set
Wait till the copy to clipboard is complete before exiting. Useful if running Wait till the copy to clipboard is complete before exiting. Useful if running

View File

@ -239,6 +239,7 @@ class WriteRequest:
self.currently_writing_mime = '' self.currently_writing_mime = ''
self.current_leftover_bytes = memoryview(b'') self.current_leftover_bytes = memoryview(b'')
self.max_size = (get_options().clipboard_max_size * 1024 * 1024) if max_size < 0 else max_size self.max_size = (get_options().clipboard_max_size * 1024 * 1024) if max_size < 0 else max_size
self.aliases: Dict[str, str] = {}
self.commited = False self.commited = False
def encode_response(self, status: str = 'OK') -> bytes: def encode_response(self, status: str = 'OK') -> bytes:
@ -254,6 +255,10 @@ class WriteRequest:
self.commited = True self.commited = True
cp = get_boss().primary_selection if self.is_primary_selection else get_boss().clipboard cp = get_boss().primary_selection if self.is_primary_selection else get_boss().clipboard
if cp.enabled: if cp.enabled:
for alias, src in self.aliases.items():
pos = self.mime_map.get(src)
if pos is not None:
self.mime_map[alias] = pos
x = {mime: self.tempfile.create_chunker(pos.start, pos.size) for mime, pos in self.mime_map.items()} x = {mime: self.tempfile.create_chunker(pos.start, pos.size) for mime, pos in self.mime_map.items()}
cp.set_mime(x) cp.set_mime(x)
@ -346,6 +351,13 @@ class ClipboardRequestManager:
protocol_type=ProtocolType.osc_5522, id=sanitize_id(m.get('id', '')) protocol_type=ProtocolType.osc_5522, id=sanitize_id(m.get('id', ''))
) )
self.handle_write_request(self.in_flight_write_request) self.handle_write_request(self.in_flight_write_request)
elif typ == 'walias':
wr = self.in_flight_write_request
mime = m.get('mime', '')
if mime and wr is not None:
aliases = base64.standard_b64decode(epayload).decode('utf-8').split()
for alias in aliases:
wr.aliases[alias] = mime
elif typ == 'wdata': elif typ == 'wdata':
wr = self.in_flight_write_request wr = self.in_flight_write_request
w = get_boss().window_id_map.get(self.window_id) w = get_boss().window_id_map.get(self.window_id)

View File

@ -139,14 +139,22 @@ func (self *Output) commit() {
self.dest = nil self.dest = nil
} }
func (self *Output) assign_mime_type(available_mimes []string) (err error) { func (self *Output) assign_mime_type(available_mimes []string, aliases map[string][]string) (err error) {
if self.mime_type == "." {
self.remote_mime_type = "."
return
}
if utils.Contains(available_mimes, self.mime_type) { if utils.Contains(available_mimes, self.mime_type) {
self.remote_mime_type = self.mime_type self.remote_mime_type = self.mime_type
return return
} }
if self.mime_type == "." { if len(aliases[self.mime_type]) > 0 {
self.remote_mime_type = "." for _, alias := range aliases[self.mime_type] {
return if utils.Contains(available_mimes, alias) {
self.remote_mime_type = alias
return
}
}
} }
if images.EncodableImageTypes[self.mime_type] { if images.EncodableImageTypes[self.mime_type] {
for _, mt := range available_mimes { for _, mt := range available_mimes {
@ -242,6 +250,19 @@ func parse_escape_code(etype loop.EscapeCodeType, data []byte) (metadata map[str
return return
} }
func parse_aliases(raw []string) (map[string][]string, error) {
ans := make(map[string][]string, len(raw))
for _, x := range raw {
k, v, found := utils.Cut(x, "=")
if !found {
return nil, fmt.Errorf("%s is not valid MIME alias specification", x)
}
ans[k] = append(ans[k], v)
ans[v] = append(ans[v], k)
}
return ans, nil
}
func run_get_loop(opts *Options, args []string) (err error) { func run_get_loop(opts *Options, args []string) (err error) {
lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking) lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking)
if err != nil { if err != nil {
@ -253,6 +274,10 @@ func run_get_loop(opts *Options, args []string) (err error) {
requested_mimes := make(map[string]*Output) requested_mimes := make(map[string]*Output)
reading_available_mimes := true reading_available_mimes := true
outputs := make([]*Output, len(args)) outputs := make([]*Output, len(args))
aliases, merr := parse_aliases(opts.Alias)
if merr != nil {
return merr
}
for i, arg := range args { for i, arg := range args {
outputs[i] = &Output{arg: arg, arg_is_stream: arg == "/dev/stdout" || arg == "/dev/stderr", ext: filepath.Ext(arg)} outputs[i] = &Output{arg: arg, arg_is_stream: arg == "/dev/stdout" || arg == "/dev/stderr", ext: filepath.Ext(arg)}
@ -301,7 +326,7 @@ func run_get_loop(opts *Options, args []string) (err error) {
return fmt.Errorf("The clipboard is empty") return fmt.Errorf("The clipboard is empty")
} }
for _, o := range outputs { for _, o := range outputs {
err = o.assign_mime_type(available_mimes) err = o.assign_mime_type(available_mimes, aliases)
if err != nil { if err != nil {
return err return err
} }

View File

@ -8,6 +8,7 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"kitty/tools/tui/loop" "kitty/tools/tui/loop"
"kitty/tools/utils" "kitty/tools/utils"
@ -23,13 +24,17 @@ type Input struct {
mime_type string mime_type string
} }
func write_loop(inputs []*Input) (err error) { func write_loop(inputs []*Input, opts *Options) (err error) {
lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking) lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking)
if err != nil { if err != nil {
return err return err
} }
var waiting_for_write loop.IdType var waiting_for_write loop.IdType
var buf [4096]byte var buf [4096]byte
aliases, aerr := parse_aliases(opts.Alias)
if aerr != nil {
return aerr
}
lp.OnInitialize = func() (string, error) { lp.OnInitialize = func() (string, error) {
waiting_for_write = lp.QueueWriteString(encode(map[string]string{"type": "write"}, "")) waiting_for_write = lp.QueueWriteString(encode(map[string]string{"type": "write"}, ""))
@ -47,6 +52,9 @@ func write_loop(inputs []*Input) (err error) {
} }
if err != nil { if err != nil {
if errors.Is(err, io.EOF) { if errors.Is(err, io.EOF) {
if len(aliases[i.mime_type]) > 0 {
lp.QueueWriteString(encode(map[string]string{"type": "walias", "mime": i.mime_type}, strings.Join(aliases[i.mime_type], " ")))
}
inputs = inputs[1:] inputs = inputs[1:]
if len(inputs) == 0 { if len(inputs) == 0 {
lp.QueueWriteString(encode(map[string]string{"type": "wdata"}, "")) lp.QueueWriteString(encode(map[string]string{"type": "wdata"}, ""))
@ -151,5 +159,5 @@ func run_set_loop(opts *Options, args []string) (err error) {
} }
to_process[i] = inputs[i] to_process[i] = inputs[i]
} }
return write_loop(to_process) return write_loop(to_process, opts)
} }