| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- -- which-key.lua
- -- ============================================================================
- -- Displays available keybindings when a prefix key is pressed.
- -- Similar to Emacs which-key package.
- -- ============================================================================
- local which_key = {}
- -- Configuration
- which_key.config = {
- delay = 0.5, -- Delay before showing hints (not yet implemented - needs timer)
- max_display = 10, -- Max number of hints to show
- separator = " | ", -- Separator between hints
- show_description = true, -- Show command descriptions
- }
- -- Parse a key sequence like "C-x C-f" into parts
- local function parse_sequence(seq)
- local parts = {}
- for part in seq:gmatch("%S+") do
- table.insert(parts, part)
- end
- return parts
- end
- -- Check if sequence starts with prefix
- local function starts_with(sequence, prefix)
- local seq_parts = parse_sequence(sequence)
- local prefix_parts = parse_sequence(prefix)
- if #seq_parts < #prefix_parts then
- return false
- end
- for i, part in ipairs(prefix_parts) do
- if seq_parts[i] ~= part then
- return false
- end
- end
- return true
- end
- -- Get the next key after the prefix
- local function get_next_key(sequence, prefix)
- local seq_parts = parse_sequence(sequence)
- local prefix_parts = parse_sequence(prefix)
- if #seq_parts > #prefix_parts then
- return seq_parts[#prefix_parts + 1]
- end
- return nil
- end
- -- Get all bindings that start with the given prefix
- function which_key.get_bindings_for_prefix(prefix)
- local all_bindings = editor:get_all_bindings()
- local matching = {}
- -- Normalize prefix (remove trailing dash if present)
- prefix = prefix:gsub("%-$", "")
- for _, binding in ipairs(all_bindings) do
- if starts_with(binding.sequence, prefix) and binding.sequence ~= prefix then
- local next_key = get_next_key(binding.sequence, prefix)
- if next_key then
- -- Check if we already have this next_key
- local found = false
- for _, m in ipairs(matching) do
- if m.key == next_key then
- found = true
- break
- end
- end
- if not found then
- -- Determine if this is a prefix or a command
- local is_prefix = false
- local prefix_with_key = prefix .. " " .. next_key
- for _, b in ipairs(all_bindings) do
- if starts_with(b.sequence, prefix_with_key) and b.sequence ~= prefix_with_key then
- is_prefix = true
- break
- end
- end
- table.insert(matching, {
- key = next_key,
- command = binding.command_name,
- description = binding.description,
- full_sequence = binding.sequence,
- is_prefix = is_prefix,
- })
- end
- end
- end
- end
- -- Sort by key
- table.sort(matching, function(a, b)
- return a.key < b.key
- end)
- return matching
- end
- -- Format hints for display
- function which_key.format_hints(bindings, max_width)
- max_width = max_width or 80
- local hints = {}
- for i, binding in ipairs(bindings) do
- if i > which_key.config.max_display then
- table.insert(hints, "...")
- break
- end
- local hint
- if binding.is_prefix then
- hint = binding.key .. ":+prefix"
- elseif which_key.config.show_description and binding.description ~= "" then
- hint = binding.key .. ":" .. binding.description
- else
- hint = binding.key .. ":" .. binding.command
- end
- table.insert(hints, hint)
- end
- -- Join with separator
- local result = table.concat(hints, which_key.config.separator)
- -- Truncate if too long
- if #result > max_width then
- result = result:sub(1, max_width - 3) .. "..."
- end
- return result
- end
- -- Show which-key hints for current prefix
- function which_key.show_hints()
- if not editor:is_building_sequence() then
- return
- end
- local current = editor:current_sequence()
- -- Remove trailing dash
- current = current:gsub("%-$", "")
- local bindings = which_key.get_bindings_for_prefix(current)
- if #bindings > 0 then
- local hints = which_key.format_hints(bindings)
- editor:message(current .. "- " .. hints)
- end
- end
- -- Manually show which-key for a prefix
- function which_key.show_for_prefix(prefix)
- local bindings = which_key.get_bindings_for_prefix(prefix)
- if #bindings > 0 then
- local hints = which_key.format_hints(bindings)
- editor:message(prefix .. " " .. hints)
- else
- editor:message("No bindings for prefix: " .. prefix)
- end
- end
- -- Register commands
- editor:register_command("which-key", "Show available keybindings for prefix", function(args)
- if #args > 0 then
- which_key.show_for_prefix(args[1])
- elseif editor:is_building_sequence() then
- which_key.show_hints()
- else
- -- Show all prefixes
- local all_bindings = editor:get_all_bindings()
- local prefixes = {}
- for _, binding in ipairs(all_bindings) do
- local parts = parse_sequence(binding.sequence)
- if #parts > 1 then
- local prefix = parts[1]
- if not prefixes[prefix] then
- prefixes[prefix] = 0
- end
- prefixes[prefix] = prefixes[prefix] + 1
- end
- end
- local hints = {}
- for prefix, count in pairs(prefixes) do
- table.insert(hints, prefix .. ":+" .. count)
- end
- table.sort(hints)
- editor:message("Prefix keys: " .. table.concat(hints, " | "))
- end
- return {success = true}
- end, {"wk"}, true, "s")
- -- Describe key binding
- editor:register_command("describe-key", "Describe a key binding", function(args)
- if #args == 0 then
- return {success = false, message = "Usage: describe-key <key-sequence>"}
- end
- local key = args[1]
- local all_bindings = editor:get_all_bindings()
- for _, binding in ipairs(all_bindings) do
- if binding.sequence == key then
- local msg = key .. " runs " .. binding.command
- if binding.description ~= "" then
- msg = msg .. " (" .. binding.description .. ")"
- end
- return {success = true, message = msg}
- end
- end
- -- Check if it's a prefix
- if editor:has_prefix_bindings(key) then
- which_key.show_for_prefix(key)
- return {success = true}
- end
- return {success = false, message = key .. " is not bound"}
- end, {"dk"}, true, "s")
- -- Bind C-h k to describe-key (Emacs standard)
- editor:bind_key("C-h k", "describe-key", "Describe a key binding")
- -- Store module
- lumacs.which_key = which_key
- return which_key
|