Skip to content

Commit 14e880e

Browse files
feat: Add ability to provide closing note when marking TODO as DONE
1 parent b82cc5d commit 14e880e

File tree

11 files changed

+265
-54
lines changed

11 files changed

+265
-54
lines changed

DOCS.md

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
1. [Global mappings](#global-mappings)
1010
2. [Agenda mappings](#agenda-mappings)
1111
3. [Capture mappings](#capture-mappings)
12-
4. [Org mappings](#org-mappings)
13-
5. [Edit Src mappings](#edit-src)
14-
6. [Text objects](#text-objects)
15-
7. [Dot repeat](#dot-repeat)
12+
4. [Note mappings](#note-mappings)
13+
5. [Org mappings](#org-mappings)
14+
6. [Edit Src mappings](#edit-src)
15+
7. [Text objects](#text-objects)
16+
8. [Dot repeat](#dot-repeat)
1617
4. [Document Diagnostics](#document-diagnostics)
1718
5. [Tables](#tables)
1819
6. [Hyperlinks](#hyperlinks)
@@ -209,8 +210,10 @@ Marker used to indicate a folded headline.
209210
#### **org_log_done**
210211
*type*: `string|nil`<br />
211212
*default value*: `time`<br />
212-
When set to `time`(default), adds `CLOSED` date when marking headline as done.<br />
213-
When set to `false`, it is disabled.
213+
Possible values:
214+
* `time` - adds `CLOSED` date when marking headline as done
215+
* `note` - adds `CLOSED` date as above, and prompts for closing note via capture window. Confirm note with `org_note_finalize` (Default `<C-c>`), or ignore providing note via `org_note_kill` (Default `<Leader>ok`)
216+
* `nil|false` - Disable any logging
214217

215218
#### **org_highlight_latex_and_related**
216219
*type*: `string|nil`<br />
@@ -709,6 +712,36 @@ require('orgmode').setup({
709712
})
710713
```
711714

715+
### Closing note mappings
716+
717+
Mappings used in closing note window.
718+
719+
#### **org_note_finalize**
720+
*mapped to*: `<C-c>`<br />
721+
Save note window content as closing note for a headline. Ignores first comment (if exists)
722+
#### **org_note_kill**
723+
*mapped to*: `<Leader>ok`<br />
724+
Close note window without saving anything
725+
#### **org_capture_show_help**
726+
*mapped to*: `g?`<br />
727+
Show help popup with mappings
728+
729+
These mappings live under `mappings.capture`, and can be changed like this:
730+
731+
```lua
732+
require('orgmode').setup({
733+
org_agenda_files = {'~/Dropbox/org/*', '~/my-orgs/**/*'},
734+
org_default_notes_file = '~/Dropbox/org/refile.org',
735+
mappings = {
736+
capture = {
737+
org_capture_finalize = '<Leader>w',
738+
org_capture_refile = 'R',
739+
org_capture_kill = 'Q'
740+
}
741+
}
742+
})
743+
```
744+
712745
### Org mappings
713746

714747
Mappings for `org` files.

lua/orgmode/capture/closing_note.lua

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
local utils = require('orgmode.utils')
2+
local config = require('orgmode.config')
3+
local Promise = require('orgmode.utils.promise')
4+
5+
---@class ClosingNote
6+
local ClosingNote = {}
7+
8+
function ClosingNote:new()
9+
local data = {}
10+
setmetatable(data, self)
11+
self.__index = self
12+
return data
13+
end
14+
15+
function ClosingNote:open()
16+
self._resolve_fn = nil
17+
self._close_tmp = utils.open_tmp_org_window(16, config.win_split_mode)
18+
config:setup_mappings('note')
19+
vim.api.nvim_buf_set_var(0, 'org_note', true)
20+
vim.api.nvim_buf_set_lines(0, 0, -1, false, { '# Insert note for closed todo item', '', '' })
21+
vim.schedule(function()
22+
vim.cmd('normal! G')
23+
vim.cmd('startinsert')
24+
end)
25+
26+
return Promise.new(function(resolve)
27+
self._resolve_fn = resolve
28+
end)
29+
end
30+
31+
function ClosingNote:refile()
32+
local content = vim.api.nvim_buf_get_lines(0, 0, -1, false)
33+
if content[1] and content[1]:match('#%s+') then
34+
content = { unpack(content, 2) }
35+
end
36+
if content[1] and vim.trim(content[1]) == '' then
37+
content = { unpack(content, 2) }
38+
end
39+
local fn = self._resolve_fn
40+
self._resolve_fn = nil
41+
self:kill()
42+
fn(content)
43+
end
44+
45+
function ClosingNote:kill()
46+
if self._close_tmp then
47+
self._close_tmp()
48+
self._close_tmp = nil
49+
if self._resolve_fn then
50+
local fn = self._resolve_fn
51+
self._resolve_fn = nil
52+
fn(nil)
53+
end
54+
end
55+
end
56+
57+
return ClosingNote

lua/orgmode/capture/init.lua

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ local config = require('orgmode.config')
33
local Files = require('orgmode.parser.files')
44
local File = require('orgmode.parser.file')
55
local Templates = require('orgmode.capture.templates')
6-
7-
local capture_augroup = vim.api.nvim_create_augroup('OrgCapture', { clear = true })
6+
local ClosingNote = require('orgmode.capture.closing_note')
87

98
---@class Capture
109
---@field templates Templates
10+
---@field closing_note ClosingNote
1111
local Capture = {}
1212

1313
function Capture:new()
1414
local data = {}
1515
data.templates = Templates:new()
16+
data.closing_note = ClosingNote:new()
1617
setmetatable(data, self)
1718
self.__index = self
1819
return data
@@ -68,33 +69,15 @@ end
6869
---@param template table
6970
function Capture:open_template(template)
7071
local content = self.templates:compile(template)
71-
local winnr = vim.api.nvim_get_current_win()
72-
utils.open_window(vim.fn.tempname(), 16, config.win_split_mode)
73-
vim.cmd([[setf org]])
74-
vim.cmd([[setlocal bufhidden=wipe nobuflisted nolist noswapfile nofoldenable]])
72+
local on_close = function()
73+
require('orgmode').action('capture.refile', true)
74+
end
75+
self._close_tmp = utils.open_tmp_org_window(16, config.win_split_mode, on_close)
7576
vim.api.nvim_buf_set_lines(0, 0, -1, true, content)
7677
self.templates:setup()
7778
vim.api.nvim_buf_set_var(0, 'org_template', template)
7879
vim.api.nvim_buf_set_var(0, 'org_capture', true)
79-
vim.api.nvim_buf_set_var(0, 'org_prev_window', winnr)
8080
config:setup_mappings('capture')
81-
82-
vim.api.nvim_create_autocmd('BufWipeout', {
83-
buffer = 0,
84-
group = capture_augroup,
85-
callback = function()
86-
require('orgmode').action('capture.refile', true)
87-
end,
88-
once = true,
89-
})
90-
vim.api.nvim_create_autocmd('VimLeavePre', {
91-
buffer = 0,
92-
group = capture_augroup,
93-
callback = function()
94-
require('orgmode').action('capture.refile', true)
95-
end,
96-
once = true,
97-
})
9881
end
9982

10083
---@param shortcut string
@@ -351,12 +334,9 @@ function Capture.autocomplete_refile(arg_lead)
351334
end
352335

353336
function Capture:kill()
354-
-- Clear all autocmds
355-
vim.api.nvim_create_augroup('OrgCapture', { clear = true })
356-
local prev_winnr = vim.api.nvim_buf_get_var(0, 'org_prev_window')
357-
vim.api.nvim_win_close(0, true)
358-
if prev_winnr and vim.api.nvim_win_is_valid(prev_winnr) then
359-
vim.api.nvim_set_current_win(prev_winnr)
337+
if self._close_tmp then
338+
self._close_tmp()
339+
self._close_tmp = nil
360340
end
361341
end
362342

lua/orgmode/config/defaults.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ return {
9494
org_capture_kill = '<prefix>k',
9595
org_capture_show_help = 'g?',
9696
},
97+
note = {
98+
org_note_finalize = '<C-c>',
99+
org_note_kill = '<prefix>k',
100+
org_note_show_help = 'g?',
101+
},
97102
org = {
98103
org_refile = '<prefix>r',
99104
org_timestamp_up_day = '<S-UP>',

lua/orgmode/config/mappings/init.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ return {
4848
org_capture_kill = m.action('capture.kill', { opts = { desc = 'org kill' } }),
4949
org_capture_show_help = m.action('org_mappings.show_help', { opts = { desc = 'org show help' } }),
5050
},
51+
note = {
52+
org_note_finalize = m.action('capture.closing_note.refile', { opts = { desc = 'org finalize note' } }),
53+
org_note_kill = m.action('capture.closing_note.kill', { opts = { desc = 'org kill note' } }),
54+
org_note_show_help = m.action('org_mappings.show_help', { opts = { desc = 'org show help' } }),
55+
},
5156
org = {
5257
org_refile = m.action('capture.refile_headline_to_destination', { opts = { desc = 'org refile' } }),
5358
org_timestamp_up_day = m.action(

lua/orgmode/init.lua

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,17 @@ local function action(cmd, opts)
145145
return
146146
end
147147
instance:init()
148-
if instance[parts[1]] and instance[parts[1]][parts[2]] then
149-
local item = instance[parts[1]]
150-
local method = item[parts[2]]
148+
local item = nil
149+
for i = 1, #parts - 1 do
150+
local part = parts[i]
151+
if not item then
152+
item = instance[part]
153+
else
154+
item = item[part]
155+
end
156+
end
157+
if item and item[parts[#parts]] then
158+
local method = item[parts[#parts]]
151159
local success, result = pcall(method, item, opts)
152160
if not success then
153161
if result.message then

lua/orgmode/objects/help.lua

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ local helps = {
9494
{ key = 'org_capture_refile', description = 'Save to specific destination' },
9595
{ key = 'org_capture_kill', description = 'Close without saving' },
9696
},
97+
note = {
98+
{ key = 'org_note_finalize', description = 'Save note and close the window' },
99+
{ key = 'org_note_kill', description = 'Close without saving' },
100+
},
97101
edit_src = {
98102
{ key = 'org_edit_src_abort', description = 'Abort edit special buffer changes and discard content' },
99103
{ key = 'org_edit_src_save', description = 'Apply changes from the special buffer to the source Org buffer' },
@@ -125,6 +129,11 @@ function Help._get_content_type(opts)
125129
return 'orgcapture'
126130
end
127131

132+
local has_note, is_note = pcall(vim.api.nvim_buf_get_var, 0, 'org_note')
133+
if has_note and is_note then
134+
return 'orgnote'
135+
end
136+
128137
local ft = vim.bo.filetype
129138
local prepare_func = '_prepare_' .. ft
130139
if Help[prepare_func] then
@@ -164,6 +173,27 @@ function Help._prepare_orgcapture(mappings, max_height)
164173
return content, false
165174
end
166175

176+
function Help._prepare_orgnote(mappings, max_height)
177+
local scroll_more_text = ''
178+
if (#helps.note + #helps.org) > max_height then
179+
scroll_more_text = ' (Scroll down for more)'
180+
end
181+
182+
local content = { string.format(' **Orgmode mappings Note + Org:%s**', scroll_more_text), '', ' __Note__' }
183+
for _, item in ipairs(helps.note) do
184+
local maps = mappings.note[item.key]
185+
if type(maps) == 'table' then
186+
maps = table.concat(maps, ', ')
187+
end
188+
189+
table.insert(content, string.format(' `%-12s` - %s', maps, item.description))
190+
end
191+
192+
table.insert(content, ' __Org__')
193+
194+
return content, false
195+
end
196+
167197
function Help._prepare_orgagenda(mappings, max_height)
168198
local scroll_more_text = ''
169199
if #helps.orgagenda > max_height then

lua/orgmode/org/mappings.lua

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ local utils = require('orgmode.utils')
1313
local ts_org = require('orgmode.treesitter')
1414
local ts_table = require('orgmode.treesitter.table')
1515
local EventManager = require('orgmode.events')
16+
local Promise = require('orgmode.utils.promise')
1617
local events = EventManager.event
1718

1819
---@class OrgMappings
@@ -395,13 +396,41 @@ function OrgMappings:_todo_change_state(direction)
395396
return dispatchEvent()
396397
end
397398

399+
local log_note = config.org_log_done == 'note'
400+
local log_time = config.org_log_done == 'time'
401+
local should_log_time = log_note or log_time
402+
local indent = config:get_indent(item.level + 1)
403+
404+
local get_note = function(note)
405+
if note == nil then
406+
return
407+
end
408+
409+
for i, line in ipairs(note) do
410+
note[i] = indent .. ' ' .. line
411+
end
412+
413+
table.insert(note, 1, ('%s- CLOSING NOTE %s \\\\'):format(indent, Date.now():to_wrapped_string(false)))
414+
return note
415+
end
416+
398417
local repeater_dates = item:get_repeater_dates()
399418
if #repeater_dates == 0 then
400-
local log_time = config.org_log_done == 'time'
401-
if log_time and item:is_done() and not was_done then
419+
if should_log_time and item:is_done() and not was_done then
402420
headline:add_closed_date()
421+
item = Files.get_closest_headline()
422+
423+
if log_note then
424+
dispatchEvent()
425+
return self.capture.closing_note:open():next(function(note)
426+
local valid_note = get_note(note)
427+
if valid_note then
428+
vim.fn.append(item:get_todo_note_line_number(), valid_note)
429+
end
430+
end)
431+
end
403432
end
404-
if log_time and not item:is_done() and was_done then
433+
if should_log_time and not item:is_done() and was_done then
405434
headline:remove_closed_date()
406435
end
407436
return dispatchEvent()
@@ -413,19 +442,35 @@ function OrgMappings:_todo_change_state(direction)
413442

414443
self:_change_todo_state('reset')
415444
local state_change =
416-
string.format('- State "%s" from "%s" [%s]', item.todo_keyword.value, old_state, Date.now():to_string())
417-
418-
local data = item:add_properties({ LAST_REPEAT = '[' .. Date.now():to_string() .. ']' })
419-
if data.is_new then
420-
vim.fn.append(data.end_line, data.indent .. state_change)
421-
return dispatchEvent()
422-
end
423-
item = Files.get_closest_headline()
445+
string.format('%s- State "%s" from "%s" [%s]', indent, item.todo_keyword.value, old_state, Date.now():to_string())
424446

425-
if item.properties.valid then
426-
vim.fn.append(item.properties.range.end_line, data.indent .. state_change)
427-
end
428447
dispatchEvent()
448+
return Promise.resolve()
449+
:next(function()
450+
if not log_note then
451+
return state_change
452+
end
453+
454+
return self.capture.closing_note:open():next(function(closing_note)
455+
return get_note(closing_note)
456+
end)
457+
end)
458+
:next(function(note)
459+
local data = item:add_properties({ LAST_REPEAT = '[' .. Date.now():to_string() .. ']' })
460+
if not note then
461+
return
462+
end
463+
464+
if data.is_new then
465+
vim.fn.append(data.end_line, note)
466+
return
467+
end
468+
item = Files.get_closest_headline()
469+
470+
if item.properties.valid then
471+
vim.fn.append(item.properties.range.end_line, note)
472+
end
473+
end)
429474
end
430475

431476
function OrgMappings:do_promote(whole_subtree)

0 commit comments

Comments
 (0)