Skip to content

Commit 862bb5f

Browse files
feat(input): Add async input
1 parent 13b5ff7 commit 862bb5f

File tree

21 files changed

+505
-316
lines changed

21 files changed

+505
-316
lines changed

docs/configuration.org

+22
Original file line numberDiff line numberDiff line change
@@ -2838,3 +2838,25 @@ require('orgmode').setup({
28382838
}
28392839
})
28402840
#+end_src
2841+
2842+
*** Input
2843+
:PROPERTIES:
2844+
:CUSTOM_ID: input
2845+
:END:
2846+
- Type: =boolean=
2847+
- Default: =false=
2848+
2849+
By default, Vim's built-in =input()= function is used for input prompts.
2850+
To use Neovim's =vim.ui.input()= function, add this to config:
2851+
2852+
#+begin_src lua
2853+
require('orgmode').setup({
2854+
ui = {
2855+
input = {
2856+
use_vim_ui = true
2857+
}
2858+
}
2859+
})
2860+
#+end_src
2861+
2862+
📝 NOTE: If you are using a plugin for =vim.ui.input=, make sure it supports autocompletion for better experience. [[https://github.com/folke/snacks.nvim][snacks.nvim]] input module supports autocompletion.

lua/orgmode/agenda/init.lua

+63-22
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ local AgendaFilter = require('orgmode.agenda.filter')
77
local Menu = require('orgmode.ui.menu')
88
local Promise = require('orgmode.utils.promise')
99
local AgendaTypes = require('orgmode.agenda.types')
10+
local Input = require('orgmode.ui.input')
1011

1112
---@class OrgAgenda
1213
---@field highlights table[]
@@ -48,7 +49,25 @@ function Agenda:open_view(type, opts)
4849
return
4950
end
5051
self.views = { view }
51-
return self:render()
52+
return self:prepare_and_render()
53+
end
54+
55+
function Agenda:prepare_and_render()
56+
return Promise.map(function(view)
57+
return view:prepare()
58+
end, self.views):next(function(views)
59+
local valid_views = vim.tbl_filter(function(view)
60+
return view ~= false
61+
end, views)
62+
63+
-- Some of the views returned false, abort render
64+
if #valid_views ~= #self.views then
65+
return
66+
end
67+
68+
self.views = views
69+
return self:render()
70+
end)
5271
end
5372

5473
function Agenda:render()
@@ -71,6 +90,10 @@ function Agenda:render()
7190
vim.w.org_window_pos = nil
7291
end
7392
end
93+
94+
if #self.views > 1 then
95+
vim.fn.cursor({ 1, 0 })
96+
end
7497
end
7598

7699
function Agenda:agenda(opts)
@@ -151,11 +174,7 @@ function Agenda:_build_custom_commands()
151174
table.insert(views, AgendaTypes[agenda_type.type]:new(opts))
152175
end
153176
self.views = views
154-
local result = self:render()
155-
if #self.views > 1 then
156-
vim.fn.cursor({ 1, 0 })
157-
end
158-
return result
177+
return self:prepare_and_render()
159178
end,
160179
})
161180
end
@@ -271,16 +290,21 @@ end
271290
---@param source? string
272291
function Agenda:redo(source, preserve_cursor_pos)
273292
self:_call_all_views('redo')
274-
return self.files:load(true):next(vim.schedule_wrap(function()
275-
local save_view = preserve_cursor_pos and vim.fn.winsaveview()
276-
if source == 'mapping' then
277-
self:_call_view_and_render('redraw')
278-
end
279-
self:render()
280-
if save_view then
281-
vim.fn.winrestview(save_view)
282-
end
283-
end))
293+
local save_view = preserve_cursor_pos and vim.fn.winsaveview()
294+
return self.files
295+
:load(true)
296+
:next(function()
297+
if source == 'mapping' then
298+
return self:_call_view_async('redraw')
299+
end
300+
return true
301+
end)
302+
:next(function()
303+
self:render()
304+
if save_view then
305+
vim.fn.winrestview(save_view)
306+
end
307+
end)
284308
end
285309

286310
function Agenda:advance_span(direction)
@@ -499,14 +523,15 @@ end
499523
function Agenda:filter()
500524
local this = self
501525
self.filters:parse_available_filters(self.views)
502-
local filter_term = utils.input('Filter [+cat-tag/regexp/]: ', self.filters.value, function(arg_lead)
526+
return Input.open('Filter [+cat-tag/regexp/]: ', self.filters.value, function(arg_lead)
503527
return utils.prompt_autocomplete(arg_lead, this.filters:get_completion_list(), { '+', '-' })
528+
end):next(function(value)
529+
if not value or value == self.filters.value then
530+
return false
531+
end
532+
self.filters:parse(value)
533+
return self:redo('filter', true)
504534
end)
505-
if filter_term == self.filters.value then
506-
return
507-
end
508-
self.filters:parse(filter_term)
509-
return self:redo('filter', true)
510535
end
511536

512537
---@param opts table
@@ -576,6 +601,22 @@ function Agenda:_call_view(method, ...)
576601
return executed
577602
end
578603

604+
function Agenda:_call_view_async(method, ...)
605+
local args = { ... }
606+
return Promise.map(function(view)
607+
if view[method] and view.view:is_in_range() then
608+
return view[method](view, unpack(args))
609+
end
610+
end, self.views):next(function(views)
611+
for _, view in ipairs(views) do
612+
if view then
613+
return true
614+
end
615+
end
616+
return false
617+
end)
618+
end
619+
579620
function Agenda:_call_all_views(method, ...)
580621
local executed = false
581622
for _, view in ipairs(self.views) do

lua/orgmode/agenda/types/agenda.lua

+5-7
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,7 @@ local AgendaLineToken = require('orgmode.agenda.view.token')
99
local ClockReport = require('orgmode.clock.report')
1010
local utils = require('orgmode.utils')
1111
local SortingStrategy = require('orgmode.agenda.sorting_strategy')
12-
13-
---@class OrgAgendaViewType
14-
---@field render fun(self: OrgAgendaViewType, bufnr:number, current_line?: number): OrgAgendaView
15-
---@field get_lines fun(self: OrgAgendaViewType): OrgAgendaLine | OrgAgendaLine[]
16-
---@field get_line fun(self: OrgAgendaViewType, line_nr: number): OrgAgendaLine | nil
17-
---@field rerender_agenda_line fun(self: OrgAgendaViewType, agenda_line: OrgAgendaLine, headline: OrgHeadline): OrgAgendaLine | nil
18-
---@field view OrgAgendaView
12+
local Promise = require('orgmode.utils.promise')
1913

2014
---@class OrgAgendaTypeOpts
2115
---@field files OrgFiles
@@ -97,6 +91,10 @@ function OrgAgendaType:new(opts)
9791
return this
9892
end
9993

94+
function OrgAgendaType:prepare()
95+
return Promise.resolve(self)
96+
end
97+
10098
function OrgAgendaType:redo()
10199
if self.agenda_files then
102100
self.files:load_sync(true)

lua/orgmode/agenda/types/init.lua

+10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
---@class OrgAgendaViewType
2+
---@field render fun(self: OrgAgendaViewType, bufnr:number, current_line?: number): OrgAgendaView
3+
---@field get_lines fun(self: OrgAgendaViewType): OrgAgendaLine | OrgAgendaLine[]
4+
---@field get_line fun(self: OrgAgendaViewType, line_nr: number): OrgAgendaLine | nil
5+
---@field rerender_agenda_line fun(self: OrgAgendaViewType, agenda_line: OrgAgendaLine, headline: OrgHeadline): OrgAgendaLine | nil
6+
---@field view OrgAgendaView
7+
---@field prepare fun(self: OrgAgendaViewType): OrgPromise<OrgAgendaViewType>
8+
---@field redraw? fun(self: OrgAgendaViewType): OrgPromise<OrgAgendaViewType>
9+
---@field redo? fun(self: OrgAgendaViewType): OrgPromise<OrgAgendaViewType>
10+
111
---@alias OrgAgendaTypes 'agenda' | 'todo' | 'tags' | 'tags_todo' | 'search'
212
return {
313
agenda = require('orgmode.agenda.types.agenda'),

lua/orgmode/agenda/types/search.lua

+15-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
local utils = require('orgmode.utils')
21
---@diagnostic disable: inject-field
32
local OrgAgendaTodosType = require('orgmode.agenda.types.todo')
3+
local Input = require('orgmode.ui.input')
44

55
---@class OrgAgendaSearchTypeOpts:OrgAgendaTodosTypeOpts
66
---@field headline_query? string
@@ -18,27 +18,35 @@ function OrgAgendaSearchType:new(opts)
1818
local obj = OrgAgendaTodosType:new(opts)
1919
setmetatable(obj, self)
2020
obj.headline_query = self.headline_query
21-
if not opts.headline_query or opts.headline_query == '' then
22-
obj.headline_query = self:get_search_term()
23-
end
2421
return obj
2522
end
2623

24+
function OrgAgendaSearchType:prepare()
25+
if not self.headline_query or self.headline_query == '' then
26+
return self:get_search_term()
27+
end
28+
end
29+
2730
function OrgAgendaSearchType:get_file_headlines(file)
2831
return file:find_headlines_matching_search_term(self.headline_query or '', false, true)
2932
end
3033

3134
function OrgAgendaSearchType:get_search_term()
32-
return utils.input('Enter search term: ', self.headline_query or '')
35+
return Input.open('Enter search term: ', self.headline_query or ''):next(function(value)
36+
if not value then
37+
return false
38+
end
39+
self.headline_query = value
40+
return self
41+
end)
3342
end
3443

3544
function OrgAgendaSearchType:redraw()
3645
-- Skip prompt for custom views
3746
if self.id then
3847
return self
3948
end
40-
self.headline_query = self:get_search_term()
41-
return self
49+
return self:get_search_term()
4250
end
4351

4452
return OrgAgendaSearchType

lua/orgmode/agenda/types/tags.lua

+32-21
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ local config = require('orgmode.config')
44
local utils = require('orgmode.utils')
55
local Search = require('orgmode.files.elements.search')
66
local OrgAgendaTodosType = require('orgmode.agenda.types.todo')
7+
local Input = require('orgmode.ui.input')
78

89
---@alias OrgAgendaTodoIgnoreDeadlinesTypes 'all' | 'near' | 'far' | 'past' | 'future'
910
---@alias OrgAgendaTodoIgnoreScheduledTypes 'all' | 'past' | 'future'
@@ -27,24 +28,31 @@ function OrgAgendaTagsType:new(opts)
2728
if not opts.id then
2829
opts.subheader = 'Press "r" to update search'
2930
end
30-
local match_query = opts.match_query
31-
if not opts.id and (not match_query or match_query == '') then
32-
match_query = self:get_tags(opts.files)
33-
if not match_query then
34-
return nil
35-
end
36-
end
37-
3831
setmetatable(self, { __index = OrgAgendaTodosType })
3932
local obj = OrgAgendaTodosType:new(opts)
4033
setmetatable(obj, self)
41-
obj.match_query = match_query or ''
34+
obj.match_query = opts.match_query or ''
4235
obj.todo_ignore_deadlines = opts.todo_ignore_deadlines
4336
obj.todo_ignore_scheduled = opts.todo_ignore_scheduled
44-
obj.header = opts.header or ('Headlines with TAGS match: ' .. obj.match_query)
4537
return obj
4638
end
4739

40+
function OrgAgendaTagsType:_get_header()
41+
if self.header then
42+
return self.header
43+
end
44+
45+
return 'Headlines with TAGS match: ' .. (self.match_query or '')
46+
end
47+
48+
function OrgAgendaTagsType:prepare()
49+
if self.id or self.match_query and self.match_query ~= '' then
50+
return self
51+
end
52+
53+
return self:get_tags()
54+
end
55+
4856
function OrgAgendaTagsType:get_file_headlines(file)
4957
local headlines = file:apply_search(Search:new(self.match_query), self.todo_only)
5058
if self.todo_ignore_deadlines then
@@ -94,25 +102,28 @@ function OrgAgendaTagsType:get_file_headlines(file)
94102
return headlines
95103
end
96104

97-
---@param files? OrgFiles
98-
function OrgAgendaTagsType:get_tags(files)
99-
local tags = utils.input('Match: ', self.match_query or '', function(arg_lead)
100-
return utils.prompt_autocomplete(arg_lead, (files or self.files):get_tags())
105+
function OrgAgendaTagsType:get_tags()
106+
return Input.open('Match: ', self.match_query or '', function(arg_lead)
107+
return utils.prompt_autocomplete(arg_lead, self.files:get_tags())
108+
end):next(function(tags)
109+
if not tags then
110+
return false
111+
end
112+
if vim.trim(tags) == '' then
113+
utils.echo_warning('Invalid tag.')
114+
return false
115+
end
116+
self.match_query = tags
117+
return self
101118
end)
102-
if vim.trim(tags) == '' then
103-
return utils.echo_warning('Invalid tag.')
104-
end
105-
return tags
106119
end
107120

108121
function OrgAgendaTagsType:redraw()
109122
-- Skip prompt for custom views
110123
if self.id then
111124
return self
112125
end
113-
self.match_query = self:get_tags() or ''
114-
self.header = 'Headlines with TAGS match: ' .. self.match_query
115-
return self
126+
return self:get_tags()
116127
end
117128

118129
return OrgAgendaTagsType

lua/orgmode/agenda/types/todo.lua

+13-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ local utils = require('orgmode.utils')
88
local agenda_highlights = require('orgmode.colors.highlights')
99
local hl_map = agenda_highlights.get_agenda_hl_map()
1010
local SortingStrategy = require('orgmode.agenda.sorting_strategy')
11+
local Promise = require('orgmode.utils.promise')
1112

1213
---@class OrgAgendaTodosTypeOpts
1314
---@field files OrgFiles
@@ -74,6 +75,10 @@ function OrgAgendaTodosType:new(opts)
7475
return this
7576
end
7677

78+
function OrgAgendaTodosType:prepare()
79+
return Promise.resolve(self)
80+
end
81+
7782
function OrgAgendaTodosType:_setup_agenda_files()
7883
if not self.agenda_files then
7984
return
@@ -90,14 +95,21 @@ function OrgAgendaTodosType:redo()
9095
end
9196
end
9297

98+
function OrgAgendaTodosType:_get_header()
99+
if self.header then
100+
return self.header
101+
end
102+
return 'Global list of TODO items of type: ALL'
103+
end
104+
93105
---@param bufnr? number
94106
function OrgAgendaTodosType:render(bufnr)
95107
self.bufnr = bufnr or 0
96108
local headlines, category_length = self:_get_headlines()
97109
local agendaView = AgendaView:new({ bufnr = self.bufnr, highlighter = self.highlighter })
98110

99111
agendaView:add_line(AgendaLine:single_token({
100-
content = self.header or 'Global list of TODO items of type: ALL',
112+
content = self:_get_header(),
101113
hl_group = '@org.agenda.header',
102114
}))
103115
if self.subheader then

0 commit comments

Comments
 (0)