Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions lua/orgmode/objects/todo_state.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,58 @@ end

---@return OrgTodoKeyword | nil
function TodoState:open_fast_access()
if config.ui.menu.handler then
return self:_open_fast_access_menu()
end
return self:_open_fast_access_echo()
end

--- Delegates to the configurable menu handler so UI plugins can render the
--- prompt in a window instead of the message area.
---@private
---@return OrgTodoKeyword | nil
function TodoState:_open_fast_access_menu()
local Menu = require('orgmode.ui.menu')
local menu = Menu:new({
title = 'Select a TODO keyword',
prompt = 'Press key',
kind = 'todo_fast_access',
})

-- One group per keyword sequence, so a handler can keep the cycle grouping.
for _, sequence in ipairs(self.todos.sequences) do
local group = {}
for _, todo in ipairs(sequence) do
table.insert(group, {
label = todo.value,
key = todo.shortcut,
hl = todo.hl,
action = function()
return todo
end,
})
end
if #group > 0 then
menu:add_group(group)
end
end

menu:add_group({
{
label = 'Clear keyword',
key = ' ',
action = function()
return TodoKeyword:empty()
end,
},
})

return menu:open()
end

---@private
---@return OrgTodoKeyword | nil
function TodoState:_open_fast_access_echo()
local output = {}

-- Group keywords by sequence
Expand Down
30 changes: 30 additions & 0 deletions lua/orgmode/ui/menu.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,26 @@ local config = require('orgmode.config')
---@field label string Description of the action
---@field key string Key that will be processed when the keys are pressed in the menu
---@field action? function Handler that will be called when the `key` is pressed in the menu.
---@field hl? string Highlight group for the label, for handlers that render it

---@class OrgMenuSeparator
---@field icon string? Character used as separator. The default character is `-`
---@field length number? Number of repetitions of the separator character. The default length is 80

---@alias OrgMenuItem OrgMenuOption | OrgMenuSeparator

---@alias OrgMenuGroup OrgMenuOption[]

---@class OrgMenuOpts
---@field title string Menu title
---@field items OrgMenuItem[]? Menu items, may include options and separators
---@field prompt string Prompt text used to prompt a keystroke
---@field separator OrgMenuSeparator? Default separator
---@field kind string? Stable identifier of the menu type, for handlers that branch on it

--- Menu for selecting an action by pressing a key by the user
---@class OrgMenu:OrgMenuOpts
---@field groups OrgMenuGroup[]
local Menu = {}

---@param data OrgMenuOpts
Expand All @@ -30,6 +35,8 @@ function Menu:new(data)
opts.title = data.title
opts.prompt = data.prompt
opts.items = data.items or {}
opts.groups = {}
opts.kind = data.kind
opts.separator = vim.tbl_deep_extend('force', { icon = '-', length = 80 }, data.separator or {})

setmetatable(opts, self)
Expand All @@ -42,6 +49,7 @@ function Menu:_validate_option(option)
vim.validate('label', option.label, 'string')
vim.validate('key', option.key, 'string')
vim.validate('action', option.action, 'function', true)
vim.validate('hl', option.hl, 'string', true)
end

---@param items OrgMenuItem[]?
Expand Down Expand Up @@ -75,6 +83,7 @@ end
function Menu:_validate_data(data)
vim.validate('title', data.title, 'string')
vim.validate('prompt', data.prompt, 'string')
vim.validate('kind', data.kind, 'string', true)
self:_validate_items(data.items)
self:_validate_separator(data.separator)
end
Expand All @@ -91,10 +100,27 @@ function Menu:add_separator(separator)
table.insert(self.items, vim.tbl_deep_extend('force', self.separator, separator or {}))
end

--- Adds a group of options. Groups carry structure (e.g. one todo sequence per
--- group) for handlers that lay them out; the options are also appended to the
--- flat `items` list so handlers that ignore groups keep working.
---@param options OrgMenuGroup
function Menu:add_group(options)
vim.validate('options', options, 'table')
for _, option in ipairs(options) do
self:_validate_option(option)
end
table.insert(self.groups, options)
for _, option in ipairs(options) do
table.insert(self.items, option)
end
end

---@class OrgMenuData
---@field title string Menu title
---@field items OrgMenuItem[] Menu items, may include options and separators
---@field prompt string Prompt text used to prompt a keystroke
---@field groups? OrgMenuGroup[] Options grouped by structure; `items` is the flat view
---@field kind? string Stable identifier of the menu type

---@param data OrgMenuData
function Menu._default_menu(data)
Expand Down Expand Up @@ -145,7 +171,11 @@ function Menu:open()
title = self.title,
items = self.items,
prompt = self.prompt,
kind = self.kind,
}
if #self.groups > 0 then
menu_data.groups = self.groups
end
local custom_handler = config.ui.menu.handler
if custom_handler then
return custom_handler(menu_data)
Expand Down
Loading