Skip to content

Commit 89a9ac7

Browse files
committed
feature: add support for cookies
fix nvim-orgmode#81
1 parent 9cf968e commit 89a9ac7

File tree

3 files changed

+127
-14
lines changed

3 files changed

+127
-14
lines changed

lua/orgmode/org/mappings.lua

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ local utils = require('orgmode.utils')
88
local Files = require('orgmode.parser.files')
99
local config = require('orgmode.config')
1010
local Help = require('orgmode.objects.help')
11+
local syntax = require('orgmode.org.syntax')
1112

1213
---@class OrgMappings
1314
---@field capture Capture
@@ -142,18 +143,8 @@ function OrgMappings:global_cycle()
142143
return vim.cmd([[silent! norm!zx]])
143144
end
144145

145-
-- TODO: Add hierarchy
146146
function OrgMappings:toggle_checkbox()
147-
local line = vim.fn.getline('.')
148-
local pattern = '^(%s*[%-%+]%s*%[([%sXx%-]?)%])'
149-
local checkbox, state = line:match(pattern)
150-
if not checkbox then
151-
return
152-
end
153-
local new_val = vim.trim(state) == '' and '[X]' or '[ ]'
154-
checkbox = checkbox:gsub('%[[%sXx%-]?%]$', new_val)
155-
local new_line = line:gsub(pattern, checkbox)
156-
vim.fn.setline('.', new_line)
147+
syntax.update_checkbox()
157148
end
158149

159150
function OrgMappings:timestamp_up_day()
@@ -378,12 +369,12 @@ function OrgMappings:handle_return(suffix)
378369
return vim.cmd([[startinsert!]])
379370
end
380371

381-
if item.type == 'list' or item.type == 'listitem' then
372+
if vim.tbl_contains({ 'list', 'listitem', 'cookie' }, item.type) then
382373
vim.cmd([[normal! ^]])
383374
item = Files.get_current_file():get_current_node()
384375
end
385376

386-
if item.type == 'itemtext' or item.type == 'bullet' or item.type == 'checkbox' or item.type == 'description' then
377+
if vim.tbl_contains({ 'itemtext', 'bullet', 'checkbox', 'description' }, item.type) then
387378
local list_item = item.node:parent()
388379
if list_item:type() ~= 'listitem' then
389380
return
@@ -406,7 +397,8 @@ function OrgMappings:handle_return(suffix)
406397
if checkbox then
407398
table.insert(text_edits, {
408399
range = range,
409-
newText = checkbox .. ' [ ] \n',
400+
-- we initialize the checkbox checked, then use update_checkbox to toggle it off and update the tree
401+
newText = checkbox .. ' [X] \n',
410402
})
411403
elseif plain_list then
412404
table.insert(text_edits, {
@@ -442,6 +434,7 @@ function OrgMappings:handle_return(suffix)
442434
vim.lsp.util.apply_text_edits(text_edits, 0)
443435

444436
vim.fn.cursor(end_row + 2 + (add_empty_line and 1 or 0), 0) -- +1 for 0 index and +1 for next line
437+
syntax.update_checkbox(ts_utils.get_next_node(list_item))
445438
vim.cmd([[startinsert!]])
446439
end
447440
end

lua/orgmode/org/syntax.lua

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
local Files = require('orgmode.parser.files')
22
local config = require('orgmode.config')
3+
local utils = require('orgmode.utils')
4+
local ts_utils = require('nvim-treesitter.ts_utils')
5+
6+
local checkbox_query = vim.treesitter.parse_query('org', '(list (listitem (checkbox) @checkbox))')
7+
local headline_cookie_query = vim.treesitter.parse_query('org', '(headline (cookie) @cookie)')
38

49
local function load_code_blocks()
510
local file = vim.api.nvim_buf_get_name(0)
@@ -36,7 +41,115 @@ local function add_todo_keywords_to_spellgood()
3641
end
3742
end
3843

44+
local function _get_cookie_checked_and_total(parent)
45+
local parent_type = parent:type()
46+
local start_row, _, end_row, _ = parent:range()
47+
local checked, total = 0, 0
48+
for _, checkbox in checkbox_query:iter_captures(parent, 0, start_row, end_row + 1) do
49+
local closest_parent = utils.get_closest_parent_of_type(checkbox, parent_type)
50+
if closest_parent and closest_parent == parent then -- only count direct children
51+
local checkbox_text = vim.treesitter.get_node_text(checkbox, 0)
52+
if checkbox_text:match('%[[x|X]%]') then
53+
checked = checked + 1
54+
end
55+
total = total + 1
56+
end
57+
end
58+
59+
return checked, total
60+
end
61+
62+
local function _update_checkbox_text(checkbox, checked_children, total_children)
63+
local checkbox_text
64+
if total_children == nil then -- if the function is called without child information, we toggle the current value
65+
checkbox_text = vim.treesitter.get_node_text(checkbox, 0)
66+
if checkbox_text:match('%[[xX]%]') then
67+
checkbox_text = '[ ]'
68+
else
69+
checkbox_text = '[X]'
70+
end
71+
else
72+
checkbox_text = '[ ]'
73+
if checked_children == total_children then
74+
checkbox_text = '[x]'
75+
elseif checked_children > 0 then
76+
checkbox_text = '[-]'
77+
end
78+
end
79+
80+
utils.update_node_text(checkbox, { checkbox_text })
81+
end
82+
83+
local function _update_cookie_text(cookie, checked_children, total_children)
84+
local cookie_text = vim.treesitter.get_node_text(cookie, 0)
85+
86+
if total_children == nil then
87+
checked_children, total_children = 0, 0
88+
end
89+
90+
local new_cookie
91+
if cookie_text:find('/') then
92+
new_cookie = string.format('[%d/%d]', checked_children, total_children)
93+
else
94+
if total_children > 0 then
95+
new_cookie = string.format('[%d%%%%]', (100 * checked_children) / total_children)
96+
else
97+
new_cookie = '[0%%%]'
98+
end
99+
end
100+
cookie_text = cookie_text:gsub('%[.*%]', new_cookie)
101+
utils.update_node_text(cookie, { cookie_text })
102+
end
103+
104+
local function update_checkbox(node, checked_children, total_children)
105+
if not node then
106+
node = utils.get_closest_parent_of_type(ts_utils.get_node_at_cursor(0), 'listitem')
107+
if not node then
108+
return
109+
end
110+
end
111+
112+
local checkbox
113+
local cookie
114+
for child in node:iter_children() do
115+
if child:type() == 'checkbox' then
116+
checkbox = child
117+
elseif child:type() == 'itemtext' then
118+
local c_child = child:named_child(0)
119+
if c_child and c_child:type() == 'cookie' then
120+
cookie = c_child
121+
end
122+
end
123+
end
124+
125+
if checkbox then
126+
_update_checkbox_text(checkbox, checked_children, total_children)
127+
end
128+
129+
if cookie then
130+
_update_cookie_text(cookie, checked_children, total_children)
131+
end
132+
133+
local listitem_parent = utils.get_closest_parent_of_type(node:parent(), 'listitem')
134+
if listitem_parent then
135+
local list_parent = utils.get_closest_parent_of_type(node, 'list')
136+
local checked, total = _get_cookie_checked_and_total(list_parent)
137+
return update_checkbox(listitem_parent, checked, total)
138+
end
139+
140+
local section = utils.get_closest_parent_of_type(node:parent(), 'section')
141+
if section then
142+
local list_parent = utils.get_closest_parent_of_type(node, 'list')
143+
local checked, total = _get_cookie_checked_and_total(list_parent)
144+
local start_row, _, end_row, _ = section:range()
145+
for _, headline_cookie in headline_cookie_query:iter_captures(section, 0, start_row, end_row + 1) do
146+
_update_cookie_text(headline_cookie, checked, total)
147+
end
148+
end
149+
end
150+
39151
return {
40152
load_code_blocks = load_code_blocks,
41153
add_todo_keywords_to_spellgood = add_todo_keywords_to_spellgood,
154+
update_checkbox = update_checkbox,
42155
}

lua/orgmode/utils/init.lua

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,13 @@ function utils.get_node_text(node, content)
321321
end
322322
end
323323

324+
---@param node userdata
325+
---@param text string[]
326+
function utils.update_node_text(node, text)
327+
local start_row, start_col, end_row, end_col = node:range()
328+
vim.api.nvim_buf_set_text(0, start_row, start_col, end_row, end_col, text)
329+
end
330+
324331
---@param node table
325332
---@param type string
326333
---@return table

0 commit comments

Comments
 (0)