utils.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package config
  3. import (
  4. "fmt"
  5. "kitty/tools/tui/loop"
  6. "kitty/tools/utils"
  7. "regexp"
  8. "slices"
  9. "strconv"
  10. "strings"
  11. "sync"
  12. )
  13. var _ = fmt.Print
  14. func ParseStrDict(val, record_sep, field_sep string) (map[string]string, error) {
  15. ans := make(map[string]string)
  16. for _, record := range strings.Split(val, record_sep) {
  17. key, val, found := strings.Cut(record, field_sep)
  18. if found {
  19. ans[key] = val
  20. }
  21. }
  22. return ans, nil
  23. }
  24. func PositiveFloat(val string) (ans float64, err error) {
  25. ans, err = strconv.ParseFloat(val, 64)
  26. if err == nil {
  27. ans = max(0, ans)
  28. }
  29. return
  30. }
  31. func UnitFloat(val string) (ans float64, err error) {
  32. ans, err = strconv.ParseFloat(val, 64)
  33. if err == nil {
  34. ans = max(0, min(ans, 1))
  35. }
  36. return
  37. }
  38. func StringLiteral(val string) (string, error) {
  39. ans := strings.Builder{}
  40. ans.Grow(len(val))
  41. var buf [8]rune
  42. bufcount := 0
  43. buflimit := 0
  44. var prefix rune
  45. type State int
  46. const (
  47. normal State = iota
  48. backslash
  49. octal
  50. hex
  51. )
  52. var state State
  53. decode := func(base int) {
  54. text := string(buf[:bufcount])
  55. num, _ := strconv.ParseUint(text, base, 32)
  56. ans.WriteRune(rune(num))
  57. state = normal
  58. bufcount = 0
  59. buflimit = 0
  60. prefix = 0
  61. }
  62. write_invalid_buf := func() {
  63. ans.WriteByte('\\')
  64. ans.WriteRune(prefix)
  65. for _, r := range buf[:bufcount] {
  66. ans.WriteRune(r)
  67. }
  68. state = normal
  69. bufcount = 0
  70. buflimit = 0
  71. prefix = 0
  72. }
  73. var dispatch_ch_recurse func(rune)
  74. dispatch_ch := func(ch rune) {
  75. switch state {
  76. case normal:
  77. switch ch {
  78. case '\\':
  79. state = backslash
  80. default:
  81. ans.WriteRune(ch)
  82. }
  83. case octal:
  84. switch ch {
  85. case '0', '1', '2', '3', '4', '5', '6', '7':
  86. if bufcount >= buflimit {
  87. decode(8)
  88. dispatch_ch_recurse(ch)
  89. } else {
  90. buf[bufcount] = ch
  91. bufcount++
  92. }
  93. default:
  94. decode(8)
  95. dispatch_ch_recurse(ch)
  96. }
  97. case hex:
  98. switch ch {
  99. case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'A', 'b', 'B', 'c', 'C', 'd', 'D', 'e', 'E', 'f', 'F':
  100. buf[bufcount] = ch
  101. bufcount++
  102. if bufcount >= buflimit {
  103. decode(16)
  104. }
  105. default:
  106. write_invalid_buf()
  107. dispatch_ch_recurse(ch)
  108. }
  109. case backslash:
  110. switch ch {
  111. case '\n':
  112. case '\\':
  113. ans.WriteRune('\\')
  114. state = normal
  115. case '\'', '"':
  116. ans.WriteRune(ch)
  117. state = normal
  118. case 'a':
  119. ans.WriteRune('\a')
  120. state = normal
  121. case 'b':
  122. ans.WriteRune('\b')
  123. state = normal
  124. case 'f':
  125. ans.WriteRune('\f')
  126. state = normal
  127. case 'n':
  128. ans.WriteRune('\n')
  129. state = normal
  130. case 'r':
  131. ans.WriteRune('\r')
  132. state = normal
  133. case 't':
  134. ans.WriteRune('\t')
  135. state = normal
  136. case 'v':
  137. ans.WriteRune('\v')
  138. state = normal
  139. case '0', '1', '2', '3', '4', '5', '6', '7':
  140. buf[0] = ch
  141. bufcount = 1
  142. buflimit = 3
  143. state = octal
  144. case 'x':
  145. bufcount = 0
  146. buflimit = 2
  147. state = hex
  148. prefix = ch
  149. case 'u':
  150. bufcount = 0
  151. buflimit = 4
  152. state = hex
  153. prefix = ch
  154. case 'U':
  155. bufcount = 0
  156. buflimit = 8
  157. state = hex
  158. prefix = ch
  159. default:
  160. ans.WriteByte('\\')
  161. ans.WriteRune(ch)
  162. state = normal
  163. }
  164. }
  165. }
  166. dispatch_ch_recurse = dispatch_ch
  167. for _, ch := range val {
  168. dispatch_ch(ch)
  169. }
  170. switch state {
  171. case octal:
  172. decode(8)
  173. case hex:
  174. write_invalid_buf()
  175. case backslash:
  176. ans.WriteRune('\\')
  177. }
  178. return ans.String(), nil
  179. }
  180. var ModMap = sync.OnceValue(func() map[string]string {
  181. return map[string]string{
  182. "shift": "shift",
  183. "⇧": "shift",
  184. "alt": "alt",
  185. "option": "alt",
  186. "opt": "alt",
  187. "⌥": "alt",
  188. "super": "super",
  189. "command": "super",
  190. "cmd": "super",
  191. "⌘": "super",
  192. "control": "ctrl",
  193. "ctrl": "ctrl",
  194. "⌃": "ctrl",
  195. "hyper": "hyper",
  196. "meta": "meta",
  197. "num_lock": "num_lock",
  198. "caps_lock": "caps_lock",
  199. }
  200. })
  201. var ShortcutSpecPat = sync.OnceValue(func() *regexp.Regexp {
  202. return regexp.MustCompile(`([^+])>`)
  203. })
  204. func NormalizeShortcut(spec string) string {
  205. parts := strings.Split(strings.ToLower(spec), "+")
  206. key := parts[len(parts)-1]
  207. if len(parts) == 1 {
  208. return key
  209. }
  210. mods := parts[:len(parts)-1]
  211. mmap := ModMap()
  212. mods = utils.Map(func(x string) string {
  213. ans := mmap[x]
  214. if ans == "" {
  215. ans = x
  216. }
  217. return ans
  218. }, mods)
  219. slices.Sort(mods)
  220. return strings.Join(mods, "+") + "+" + key
  221. }
  222. func NormalizeShortcuts(spec string) []string {
  223. if strings.HasSuffix(spec, "+") {
  224. spec = spec[:len(spec)-1] + "plus"
  225. }
  226. spec = strings.ReplaceAll(spec, "++", "+plus")
  227. spec = ShortcutSpecPat().ReplaceAllString(spec, "$1\x00")
  228. return utils.Map(NormalizeShortcut, strings.Split(spec, "\x00"))
  229. }
  230. type KeyAction struct {
  231. Normalized_keys []string
  232. Name string
  233. Args string
  234. }
  235. func (self *KeyAction) String() string {
  236. return fmt.Sprintf("map %#v %#v %#v\n", strings.Join(self.Normalized_keys, ">"), self.Name, self.Args)
  237. }
  238. func ParseMap(val string) (*KeyAction, error) {
  239. spec, action, found := strings.Cut(val, " ")
  240. if !found {
  241. return nil, fmt.Errorf("No action specified for shortcut %s", val)
  242. }
  243. action = strings.TrimSpace(action)
  244. action_name, action_args, _ := strings.Cut(action, " ")
  245. action_args = strings.TrimSpace(action_args)
  246. return &KeyAction{Name: action_name, Args: action_args, Normalized_keys: NormalizeShortcuts(spec)}, nil
  247. }
  248. type ShortcutTracker struct {
  249. partial_matches []*KeyAction
  250. partial_num_consumed int
  251. }
  252. func (self *ShortcutTracker) Match(ev *loop.KeyEvent, all_actions []*KeyAction) *KeyAction {
  253. if self.partial_num_consumed > 0 {
  254. ev.Handled = true
  255. self.partial_matches = utils.Filter(self.partial_matches, func(ac *KeyAction) bool {
  256. return self.partial_num_consumed < len(ac.Normalized_keys) && ev.MatchesPressOrRepeat(ac.Normalized_keys[self.partial_num_consumed])
  257. })
  258. if len(self.partial_matches) == 0 {
  259. self.partial_num_consumed = 0
  260. return nil
  261. }
  262. } else {
  263. self.partial_matches = utils.Filter(all_actions, func(ac *KeyAction) bool {
  264. return ev.MatchesPressOrRepeat(ac.Normalized_keys[0])
  265. })
  266. if len(self.partial_matches) == 0 {
  267. return nil
  268. }
  269. ev.Handled = true
  270. }
  271. self.partial_num_consumed++
  272. for _, x := range self.partial_matches {
  273. if self.partial_num_consumed >= len(x.Normalized_keys) {
  274. self.partial_num_consumed = 0
  275. return x
  276. }
  277. }
  278. return nil
  279. }
  280. func ResolveShortcuts(actions []*KeyAction) []*KeyAction {
  281. action_map := make(map[string]*KeyAction, len(actions))
  282. for _, ac := range actions {
  283. key := strings.Join(ac.Normalized_keys, "\x00")
  284. if ac.Name == "no_op" || ac.Name == "no-op" {
  285. delete(action_map, key)
  286. } else {
  287. action_map[key] = ac
  288. }
  289. }
  290. return utils.Values(action_map)
  291. }