Skip to content

Commit 68b6275

Browse files
refactor(capture): Make template compilation async
1 parent 4874e72 commit 68b6275

File tree

6 files changed

+112
-55
lines changed

6 files changed

+112
-55
lines changed

lua/orgmode/capture/init.lua

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,6 @@ function Capture:open_template(template)
7676
end,
7777
})
7878

79-
if template:has_input_prompts() then
80-
return template:prompt_for_inputs():next(function(proceed)
81-
if not proceed then
82-
return utils.echo_info('Canceled.')
83-
end
84-
return self._window:open()
85-
end)
86-
end
87-
8879
return self._window:open()
8980
end
9081

lua/orgmode/capture/template/init.lua

Lines changed: 76 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ local expansions = {
4646
---@field whole_file? boolean
4747

4848
---@class OrgCaptureTemplate:OrgCaptureTemplateOpts
49-
---@field private _compile_hooks (fun(content:string, type: 'target' | 'content'):string)[]
49+
---@field private _compile_hooks (fun(content:string, content_type: 'target' | 'content'):string | nil)[]
5050
local Template = {}
5151

5252
---@param opts OrgCaptureTemplateOpts
@@ -156,25 +156,21 @@ function Template:compile()
156156
if type(content) == 'table' then
157157
content = table.concat(content, '\n')
158158
end
159-
content = self:_compile(content or '', 'content')
160-
return vim.split(content, '\n', { plain = true })
161-
end
162-
163-
function Template:has_input_prompts()
164-
return self.datetree and type(self.datetree) == 'table' and self.datetree.time_prompt
165-
end
166-
167-
function Template:prompt_for_inputs()
168-
if not self:has_input_prompts() then
169-
return Promise.resolve(true)
170-
end
171-
return Calendar.new({ date = Date.now() }):open():next(function(date)
172-
if date then
173-
self.datetree.date = date
174-
return true
175-
end
176-
return false
177-
end)
159+
return self
160+
:_compile(self.target, 'target')
161+
:next(function(target)
162+
if not target then
163+
return nil
164+
end
165+
self.target = target
166+
return self:_compile(content or '', 'content')
167+
end)
168+
:next(function(compiled_content)
169+
if not compiled_content then
170+
return nil
171+
end
172+
return vim.split(compiled_content, '\n', { plain = true })
173+
end)
178174
end
179175

180176
---@return OrgCaptureTemplateDatetreeOpts
@@ -189,7 +185,7 @@ end
189185

190186
---@return string
191187
function Template:get_target()
192-
return vim.fn.resolve(vim.fn.fnamemodify(self:_compile(self.target, 'target'), ':p'))
188+
return vim.fn.resolve(vim.fn.fnamemodify(self.target, ':p'))
193189
end
194190

195191
---@param lines string[]
@@ -210,31 +206,80 @@ end
210206

211207
---@private
212208
---@param content string
213-
---@param type 'target' | 'content'
214-
---@return string
215-
function Template:_compile(content, type)
209+
---@param content_type 'target' | 'content'
210+
---@return OrgPromise<string | nil>
211+
function Template:_compile(content, content_type)
216212
content = self:_compile_dates(content)
217-
content = self:_compile_expansions(content)
218-
content = self:_compile_expressions(content)
219213
content = self:_compile_prompts(content)
214+
content = self:_compile_expressions(content)
220215
if self._compile_hooks then
221216
for _, hook in ipairs(self._compile_hooks) do
222-
content = hook(content, type)
217+
content = hook(content, content_type)
218+
if not content then
219+
return Promise.resolve(nil)
220+
end
223221
end
224222
end
225-
return content
223+
return self:_compile_datetree(content, content_type):next(function(compiled_content)
224+
if not compiled_content then
225+
return nil
226+
end
227+
return self:_compile_expansions(compiled_content)
228+
end)
226229
end
227230

228231
---@param content string
229-
---@return string
232+
---@param content_type 'target' | 'content'
233+
---@return OrgPromise<string | nil>
234+
function Template:_compile_datetree(content, content_type)
235+
if
236+
not self.datetree
237+
or type(self.datetree) ~= 'table'
238+
or not self.datetree.time_prompt
239+
or content_type ~= 'target'
240+
then
241+
return Promise.resolve(content)
242+
end
243+
244+
return Calendar.new({ date = Date.now() }):open():next(function(date)
245+
if date then
246+
self.datetree.date = date
247+
return content
248+
end
249+
return nil
250+
end)
251+
end
252+
253+
---@param content string
254+
---@return OrgPromise<string | nil>
230255
function Template:_compile_expansions(content, found_expansions)
231256
found_expansions = found_expansions or expansions
257+
local promises = {}
258+
local proceed = true
232259
for expansion, compiler in pairs(found_expansions) do
233260
if content:match(vim.pesc(expansion)) then
234-
content = content:gsub(vim.pesc(expansion), vim.pesc(compiler()))
261+
table.insert(
262+
promises,
263+
Promise.resolve()
264+
:next(function()
265+
return compiler()
266+
end)
267+
:next(function(replacement)
268+
if not proceed or not replacement then
269+
proceed = false
270+
return
271+
end
272+
content = content:gsub(vim.pesc(expansion), vim.pesc(replacement))
273+
end)
274+
)
235275
end
236276
end
237-
return content
277+
return Promise.all(promises):next(function()
278+
if not proceed then
279+
return nil
280+
end
281+
return content
282+
end)
238283
end
239284

240285
---@param content string

lua/orgmode/capture/templates.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ local Template = require('orgmode.capture.template')
77
---@field templates table<string, OrgCaptureTemplate>
88
local Templates = {}
99

10-
---@param templates table<string, OrgCaptureTemplate>
10+
---@param templates? table<string, OrgCaptureTemplate>
1111
---@return OrgCaptureTemplates
1212
function Templates:new(templates)
1313
local opts = {}

lua/orgmode/capture/window.lua

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,23 @@ function CaptureWindow:open()
3131
return self:focus()
3232
end
3333
self._resolve_fn = nil
34-
local content = self.template:compile()
35-
self._window = utils.open_tmp_org_window(16, config.win_split_mode, config.win_border, self.on_close)
36-
vim.api.nvim_buf_set_lines(0, 0, -1, true, content)
37-
self.template:setup()
38-
vim.b.org_capture = true
39-
self._bufnr = vim.api.nvim_get_current_buf()
34+
return self.template:compile():next(function(content)
35+
if not content then
36+
return utils.echo_info('Canceled.')
37+
end
38+
self._window = utils.open_tmp_org_window(16, config.win_split_mode, config.win_border, self.on_close)
39+
vim.api.nvim_buf_set_lines(0, 0, -1, true, content)
40+
self.template:setup()
41+
vim.b.org_capture = true
42+
self._bufnr = vim.api.nvim_get_current_buf()
4043

41-
if self.on_open then
42-
self.on_open()
43-
end
44+
if self.on_open then
45+
self.on_open()
46+
end
4447

45-
return Promise.new(function(resolve)
46-
self._resolve_fn = resolve
48+
return Promise.new(function(resolve)
49+
self._resolve_fn = resolve
50+
end)
4751
end)
4852
end
4953

tests/plenary/api/api_spec.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ describe('Api', function()
410410
})
411411

412412
orgmode.capture:open_template_by_shortcut('t')
413+
vim.wait(10)
413414
local source_file = vim.api.nvim_buf_get_name(0)
414415
vim.api.nvim_buf_set_lines(0, 0, -1, false, {
415416
'* TODO Second level :NESTEDTAG:',
@@ -446,6 +447,7 @@ describe('Api', function()
446447
})
447448

448449
orgmode.capture:open_template_by_shortcut('t')
450+
vim.wait(10)
449451
local source_file = vim.api.nvim_buf_get_name(0)
450452
vim.api.nvim_buf_set_lines(0, 0, -1, false, {
451453
'* TODO Second level :NESTEDTAG:',

tests/plenary/capture/templates_spec.lua

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe('Capture template', function()
1919
os.date('%A'),
2020
'test',
2121
'hello world',
22-
}, template:compile())
22+
}, template:compile():wait())
2323

2424
vim.fn.setreg('+', clip_backup)
2525
end)
@@ -35,7 +35,7 @@ describe('Capture template', function()
3535
assert.are.same({
3636
'* TODO [[nvim-orgmode%20is%20great!][]]',
3737
'',
38-
}, template:compile())
38+
}, template:compile():wait())
3939
vim.fn.setreg('+', clip_backup)
4040
end)
4141

@@ -70,6 +70,21 @@ describe('Capture template', function()
7070
content = content:gsub('{slug}', 'org-test')
7171
return content
7272
end)
73-
assert.are.same({ '* This is a test Org Test and org-test in headline' }, template:compile())
73+
assert.are.same({ '* This is a test Org Test and org-test in headline' }, template:compile():wait())
74+
end)
75+
76+
it('should return nil if custom compile hooks return nil', function()
77+
local template = Template:new({
78+
template = '* This is a test {title} and {slug} in headline',
79+
})
80+
template:on_compile(function(content)
81+
content = content:gsub('{title}', 'Org Test')
82+
content = content:gsub('{slug}', 'org-test')
83+
return content
84+
end)
85+
template:on_compile(function()
86+
return nil
87+
end)
88+
assert.is.Nil(template:compile():wait())
7489
end)
7590
end)

0 commit comments

Comments
 (0)