Skip to content

Commit 495d1e9

Browse files
Merge pull request #287 from TravonteD/checkbox-toast
Add support for list-item cookies
2 parents 4ce3262 + 34bc997 commit 495d1e9

File tree

5 files changed

+141
-1
lines changed

5 files changed

+141
-1
lines changed

lua/orgmode/org/mappings.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ local ts_utils = require('nvim-treesitter.ts_utils')
1212
local utils = require('orgmode.utils')
1313
local tree_utils = require('orgmode.utils.treesitter')
1414
local Headline = require('orgmode.treesitter.headline')
15+
local List = require('orgmode.treesitter.list')
1516

1617
---@class OrgMappings
1718
---@field capture Capture
@@ -162,6 +163,8 @@ function OrgMappings:toggle_checkbox()
162163
checkbox = checkbox:gsub('%[[%sXx%-]?%]$', new_val)
163164
local new_line = line:gsub(pattern, checkbox)
164165
vim.fn.setline('.', new_line)
166+
local list = List:new(tree_utils.closest_list())
167+
list:update_parent_cookie()
165168
end
166169

167170
function OrgMappings:timestamp_up_day()

lua/orgmode/treesitter/headline.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,14 @@ function Headline:remove_closed_date()
145145
end
146146
end
147147

148+
function Headline:cookie()
149+
local cookie = self:parse('%[%d?/%d?%]')
150+
if cookie then
151+
return cookie
152+
end
153+
return self:parse('%[%d?%d?%d?%%%]')
154+
end
155+
148156
-- @return tsnode, string
149157
function Headline:parse(pattern)
150158
local match = ''

lua/orgmode/treesitter/list.lua

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
local ts_utils = require('nvim-treesitter.ts_utils')
2+
local tree_utils = require('orgmode.utils.treesitter')
3+
local query = vim.treesitter.query
4+
local Headline = require('orgmode.treesitter.headline')
5+
6+
local List = {}
7+
8+
function List:new(list_node)
9+
local data = { list = list_node }
10+
setmetatable(data, self)
11+
self.__index = self
12+
return data
13+
end
14+
15+
-- Updates the cookie of the immediate parent
16+
-- This always checks for a parent list first
17+
-- then for a headline.
18+
function List:parent_cookie()
19+
local parent_list = tree_utils.find_list(self.list:parent())
20+
if parent_list then
21+
-- We only care about the cookie if it's at the top
22+
local top_item = parent_list:named_child(0)
23+
local content = top_item:field('contents')[1]
24+
-- The cookie should be the last thing on the line
25+
local cookie_node = content:named_child(content:named_child_count() - 1)
26+
27+
local text = query.get_node_text(cookie_node, 0)
28+
if text:match('%[%d?/%d?%]') or text:match('%[%d?%d?%d?%%%]') then
29+
return cookie_node
30+
end
31+
end
32+
33+
local parent_header = Headline:new(tree_utils.closest_headline())
34+
return parent_header:cookie()
35+
end
36+
37+
function List:update_parent_cookie()
38+
local parent_cookie = self:parent_cookie()
39+
if not parent_cookie then
40+
return
41+
end
42+
43+
local checkboxes = self:checkboxes()
44+
local checked_boxes = vim.tbl_filter(function(box)
45+
return box:match('%[%w%]')
46+
end, checkboxes)
47+
local new_status
48+
if query.get_node_text(parent_cookie, 0):find('%%') then
49+
new_status = ('[%d%%]'):format((#checked_boxes / #checkboxes) * 100)
50+
else
51+
new_status = ('[%d/%d]'):format(#checked_boxes, #checkboxes)
52+
end
53+
tree_utils.set_node_text(parent_cookie, new_status)
54+
end
55+
56+
function List:checkboxes()
57+
return vim.tbl_map(function(node)
58+
local text = query.get_node_text(node, 0)
59+
return text:match('%[.%]')
60+
end, ts_utils.get_named_children(self.list))
61+
end
62+
63+
return List

lua/orgmode/utils/treesitter.lua

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
local ts_utils = require('nvim-treesitter.ts_utils')
22
local config = require('orgmode.config')
3+
local query = vim.treesitter.query
34
local M = {}
45

6+
function M.current_node()
7+
local window = vim.api.nvim_get_current_win()
8+
return ts_utils.get_node_at_cursor(window)
9+
end
10+
511
-- walks the tree to find a headline
612
function M.find_headline(node)
713
if node:type() == 'headline' then
@@ -19,7 +25,22 @@ end
1925
-- returns the nearest headline
2026
function M.closest_headline()
2127
vim.treesitter.get_parser(0, 'org'):parse()
22-
return M.find_headline(ts_utils.get_node_at_cursor(vim.api.nvim_get_current_win()))
28+
return M.find_headline(M.current_node())
29+
end
30+
31+
function M.find_list(node)
32+
if node:type() == 'list' then
33+
return node
34+
end
35+
if node:type() == 'body' then
36+
return nil
37+
end
38+
return M.find_list(node:parent())
39+
end
40+
41+
function M.closest_list()
42+
vim.treesitter.get_parser(0, 'org'):parse()
43+
return M.find_list(M.current_node())
2344
end
2445

2546
-- @param front_trim boolean

tests/plenary/ui/mappings_spec.lua

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,4 +1596,49 @@ describe('Mappings', function()
15961596
'*** to be refiled',
15971597
}, vim.api.nvim_buf_get_lines(0, 0, 5, false))
15981598
end)
1599+
1600+
it('should update the checklist cookies on a headline', function()
1601+
helpers.load_file_content({
1602+
'* Test orgmode [/]',
1603+
'- [ ] checkbox item',
1604+
'- [ ] checkbox item',
1605+
})
1606+
vim.fn.cursor(2, 1)
1607+
vim.cmd([[exe "norm \<C-space>"]])
1608+
assert.are.same('* Test orgmode [1/2]', vim.fn.getline(1))
1609+
end)
1610+
1611+
it('should update the checklist cookies on a parent list', function()
1612+
helpers.load_file_content({
1613+
'- Test orgmode [/]',
1614+
' - [ ] checkbox item',
1615+
' - [ ] checkbox item',
1616+
})
1617+
vim.fn.cursor(2, 1)
1618+
vim.cmd([[exe "norm \<C-space>"]])
1619+
assert.are.same('- Test orgmode [1/2]', vim.fn.getline(1))
1620+
end)
1621+
1622+
it('should update the checklist cookies with a percentage within a headline', function()
1623+
helpers.load_file_content({
1624+
'* Test orgmode [%]',
1625+
'- [ ] checkbox item',
1626+
'- [ ] checkbox item',
1627+
})
1628+
vim.fn.cursor(2, 1)
1629+
vim.cmd([[exe "norm \<C-space>"]])
1630+
assert.are.same('* Test orgmode [50%]', vim.fn.getline(1))
1631+
end)
1632+
1633+
it('should update the checklist cookies with a percentage within a nested list', function()
1634+
helpers.load_file_content({
1635+
'- Test orgmode [%]',
1636+
' - [ ] checkbox item',
1637+
' - [ ] checkbox item',
1638+
' - [ ] checkbox item',
1639+
})
1640+
vim.fn.cursor(2, 1)
1641+
vim.cmd([[exe "norm \<C-space>"]])
1642+
assert.are.same('- Test orgmode [33%]', vim.fn.getline(1))
1643+
end)
15991644
end)

0 commit comments

Comments
 (0)