123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312 |
- // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
- package config
- import (
- "fmt"
- "kitty/tools/tui/loop"
- "kitty/tools/utils"
- "regexp"
- "slices"
- "strconv"
- "strings"
- "sync"
- )
- var _ = fmt.Print
- func ParseStrDict(val, record_sep, field_sep string) (map[string]string, error) {
- ans := make(map[string]string)
- for _, record := range strings.Split(val, record_sep) {
- key, val, found := strings.Cut(record, field_sep)
- if found {
- ans[key] = val
- }
- }
- return ans, nil
- }
- func PositiveFloat(val string) (ans float64, err error) {
- ans, err = strconv.ParseFloat(val, 64)
- if err == nil {
- ans = max(0, ans)
- }
- return
- }
- func UnitFloat(val string) (ans float64, err error) {
- ans, err = strconv.ParseFloat(val, 64)
- if err == nil {
- ans = max(0, min(ans, 1))
- }
- return
- }
- func StringLiteral(val string) (string, error) {
- ans := strings.Builder{}
- ans.Grow(len(val))
- var buf [8]rune
- bufcount := 0
- buflimit := 0
- var prefix rune
- type State int
- const (
- normal State = iota
- backslash
- octal
- hex
- )
- var state State
- decode := func(base int) {
- text := string(buf[:bufcount])
- num, _ := strconv.ParseUint(text, base, 32)
- ans.WriteRune(rune(num))
- state = normal
- bufcount = 0
- buflimit = 0
- prefix = 0
- }
- write_invalid_buf := func() {
- ans.WriteByte('\\')
- ans.WriteRune(prefix)
- for _, r := range buf[:bufcount] {
- ans.WriteRune(r)
- }
- state = normal
- bufcount = 0
- buflimit = 0
- prefix = 0
- }
- var dispatch_ch_recurse func(rune)
- dispatch_ch := func(ch rune) {
- switch state {
- case normal:
- switch ch {
- case '\\':
- state = backslash
- default:
- ans.WriteRune(ch)
- }
- case octal:
- switch ch {
- case '0', '1', '2', '3', '4', '5', '6', '7':
- if bufcount >= buflimit {
- decode(8)
- dispatch_ch_recurse(ch)
- } else {
- buf[bufcount] = ch
- bufcount++
- }
- default:
- decode(8)
- dispatch_ch_recurse(ch)
- }
- case hex:
- switch ch {
- case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'A', 'b', 'B', 'c', 'C', 'd', 'D', 'e', 'E', 'f', 'F':
- buf[bufcount] = ch
- bufcount++
- if bufcount >= buflimit {
- decode(16)
- }
- default:
- write_invalid_buf()
- dispatch_ch_recurse(ch)
- }
- case backslash:
- switch ch {
- case '\n':
- case '\\':
- ans.WriteRune('\\')
- state = normal
- case '\'', '"':
- ans.WriteRune(ch)
- state = normal
- case 'a':
- ans.WriteRune('\a')
- state = normal
- case 'b':
- ans.WriteRune('\b')
- state = normal
- case 'f':
- ans.WriteRune('\f')
- state = normal
- case 'n':
- ans.WriteRune('\n')
- state = normal
- case 'r':
- ans.WriteRune('\r')
- state = normal
- case 't':
- ans.WriteRune('\t')
- state = normal
- case 'v':
- ans.WriteRune('\v')
- state = normal
- case '0', '1', '2', '3', '4', '5', '6', '7':
- buf[0] = ch
- bufcount = 1
- buflimit = 3
- state = octal
- case 'x':
- bufcount = 0
- buflimit = 2
- state = hex
- prefix = ch
- case 'u':
- bufcount = 0
- buflimit = 4
- state = hex
- prefix = ch
- case 'U':
- bufcount = 0
- buflimit = 8
- state = hex
- prefix = ch
- default:
- ans.WriteByte('\\')
- ans.WriteRune(ch)
- state = normal
- }
- }
- }
- dispatch_ch_recurse = dispatch_ch
- for _, ch := range val {
- dispatch_ch(ch)
- }
- switch state {
- case octal:
- decode(8)
- case hex:
- write_invalid_buf()
- case backslash:
- ans.WriteRune('\\')
- }
- return ans.String(), nil
- }
- var ModMap = sync.OnceValue(func() map[string]string {
- return map[string]string{
- "shift": "shift",
- "⇧": "shift",
- "alt": "alt",
- "option": "alt",
- "opt": "alt",
- "⌥": "alt",
- "super": "super",
- "command": "super",
- "cmd": "super",
- "⌘": "super",
- "control": "ctrl",
- "ctrl": "ctrl",
- "⌃": "ctrl",
- "hyper": "hyper",
- "meta": "meta",
- "num_lock": "num_lock",
- "caps_lock": "caps_lock",
- }
- })
- var ShortcutSpecPat = sync.OnceValue(func() *regexp.Regexp {
- return regexp.MustCompile(`([^+])>`)
- })
- func NormalizeShortcut(spec string) string {
- parts := strings.Split(strings.ToLower(spec), "+")
- key := parts[len(parts)-1]
- if len(parts) == 1 {
- return key
- }
- mods := parts[:len(parts)-1]
- mmap := ModMap()
- mods = utils.Map(func(x string) string {
- ans := mmap[x]
- if ans == "" {
- ans = x
- }
- return ans
- }, mods)
- slices.Sort(mods)
- return strings.Join(mods, "+") + "+" + key
- }
- func NormalizeShortcuts(spec string) []string {
- if strings.HasSuffix(spec, "+") {
- spec = spec[:len(spec)-1] + "plus"
- }
- spec = strings.ReplaceAll(spec, "++", "+plus")
- spec = ShortcutSpecPat().ReplaceAllString(spec, "$1\x00")
- return utils.Map(NormalizeShortcut, strings.Split(spec, "\x00"))
- }
- type KeyAction struct {
- Normalized_keys []string
- Name string
- Args string
- }
- func (self *KeyAction) String() string {
- return fmt.Sprintf("map %#v %#v %#v\n", strings.Join(self.Normalized_keys, ">"), self.Name, self.Args)
- }
- func ParseMap(val string) (*KeyAction, error) {
- spec, action, found := strings.Cut(val, " ")
- if !found {
- return nil, fmt.Errorf("No action specified for shortcut %s", val)
- }
- action = strings.TrimSpace(action)
- action_name, action_args, _ := strings.Cut(action, " ")
- action_args = strings.TrimSpace(action_args)
- return &KeyAction{Name: action_name, Args: action_args, Normalized_keys: NormalizeShortcuts(spec)}, nil
- }
- type ShortcutTracker struct {
- partial_matches []*KeyAction
- partial_num_consumed int
- }
- func (self *ShortcutTracker) Match(ev *loop.KeyEvent, all_actions []*KeyAction) *KeyAction {
- if self.partial_num_consumed > 0 {
- ev.Handled = true
- self.partial_matches = utils.Filter(self.partial_matches, func(ac *KeyAction) bool {
- return self.partial_num_consumed < len(ac.Normalized_keys) && ev.MatchesPressOrRepeat(ac.Normalized_keys[self.partial_num_consumed])
- })
- if len(self.partial_matches) == 0 {
- self.partial_num_consumed = 0
- return nil
- }
- } else {
- self.partial_matches = utils.Filter(all_actions, func(ac *KeyAction) bool {
- return ev.MatchesPressOrRepeat(ac.Normalized_keys[0])
- })
- if len(self.partial_matches) == 0 {
- return nil
- }
- ev.Handled = true
- }
- self.partial_num_consumed++
- for _, x := range self.partial_matches {
- if self.partial_num_consumed >= len(x.Normalized_keys) {
- self.partial_num_consumed = 0
- return x
- }
- }
- return nil
- }
- func ResolveShortcuts(actions []*KeyAction) []*KeyAction {
- action_map := make(map[string]*KeyAction, len(actions))
- for _, ac := range actions {
- key := strings.Join(ac.Normalized_keys, "\x00")
- if ac.Name == "no_op" || ac.Name == "no-op" {
- delete(action_map, key)
- } else {
- action_map[key] = ac
- }
- }
- return utils.Values(action_map)
- }
|