which-key.lua 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. -- which-key.lua
  2. -- ============================================================================
  3. -- Displays available keybindings when a prefix key is pressed.
  4. -- Similar to Emacs which-key package.
  5. -- ============================================================================
  6. local which_key = {}
  7. -- Configuration
  8. which_key.config = {
  9. delay = 0.5, -- Delay before showing hints (not yet implemented - needs timer)
  10. max_display = 10, -- Max number of hints to show
  11. separator = " | ", -- Separator between hints
  12. show_description = true, -- Show command descriptions
  13. }
  14. -- Parse a key sequence like "C-x C-f" into parts
  15. local function parse_sequence(seq)
  16. local parts = {}
  17. for part in seq:gmatch("%S+") do
  18. table.insert(parts, part)
  19. end
  20. return parts
  21. end
  22. -- Check if sequence starts with prefix
  23. local function starts_with(sequence, prefix)
  24. local seq_parts = parse_sequence(sequence)
  25. local prefix_parts = parse_sequence(prefix)
  26. if #seq_parts < #prefix_parts then
  27. return false
  28. end
  29. for i, part in ipairs(prefix_parts) do
  30. if seq_parts[i] ~= part then
  31. return false
  32. end
  33. end
  34. return true
  35. end
  36. -- Get the next key after the prefix
  37. local function get_next_key(sequence, prefix)
  38. local seq_parts = parse_sequence(sequence)
  39. local prefix_parts = parse_sequence(prefix)
  40. if #seq_parts > #prefix_parts then
  41. return seq_parts[#prefix_parts + 1]
  42. end
  43. return nil
  44. end
  45. -- Get all bindings that start with the given prefix
  46. function which_key.get_bindings_for_prefix(prefix)
  47. local all_bindings = editor:get_all_bindings()
  48. local matching = {}
  49. -- Normalize prefix (remove trailing dash if present)
  50. prefix = prefix:gsub("%-$", "")
  51. for _, binding in ipairs(all_bindings) do
  52. if starts_with(binding.sequence, prefix) and binding.sequence ~= prefix then
  53. local next_key = get_next_key(binding.sequence, prefix)
  54. if next_key then
  55. -- Check if we already have this next_key
  56. local found = false
  57. for _, m in ipairs(matching) do
  58. if m.key == next_key then
  59. found = true
  60. break
  61. end
  62. end
  63. if not found then
  64. -- Determine if this is a prefix or a command
  65. local is_prefix = false
  66. local prefix_with_key = prefix .. " " .. next_key
  67. for _, b in ipairs(all_bindings) do
  68. if starts_with(b.sequence, prefix_with_key) and b.sequence ~= prefix_with_key then
  69. is_prefix = true
  70. break
  71. end
  72. end
  73. table.insert(matching, {
  74. key = next_key,
  75. command = binding.command_name,
  76. description = binding.description,
  77. full_sequence = binding.sequence,
  78. is_prefix = is_prefix,
  79. })
  80. end
  81. end
  82. end
  83. end
  84. -- Sort by key
  85. table.sort(matching, function(a, b)
  86. return a.key < b.key
  87. end)
  88. return matching
  89. end
  90. -- Format hints for display
  91. function which_key.format_hints(bindings, max_width)
  92. max_width = max_width or 80
  93. local hints = {}
  94. for i, binding in ipairs(bindings) do
  95. if i > which_key.config.max_display then
  96. table.insert(hints, "...")
  97. break
  98. end
  99. local hint
  100. if binding.is_prefix then
  101. hint = binding.key .. ":+prefix"
  102. elseif which_key.config.show_description and binding.description ~= "" then
  103. hint = binding.key .. ":" .. binding.description
  104. else
  105. hint = binding.key .. ":" .. binding.command
  106. end
  107. table.insert(hints, hint)
  108. end
  109. -- Join with separator
  110. local result = table.concat(hints, which_key.config.separator)
  111. -- Truncate if too long
  112. if #result > max_width then
  113. result = result:sub(1, max_width - 3) .. "..."
  114. end
  115. return result
  116. end
  117. -- Show which-key hints for current prefix
  118. function which_key.show_hints()
  119. if not editor:is_building_sequence() then
  120. return
  121. end
  122. local current = editor:current_sequence()
  123. -- Remove trailing dash
  124. current = current:gsub("%-$", "")
  125. local bindings = which_key.get_bindings_for_prefix(current)
  126. if #bindings > 0 then
  127. local hints = which_key.format_hints(bindings)
  128. editor:message(current .. "- " .. hints)
  129. end
  130. end
  131. -- Manually show which-key for a prefix
  132. function which_key.show_for_prefix(prefix)
  133. local bindings = which_key.get_bindings_for_prefix(prefix)
  134. if #bindings > 0 then
  135. local hints = which_key.format_hints(bindings)
  136. editor:message(prefix .. " " .. hints)
  137. else
  138. editor:message("No bindings for prefix: " .. prefix)
  139. end
  140. end
  141. -- Register commands
  142. editor:register_command("which-key", "Show available keybindings for prefix", function(args)
  143. if #args > 0 then
  144. which_key.show_for_prefix(args[1])
  145. elseif editor:is_building_sequence() then
  146. which_key.show_hints()
  147. else
  148. -- Show all prefixes
  149. local all_bindings = editor:get_all_bindings()
  150. local prefixes = {}
  151. for _, binding in ipairs(all_bindings) do
  152. local parts = parse_sequence(binding.sequence)
  153. if #parts > 1 then
  154. local prefix = parts[1]
  155. if not prefixes[prefix] then
  156. prefixes[prefix] = 0
  157. end
  158. prefixes[prefix] = prefixes[prefix] + 1
  159. end
  160. end
  161. local hints = {}
  162. for prefix, count in pairs(prefixes) do
  163. table.insert(hints, prefix .. ":+" .. count)
  164. end
  165. table.sort(hints)
  166. editor:message("Prefix keys: " .. table.concat(hints, " | "))
  167. end
  168. return {success = true}
  169. end, {"wk"}, true, "s")
  170. -- Describe key binding
  171. editor:register_command("describe-key", "Describe a key binding", function(args)
  172. if #args == 0 then
  173. return {success = false, message = "Usage: describe-key <key-sequence>"}
  174. end
  175. local key = args[1]
  176. local all_bindings = editor:get_all_bindings()
  177. for _, binding in ipairs(all_bindings) do
  178. if binding.sequence == key then
  179. local msg = key .. " runs " .. binding.command
  180. if binding.description ~= "" then
  181. msg = msg .. " (" .. binding.description .. ")"
  182. end
  183. return {success = true, message = msg}
  184. end
  185. end
  186. -- Check if it's a prefix
  187. if editor:has_prefix_bindings(key) then
  188. which_key.show_for_prefix(key)
  189. return {success = true}
  190. end
  191. return {success = false, message = key .. " is not bound"}
  192. end, {"dk"}, true, "s")
  193. -- Bind C-h k to describe-key (Emacs standard)
  194. editor:bind_key("C-h k", "describe-key", "Describe a key binding")
  195. -- Store module
  196. lumacs.which_key = which_key
  197. return which_key