more work on porting rc command parsing to Go
This commit is contained in:
parent
79c8862d4c
commit
6f4968305a
@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
from typing import List
|
||||
|
||||
from kitty.conf.generate import write_output
|
||||
@ -11,14 +12,25 @@ from kitty.conf.generate import write_output
|
||||
def patch_color_list(path: str, colors: List[str], name: str, spc: str = ' ') -> None:
|
||||
with open(path, 'r+') as f:
|
||||
raw = f.read()
|
||||
nraw = re.sub(
|
||||
fr'(# {name}_COLORS_START).+?(\s+# {name}_COLORS_END)',
|
||||
r'\1' + f'\n{spc}' + f'\n{spc}'.join(map(lambda x: f'{x!r},', sorted(colors))) + r'\2',
|
||||
raw, flags=re.DOTALL | re.MULTILINE)
|
||||
colors = sorted(colors)
|
||||
if path.endswith('.go'):
|
||||
spc = '\t'
|
||||
nraw = re.sub(
|
||||
fr'(// {name}_COLORS_START).+?(\s+// {name}_COLORS_END)',
|
||||
r'\1' + f'\n{spc}' + f'\n{spc}'.join(map(lambda x: f'"{x}":true,', colors)) + r'\2',
|
||||
raw, flags=re.DOTALL | re.MULTILINE)
|
||||
else:
|
||||
nraw = re.sub(
|
||||
fr'(# {name}_COLORS_START).+?(\s+# {name}_COLORS_END)',
|
||||
r'\1' + f'\n{spc}' + f'\n{spc}'.join(map(lambda x: f'{x!r},', colors)) + r'\2',
|
||||
raw, flags=re.DOTALL | re.MULTILINE)
|
||||
if nraw != raw:
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write(nraw)
|
||||
f.flush()
|
||||
if path.endswith('.go'):
|
||||
subprocess.check_call(['gofmt', '-w', path])
|
||||
|
||||
|
||||
def main() -> None:
|
||||
@ -34,6 +46,7 @@ def main() -> None:
|
||||
elif opt.parser_func.__name__ in ('to_color', 'titlebar_color', 'macos_titlebar_color'):
|
||||
all_colors.append(opt.name)
|
||||
patch_color_list('kitty/rc/set_colors.py', nullable_colors, 'NULLABLE')
|
||||
patch_color_list('tools/cmd/at/set_colors.go', nullable_colors, 'NULLABLE')
|
||||
patch_color_list('kittens/themes/collection.py', all_colors, 'ALL', ' ' * 8)
|
||||
|
||||
from kittens.diff.options.definition import definition as kd
|
||||
|
||||
@ -6,7 +6,7 @@ import json
|
||||
import os
|
||||
import sys
|
||||
from contextlib import contextmanager, suppress
|
||||
from typing import Dict, Iterator, List, Tuple, Union
|
||||
from typing import Dict, Iterator, List, Set, Tuple, Union
|
||||
|
||||
import kitty.constants as kc
|
||||
from kittens.tui.operations import Mode
|
||||
@ -123,19 +123,21 @@ def build_go_code(name: str, cmd: RemoteCommand, seq: OptionSpecSeq, template: s
|
||||
field_types[f.field] = f.field_type
|
||||
jd.append(f.go_declaration())
|
||||
jc: List[str] = []
|
||||
handled_fields: Set[str] = set()
|
||||
try:
|
||||
jc.extend(cmd.args.as_go_code(name, field_types, handled_fields))
|
||||
except TypeError:
|
||||
print(f'Cant parse args for cmd: {name}', file=sys.stderr)
|
||||
|
||||
for field in json_fields:
|
||||
if field.field in option_map:
|
||||
o = option_map[field.field]
|
||||
jc.append(f'payload.{field.struct_field_name} = options_{name}.{o.go_var_name}')
|
||||
elif field.field in handled_fields:
|
||||
pass
|
||||
else:
|
||||
print(f'Cant map field: {field.field} for cmd: {name}', file=sys.stderr)
|
||||
continue
|
||||
try:
|
||||
jc.extend(cmd.args.as_go_code(name, field_types))
|
||||
except TypeError:
|
||||
print(f'Cant parse args for cmd: {name}', file=sys.stderr)
|
||||
|
||||
print('TODO: test set_window_logo, send_text, env, scroll_window', file=sys.stderr)
|
||||
print('TODO: test set_window_logo, set_window_background, set_font_size, send_text, env, scroll_window', file=sys.stderr)
|
||||
|
||||
argspec = cmd.args.spec
|
||||
if argspec:
|
||||
|
||||
@ -172,6 +172,7 @@ class ArgsHandling:
|
||||
minimum_count: int = -1
|
||||
first_rest: Optional[Tuple[str, str]] = None
|
||||
special_parse: str = ''
|
||||
args_choices: Optional[Callable[[], Iterable[str]]] = None
|
||||
|
||||
@property
|
||||
def args_count(self) -> Optional[int]:
|
||||
@ -193,6 +194,11 @@ class ArgsHandling:
|
||||
yield '}'
|
||||
if self.minimum_count > -1:
|
||||
yield f'if len(args) < {self.minimum_count} {{ return fmt.Errorf("%s", Must specify at least {self.minimum_count} arguments to {cmd_name}) }}'
|
||||
if self.args_choices:
|
||||
achoices = tuple(self.args_choices())
|
||||
yield 'achoices := map[string]bool{' + ' '.join(f'"{x}":true,' for x in achoices) + '}'
|
||||
yield 'for _, a := range args {'
|
||||
yield 'if !achoices[a] { return fmt.Errorf("Not a valid choice: %s. Allowed values are: %s", a, "' + ', '.join(achoices) + '") }'
|
||||
if self.json_field:
|
||||
jf = self.json_field
|
||||
dest = f'payload.{jf.capitalize()}'
|
||||
@ -207,6 +213,10 @@ class ArgsHandling:
|
||||
if self.special_parse:
|
||||
if self.special_parse.startswith('!'):
|
||||
yield f'io_data.multiple_payload_generator, err = {self.special_parse[1:]}'
|
||||
elif self.special_parse.startswith('+'):
|
||||
fields, sp = self.special_parse[1:].split(':', 1)
|
||||
handled_fields.update(set(fields.split(',')))
|
||||
yield f'err = {sp}'
|
||||
else:
|
||||
yield f'{dest}, err = {self.special_parse}'
|
||||
yield 'if err != nil { return err }'
|
||||
|
||||
@ -101,7 +101,7 @@ Path to a file whose contents you wish to send. Note that in this case the file
|
||||
are sent as is, not interpreted for escapes.
|
||||
'''
|
||||
no_response = True
|
||||
argspec = '[TEXT TO SEND]'
|
||||
args = RemoteCommand.Args(spec='[TEXT TO SEND]', json_field='data', special_parse='+session_id:parse_send_text(io_data, args)')
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
limit = 1024
|
||||
|
||||
@ -63,9 +63,8 @@ default=false
|
||||
Don't wait for a response from kitty. This means that even if setting the background image
|
||||
failed, the command will exit with a success code.
|
||||
''' + '\n\n' + MATCH_WINDOW_OPTION
|
||||
argspec = 'PATH_TO_PNG_IMAGE'
|
||||
args_count = 1
|
||||
args_completion = {'files': ('PNG Images', ('*.png',))}
|
||||
args = RemoteCommand.Args(spec='PATH_TO_PNG_IMAGE', count=1, special_parse='!read_window_logo(args[0])', completion={
|
||||
'files': ('PNG Images', ('*.png',))})
|
||||
images_in_flight: Dict[str, IO[bytes]] = {}
|
||||
is_asynchronous = True
|
||||
|
||||
|
||||
@ -37,8 +37,7 @@ By default, background opacity are only changed for the currently active window.
|
||||
cause background opacity to be changed in all windows.
|
||||
|
||||
''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t')
|
||||
argspec = 'OPACITY'
|
||||
args_count = 1
|
||||
args = RemoteCommand.Args(spec='OPACITY', count=1, json_field='opacity')
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
opacity = max(0.1, min(float(args[0]), 1.0))
|
||||
|
||||
@ -89,7 +89,8 @@ type=bool-set
|
||||
Restore all colors to the values they had at kitty startup. Note that if you specify
|
||||
this option, any color arguments are ignored and :option:`kitty @ set-colors --configured` and :option:`kitty @ set-colors --all` are implied.
|
||||
''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t')
|
||||
args = RemoteCommand.Args(spec='COLOR_OR_FILE ...', completion={'files': ('CONF files', ('*.conf',))})
|
||||
args = RemoteCommand.Args(spec='COLOR_OR_FILE ...', json_field='colors', special_parse='parse_colors_and_files(args)', completion={
|
||||
'files': ('CONF files', ('*.conf',))})
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
final_colors: Dict[str, Optional[int]] = {}
|
||||
|
||||
@ -41,8 +41,8 @@ type=bool-set
|
||||
Change the default enabled layout value so that the new value takes effect for all newly created tabs
|
||||
as well.
|
||||
'''
|
||||
argspec = 'LAYOUTS'
|
||||
args_completion = {'names': ('Layouts', layout_names)}
|
||||
args = RemoteCommand.Args(
|
||||
spec='LAYOUT ...', minimum_count=1, json_field='layouts', completion={'names': ('Layouts', layout_names)}, args_choices=layout_names)
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
if len(args) < 1:
|
||||
|
||||
@ -28,8 +28,7 @@ class SetFontSize(RemoteCommand):
|
||||
' with a :code:`+` or :code:`-` increments the font size by the specified'
|
||||
' amount.'
|
||||
)
|
||||
argspec = 'FONT_SIZE'
|
||||
args_count = 1
|
||||
args = RemoteCommand.Args(spec='FONT_SIZE', count=1, special_parse='+increment_op:parse_set_font_size(args[0], io_data)', json_field='size')
|
||||
options_spec = '''\
|
||||
--all -a
|
||||
type=bool-set
|
||||
|
||||
@ -95,7 +95,7 @@ type=bool-set
|
||||
Also change the configured paddings and margins (i.e. the settings kitty will use for new
|
||||
windows).
|
||||
''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t')
|
||||
argspec = 'MARGIN_OR_PADDING ...'
|
||||
args = RemoteCommand.Args(spec='MARGIN_OR_PADDING ...', minimum_count=1, json_field='settings', special_parse='parse_set_spacing(args)')
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
if not args:
|
||||
|
||||
@ -57,7 +57,7 @@ the keyword NONE to revert to using the default colors.
|
||||
type=bool-set
|
||||
Close the tab this command is run in, rather than the active tab.
|
||||
'''
|
||||
argspec = 'COLORS'
|
||||
args = RemoteCommand.Args(spec='COLORS', json_field='colors', minimum_count=1, special_parse='parse_tab_colors(args)')
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
try:
|
||||
|
||||
@ -28,7 +28,7 @@ class SetTabTitle(RemoteCommand):
|
||||
' title of the currently active window in the tab is used.'
|
||||
)
|
||||
options_spec = MATCH_TAB_OPTION
|
||||
argspec = 'TITLE ...'
|
||||
args = RemoteCommand.Args(spec='TITLE ...', json_field='title')
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
return {'title': ' '.join(args), 'match': opts.match}
|
||||
|
||||
60
tools/cmd/at/send_text.go
Normal file
60
tools/cmd/at/send_text.go
Normal file
@ -0,0 +1,60 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package at
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type generator_function func(io_data *rc_io_data) (bool, error)
|
||||
|
||||
func parse_send_text(io_data *rc_io_data, args []string) error {
|
||||
generators := make([]generator_function, 0, 1)
|
||||
var payload send_text_json_type = io_data.rc.Payload.(send_text_json_type)
|
||||
|
||||
if len(args) > 0 {
|
||||
text := strings.Join(args, " ")
|
||||
text_gen := func(io_data *rc_io_data) (bool, error) {
|
||||
payload.Data = "text:" + text[:2048]
|
||||
text = text[2048:]
|
||||
return len(text) == 0, nil
|
||||
}
|
||||
generators = append(generators, text_gen)
|
||||
}
|
||||
|
||||
if options_send_text.from_file != "" {
|
||||
f, err := os.Open(options_send_text.from_file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chunk := make([]byte, 2048)
|
||||
file_gen := func(io_data *rc_io_data) (bool, error) {
|
||||
n, err := f.Read(chunk)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
return false, err
|
||||
}
|
||||
payload.Data = "base64:" + base64.StdEncoding.EncodeToString(chunk[:n])
|
||||
return n == 0, nil
|
||||
}
|
||||
generators = append(generators, file_gen)
|
||||
}
|
||||
|
||||
io_data.multiple_payload_generator = func(io_data *rc_io_data) (bool, error) {
|
||||
if len(generators) == 0 {
|
||||
payload.Data = "text:"
|
||||
return true, nil
|
||||
}
|
||||
finished, err := generators[0](io_data)
|
||||
if finished {
|
||||
generators = generators[1:]
|
||||
finished = len(generators) == 0
|
||||
}
|
||||
return finished, err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
74
tools/cmd/at/set_colors.go
Normal file
74
tools/cmd/at/set_colors.go
Normal file
@ -0,0 +1,74 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package at
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"kitty/tools/utils"
|
||||
"kitty/tools/utils/style"
|
||||
)
|
||||
|
||||
var nullable_colors = map[string]bool{
|
||||
// generated by gen-config.py do not edit
|
||||
// NULLABLE_COLORS_START
|
||||
"active_border_color": true,
|
||||
"cursor": true,
|
||||
"cursor_text_color": true,
|
||||
"selection_background": true,
|
||||
"selection_foreground": true,
|
||||
"tab_bar_background": true,
|
||||
"tab_bar_margin_color": true,
|
||||
"visual_bell_color": true,
|
||||
// NULLABLE_COLORS_END
|
||||
}
|
||||
|
||||
func set_color_in_color_map(key, val string, ans map[string]interface{}, check_nullable, skip_nullable bool) error {
|
||||
if val == "none" {
|
||||
if check_nullable && !nullable_colors[key] {
|
||||
if skip_nullable {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("The color %s cannot be set to none", key)
|
||||
}
|
||||
ans[key] = nil
|
||||
} else {
|
||||
col, err := style.ParseColor(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s is not a valid color", val)
|
||||
}
|
||||
ans[key] = col.AsRGB()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parse_colors_and_files(args []string) (map[string]interface{}, error) {
|
||||
ans := make(map[string]interface{}, len(args))
|
||||
for _, arg := range args {
|
||||
key, val, found := utils.Cut(strings.ToLower(arg), "=")
|
||||
if found {
|
||||
err := set_color_in_color_map(key, val, ans, true, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
path := utils.Expanduser(arg)
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
key, val, found := utils.Cut(scanner.Text(), " ")
|
||||
if found {
|
||||
set_color_in_color_map(strings.ToLower(key), strings.ToLower(strings.TrimSpace(val)), ans, true, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ans, nil
|
||||
}
|
||||
20
tools/cmd/at/set_font_size.go
Normal file
20
tools/cmd/at/set_font_size.go
Normal file
@ -0,0 +1,20 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package at
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func parse_set_font_size(arg string, io_data *rc_io_data) error {
|
||||
payload := io_data.rc.Payload.(set_font_size_json_type)
|
||||
if len(arg) > 0 && (arg[0] == '+' || arg[0] == '-') {
|
||||
payload.Increment_op = arg[:1]
|
||||
}
|
||||
val, err := strconv.ParseFloat(arg, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payload.Size = val
|
||||
return nil
|
||||
}
|
||||
53
tools/cmd/at/set_spacing.go
Normal file
53
tools/cmd/at/set_spacing.go
Normal file
@ -0,0 +1,53 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package at
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"kitty/tools/utils"
|
||||
)
|
||||
|
||||
func parse_set_spacing(args []string) (map[string]interface{}, error) {
|
||||
ans := make(map[string]interface{}, len(args))
|
||||
mapper := make(map[string][]string, 32)
|
||||
types := [2]string{"margin", "padding"}
|
||||
for _, q := range types {
|
||||
mapper[q] = []string{q + "-left", q + "-top", q + "-right", q + "-bottom"}
|
||||
mapper[q+"-h"] = []string{q + "-left", q + "-right"}
|
||||
mapper[q+"-v"] = []string{q + "-top", q + "-bottom"}
|
||||
mapper[q+"-left"] = []string{q + "left"}
|
||||
mapper[q+"-right"] = []string{q + "right"}
|
||||
mapper[q+"-top"] = []string{q + "top"}
|
||||
mapper[q+"-bottom"] = []string{q + "bottom"}
|
||||
}
|
||||
for _, arg := range args {
|
||||
k, v, found := utils.Cut(arg, "=")
|
||||
if !found {
|
||||
return nil, fmt.Errorf("%s is not a valid setting", arg)
|
||||
}
|
||||
k = strings.ToLower(k)
|
||||
v = strings.ToLower(v)
|
||||
which, found := mapper[k]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("%s is not a valid edge specification", k)
|
||||
}
|
||||
if v == "default" {
|
||||
for _, q := range which {
|
||||
ans[q] = nil
|
||||
}
|
||||
} else {
|
||||
val, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s is not a number", v)
|
||||
}
|
||||
for _, q := range which {
|
||||
ans[q] = val
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return ans, nil
|
||||
}
|
||||
30
tools/cmd/at/set_tab_color.go
Normal file
30
tools/cmd/at/set_tab_color.go
Normal file
@ -0,0 +1,30 @@
|
||||
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||
|
||||
package at
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"kitty/tools/utils"
|
||||
)
|
||||
|
||||
var valid_color_names = map[string]bool{"active_fg": true, "active_bg": true, "inactive_fg": true, "inactive_bg": true}
|
||||
|
||||
func parse_tab_colors(args []string) (map[string]interface{}, error) {
|
||||
ans := make(map[string]interface{}, len(args))
|
||||
for _, arg := range args {
|
||||
key, val, found := utils.Cut(strings.ToLower(arg), "=")
|
||||
if !found {
|
||||
return nil, fmt.Errorf("%s is not a valid setting", arg)
|
||||
}
|
||||
if !valid_color_names[key] {
|
||||
return nil, fmt.Errorf("%s is not a valid color name", key)
|
||||
}
|
||||
err := set_color_in_color_map(key, val, ans, false, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ans, nil
|
||||
}
|
||||
@ -49,6 +49,6 @@ func read_window_logo(path string) (func(io_data *rc_io_data) (bool, error), err
|
||||
return false, err
|
||||
}
|
||||
buf = buf[:n]
|
||||
return n == 0, nil
|
||||
return false, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -70,6 +70,10 @@ func (self *RGBA) parse_rgb_strings(r string, g string, b string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *RGBA) AsRGB() uint32 {
|
||||
return uint32(self.Blue) | (uint32(self.Green) << 8) | (uint32(self.Red) << 16)
|
||||
}
|
||||
|
||||
type color_type struct {
|
||||
is_numbered bool
|
||||
val RGBA
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user