Skip to content

Commit 1c46d8e

Browse files
authored
feat: modern menu (#549)
1 parent 07d412e commit 1c46d8e

File tree

7 files changed

+317
-90
lines changed

7 files changed

+317
-90
lines changed

DOCS.md

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
7. [Autocompletion](#autocompletion)
2121
8. [Abbreviations](#abbreviations)
2222
9. [Formatting](#formatting)
23-
10. [Colors](#colors)
23+
10. [User interface](#user-interface)
24+
1. [Colors](#colors)
25+
2. [Menu](#menu)
2426
11. [Advanced search](#advanced-search)
2527
12. [Notifications (experimental)](#notifications-experimental)
2628
13. [Clocking](#clocking)
@@ -745,7 +747,7 @@ require('orgmode').setup({
745747
})
746748
```
747749

748-
### Closing note mappings
750+
### Note mappings
749751

750752
Mappings used in closing note window.
751753

@@ -1135,7 +1137,9 @@ Currently, these things are formatted:
11351137
* Tables are formatted (see [Tables](#Tables) for more info)
11361138
* Clock entries total time is recalculated (see [Recalculating totals](#recalculating-totals) in [Clocking](#Clocking) section)
11371139

1138-
## Colors
1140+
## User interface
1141+
1142+
### Colors
11391143
Colors used for todo keywords and agenda states (deadline, schedule ok, schedule warning)
11401144
are parsed from the current colorsheme from several highlight groups (Error, WarningMsg, DiffAdd, etc.).
11411145
If those colors are not suitable you can override them like this:
@@ -1162,7 +1166,7 @@ endfunction
11621166

11631167
For adding/changing TODO keyword colors see [org-todo-keyword-faces](#org_todo_keyword_faces)
11641168

1165-
### Highlight Groups
1169+
#### Highlight Groups
11661170

11671171
* The following highlight groups are based on _Treesitter_ query results, hence when setting up _Orgmode_ these
11681172
highlights must be enabled by removing `disable = {'org'}` from the default recommended _Treesitter_ configuration.
@@ -1197,6 +1201,76 @@ For adding/changing TODO keyword colors see [org-todo-keyword-faces](#org_todo_k
11971201
* `OrgAgendaScheduled`: A scheduled item in the agenda view
11981202
* `OrgAgendaScheduledPast`: A item past its scheduled date in the agenda view
11991203

1204+
### Menu
1205+
1206+
The menu is used when selecting further actions in `agenda`, `capture` and `export`. Here is an example of the menu you see when opening `agenda`:
1207+
1208+
```
1209+
Press key for an agenda command
1210+
-------------------------------
1211+
a Agenda for current week or day
1212+
t List of all TODO entries
1213+
m Match a TAGS/PROP/TODO query
1214+
M Like m, but only for TODO entries
1215+
s Search for keywords
1216+
q Quit
1217+
```
1218+
Users have the option to change the appearance of this menu. To do this, you need to add a handler in the UI configuration section:
1219+
```lua
1220+
require("orgmode").setup({
1221+
ui = {
1222+
menu = {
1223+
handler = function(data)
1224+
-- your handler here, for example:
1225+
local options = {}
1226+
local options_by_label = {}
1227+
1228+
for _, item in ipairs(data.items) do
1229+
-- Only MenuOption has `key`
1230+
-- Also we don't need `Quit` option because we can close the menu with ESC
1231+
if item.key and item.label:lower() ~= "quit" then
1232+
table.insert(options, item.label)
1233+
options_by_label[item.label] = item
1234+
end
1235+
end
1236+
1237+
local handler = function(choice)
1238+
if not choice then
1239+
return
1240+
end
1241+
1242+
local option = options_by_label[choice]
1243+
if option.action then
1244+
option.action()
1245+
end
1246+
end
1247+
1248+
vim.ui.select(options, {
1249+
propmt = data.propmt,
1250+
}, handler)
1251+
end,
1252+
},
1253+
},
1254+
})
1255+
```
1256+
When the menu is called, the handler receives a table `data` with the following fields as input:
1257+
* `title` (`string`) — menu title
1258+
* `items` (`table`) — array containing `MenuItem` (see below)
1259+
* `prompt` (`string`) — prompt text used to prompt a keystroke
1260+
1261+
Each menu item `MenuItem` is one of two types: `MenuOption` and `MenuSeparator`.
1262+
1263+
`MenuOption` is a table containing the following fields:
1264+
* `label` (`string`) — description of the action
1265+
* `key` (`string`) — key that will be processed when the keys are pressed in the menu
1266+
* `action` (`function` *optional*) — handler that will be called when the `key` is pressed in the menu.
1267+
1268+
`MenuSeparator` is a table containing the following fields:
1269+
* `icon` (`string` *optional*) — character used as separator. The default character is `-`
1270+
* `length` (`number` *optional*) — number of repetitions of the separator character. The default length is 80
1271+
1272+
In order for the menu to work as expected, the handler must call `action` from `MenuItem`.
1273+
12001274
## Advanced search
12011275
Part of [Advanced search](https://orgmode.org/worg/org-tutorials/advanced-searching.html) functionality
12021276
is implemented.

lua/orgmode/agenda/init.lua

Lines changed: 45 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ local AgendaSearchView = require('orgmode.agenda.views.search')
99
local AgendaTodosView = require('orgmode.agenda.views.todos')
1010
local AgendaTagsView = require('orgmode.agenda.views.tags')
1111
local AgendaView = require('orgmode.agenda.views.agenda')
12+
local Menu = require('orgmode.ui.menu')
1213

1314
---@class Agenda
1415
---@field content table[]
@@ -88,45 +89,50 @@ end
8889

8990
function Agenda:prompt()
9091
self.filters:reset()
91-
return utils.menu('Press key for an agenda command', {
92-
{
93-
label = 'Agenda for current week or day',
94-
key = 'a',
95-
action = function()
96-
return self:agenda()
97-
end,
98-
},
99-
{
100-
label = 'List of all TODO entries',
101-
key = 't',
102-
action = function()
103-
return self:todos()
104-
end,
105-
},
106-
{
107-
label = 'Match a TAGS/PROP/TODO query',
108-
key = 'm',
109-
action = function()
110-
return self:tags()
111-
end,
112-
},
113-
{
114-
label = 'Like m, but only TODO entries',
115-
key = 'M',
116-
action = function()
117-
return self:tags({ todo_only = true })
118-
end,
119-
},
120-
{
121-
label = 'Search for keywords',
122-
key = 's',
123-
action = function()
124-
return self:search()
125-
end,
126-
},
127-
{ label = 'Quit', key = 'q' },
128-
{ label = '', separator = ' ', length = 1 },
129-
}, 'Press key for an agenda command')
92+
local menu = Menu:new({
93+
title = 'Press key for an agenda command',
94+
prompt = 'Press key for an agenda command',
95+
})
96+
97+
menu:add_option({
98+
label = 'Agenda for current week or day',
99+
key = 'a',
100+
action = function()
101+
return self:agenda()
102+
end,
103+
})
104+
menu:add_option({
105+
label = 'List of all TODO entries',
106+
key = 't',
107+
action = function()
108+
return self:todos()
109+
end,
110+
})
111+
menu:add_option({
112+
label = 'Match a TAGS/PROP/TODO query',
113+
key = 'm',
114+
action = function()
115+
return self:tags()
116+
end,
117+
})
118+
menu:add_option({
119+
label = 'Like m, but only TODO entries',
120+
key = 'M',
121+
action = function()
122+
return self:tags({ todo_only = true })
123+
end,
124+
})
125+
menu:add_option({
126+
label = 'Search for keywords',
127+
key = 's',
128+
action = function()
129+
return self:search()
130+
end,
131+
})
132+
menu:add_option({ label = 'Quit', key = 'q' })
133+
menu:add_separator({ icon = ' ', length = 1 })
134+
135+
return menu:open()
130136
end
131137

132138
function Agenda:_render(skip_rebuild)

lua/orgmode/capture/init.lua

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ local Files = require('orgmode.parser.files')
44
local File = require('orgmode.parser.file')
55
local Templates = require('orgmode.capture.templates')
66
local ClosingNote = require('orgmode.capture.closing_note')
7+
local Menu = require('orgmode.ui.menu')
78

89
---@class Capture
910
---@field templates Templates
@@ -37,13 +38,13 @@ function Capture:_create_menu_items(templates)
3738
key = key,
3839
}
3940
if type(template) == 'string' then
40-
item['label'] = template .. '...'
41-
item['action'] = function()
41+
item.label = template .. '...'
42+
item.action = function()
4243
self:_create_prompt(self:_get_subtemplates(key, templates))
4344
end
4445
else
45-
item['label'] = template.description
46-
item['action'] = function()
46+
item.label = template.description
47+
item.action = function()
4748
return self:open_template(template)
4849
end
4950
end
@@ -54,12 +55,15 @@ function Capture:_create_menu_items(templates)
5455
end
5556

5657
function Capture:_create_prompt(templates)
57-
local menu_items = self:_create_menu_items(templates)
58-
table.insert(menu_items, { label = '', key = '', separator = '-' })
59-
table.insert(menu_items, { label = 'Quit', key = 'q' })
60-
table.insert(menu_items, { label = '', separator = ' ', length = 1 })
61-
62-
return utils.menu('Select a capture template', menu_items, 'Template key')
58+
local menu = Menu:new({
59+
title = 'Select a capture template',
60+
items = self:_create_menu_items(templates),
61+
prompt = 'Template key',
62+
})
63+
menu:add_separator()
64+
menu:add_option({ label = 'Quit', key = 'q' })
65+
menu:add_separator({ icon = ' ', length = 1 })
66+
return menu:open()
6367
end
6468

6569
function Capture:prompt()

lua/orgmode/config/defaults.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ local DefaultConfig = {
171171
executable_path = 'emacs',
172172
config_path = '$HOME/.emacs.d/init.el',
173173
},
174+
ui = {
175+
menu = {
176+
handler = nil,
177+
},
178+
},
174179
}
175180

176181
return DefaultConfig

lua/orgmode/export/init.lua

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
local utils = require('orgmode.utils')
22
local config = require('orgmode.config')
3+
local Menu = require('orgmode.ui.menu')
34

45
---@class Export
56
local Export = {}
@@ -32,17 +33,20 @@ function Export._exporter(cmd, target, on_success, on_error)
3233
return on_success(output)
3334
end
3435

35-
return utils.menu(string.format('Exported to %s', target), {
36-
{ label = '', separator = '-', length = 34 },
37-
{
38-
label = 'Yes',
39-
key = 'y',
40-
action = function()
41-
return utils.open(target)
42-
end,
43-
},
44-
{ label = 'No', key = 'n' },
45-
}, 'Open')
36+
local menu = Menu:new({
37+
title = string.format('Exported to %s', target),
38+
prompt = 'Open?',
39+
})
40+
menu:add_separator({ length = 34 })
41+
menu:add_option({
42+
label = 'Yes',
43+
key = 'y',
44+
action = function()
45+
return utils.open(target)
46+
end,
47+
})
48+
menu:add_option({ label = 'No', key = 'n' })
49+
return menu:open()
4650
end,
4751
})
4852
end
@@ -137,7 +141,11 @@ function Export.prompt()
137141
local action
138142
if #commands > 1 then
139143
action = function()
140-
utils.menu(label .. ' via', commands, label .. ' via')
144+
Menu:new({
145+
title = label .. ' via',
146+
items = commands,
147+
prompt = label .. ' via',
148+
}):open()
141149
end
142150

143151
table.insert(commands, {
@@ -208,9 +216,13 @@ function Export.prompt()
208216
end
209217

210218
table.insert(opts, { label = 'quit', key = 'q' })
211-
table.insert(opts, { label = '', separator = ' ', length = 1 })
219+
table.insert(opts, { icon = ' ', length = 1 })
212220

213-
return utils.menu('Export options', opts, 'Export command')
221+
return Menu:new({
222+
title = 'Export options',
223+
items = opts,
224+
prompt = 'Export command',
225+
}):open()
214226
end
215227

216228
return Export

0 commit comments

Comments
 (0)