keybind.ts 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. import { isDeepEqual } from "remeda"
  2. export namespace Keybind {
  3. export type Info = {
  4. ctrl: boolean
  5. meta: boolean
  6. shift: boolean
  7. leader: boolean
  8. name: string
  9. }
  10. export function match(a: Info, b: Info): boolean {
  11. return isDeepEqual(a, b)
  12. }
  13. export function toString(info: Info): string {
  14. const parts: string[] = []
  15. if (info.ctrl) parts.push("ctrl")
  16. if (info.meta) parts.push("alt")
  17. if (info.shift) parts.push("shift")
  18. if (info.name) {
  19. if (info.name === "delete") parts.push("del")
  20. else parts.push(info.name)
  21. }
  22. let result = parts.join("+")
  23. if (info.leader) {
  24. result = result ? `<leader> ${result}` : `<leader>`
  25. }
  26. return result
  27. }
  28. export function parse(key: string): Info[] {
  29. if (key === "none") return []
  30. return key.split(",").map((combo) => {
  31. // Handle <leader> syntax by replacing with leader+
  32. const normalized = combo.replace(/<leader>/g, "leader+")
  33. const parts = normalized.toLowerCase().split("+")
  34. const info: Info = {
  35. ctrl: false,
  36. meta: false,
  37. shift: false,
  38. leader: false,
  39. name: "",
  40. }
  41. for (const part of parts) {
  42. switch (part) {
  43. case "ctrl":
  44. info.ctrl = true
  45. break
  46. case "alt":
  47. case "meta":
  48. case "option":
  49. info.meta = true
  50. break
  51. case "shift":
  52. info.shift = true
  53. break
  54. case "leader":
  55. info.leader = true
  56. break
  57. case "esc":
  58. info.name = "escape"
  59. break
  60. default:
  61. info.name = part
  62. break
  63. }
  64. }
  65. return info
  66. })
  67. }
  68. }