Skip to content

Commit ab474fd

Browse files
authored
Merge pull request #164 from xs/recompute-checkboxes
add RecomputeCheckboxes command
2 parents c52f957 + 2e6d42f commit ab474fd

File tree

3 files changed

+223
-0
lines changed

3 files changed

+223
-0
lines changed

doc/bullets.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ GENERAL COMMANDS *bullets-commands*
8383
A blank line before/after the first/last bullet denotes
8484
the end of the list.
8585

86+
*bullets-:RecomputeCheckboxes*
87+
:RecomputeCheckboxes Recomputes all partial checkboxes in the current list.
88+
Preserves state for all checkboxes with no children and
89+
recomputes all checkboxes up to the top of the list.
90+
8691
*bullets-:BulletDemote*
8792
:BulletDemote Demotes the current bullet by indenting it and changing
8893
its bullet type to the next level defined in

plugin/bullets.vim

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,85 @@ fun! s:set_child_checkboxes(lnum, checked)
779779
endif
780780
endfun
781781

782+
" Recompute partial checkboxes of a full checkbox tree given the root lnum
783+
fun! s:recompute_checkbox_tree(lnum)
784+
if !g:bullets_nested_checkboxes
785+
return
786+
endif
787+
788+
let l:indent = indent(a:lnum)
789+
let l:bullet = s:closest_bullet_types(a:lnum, l:indent)
790+
let l:bullet = s:resolve_bullet_type(l:bullet)
791+
792+
if l:bullet.bullet_type !=# 'chk'
793+
return
794+
endif
795+
796+
" recursively recompute checkbox tree for all children, then finally self
797+
798+
let l:children = s:get_children_line_numbers(a:lnum)
799+
for l:child_nr in l:children
800+
" nb: this skips 'grandchildren' checkboxes (i.e., children who aren't
801+
" checkboxes but have checkbox children themselves), but those grandkids
802+
" will be targeted by s:recompute_checkboxes_in_range anyway
803+
call s:recompute_checkbox_tree(l:child_nr)
804+
endfor
805+
806+
807+
if empty(l:children)
808+
" if no children, preserve previous checked state
809+
" partially completed checkboxes become unchecked
810+
if empty(l:bullet) || !has_key(l:bullet, 'checkbox_marker')
811+
return
812+
endif
813+
814+
let l:checkbox_markers = split(g:bullets_checkbox_markers, '\zs')
815+
let l:partial_markers = join(l:checkbox_markers[1:-2], '')
816+
817+
if l:bullet.checkbox_marker =~# '\v[' . l:partial_markers . ']'
818+
call s:set_checkbox(a:lnum, l:checkbox_markers[0])
819+
endif
820+
else
821+
" if children exist, recompute this checkbox status
822+
let l:first_child = l:children[0]
823+
let l:completion_marker = s:sibling_checkbox_status(l:first_child)
824+
call s:set_checkbox(a:lnum, l:completion_marker)
825+
endif
826+
endfun
827+
828+
fun! s:recompute_checkboxes_in_range(start, end)
829+
if !g:bullets_nested_checkboxes
830+
return
831+
endif
832+
833+
call s:enable_bullet_cache()
834+
for l:nr in range(a:start, a:end)
835+
" find all bullets who do not have a checkbox parent
836+
let l:parent = s:get_parent(l:nr)
837+
if !empty(l:parent) && l:parent.bullet_type ==# 'chk'
838+
continue
839+
end
840+
841+
call s:recompute_checkbox_tree(l:nr)
842+
endfor
843+
call s:disable_bullet_cache()
844+
endfun
845+
846+
" Recomputes checkboxes for the whole list containing the cursor.
847+
fun! s:recompute_checkboxes()
848+
if !g:bullets_nested_checkboxes
849+
return
850+
endif
851+
852+
call s:enable_bullet_cache()
853+
let l:first_line = s:first_bullet_line(line('.'))
854+
let l:last_line = s:last_bullet_line(line('.'))
855+
if l:first_line > 0 && l:last_line > 0
856+
call s:recompute_checkboxes_in_range(l:first_line, l:last_line)
857+
endif
858+
call s:disable_bullet_cache()
859+
endfun
860+
782861
command! SelectCheckboxInside call <SID>select_checkbox(1)
783862
command! SelectCheckbox call <SID>select_checkbox(0)
784863
command! ToggleCheckbox call <SID>toggle_checkboxes_nested()
@@ -961,6 +1040,7 @@ endfun
9611040

9621041
command! -range=% RenumberSelection call <SID>renumber_selection()
9631042
command! RenumberList call <SID>renumber_whole_list()
1043+
command! RecomputeCheckboxes call <SID>recompute_checkboxes()
9641044

9651045
" --------------------------------------------------------- }}}
9661046

@@ -1107,6 +1187,9 @@ nnoremap <silent> <Plug>(bullets-renumber) :RenumberList<cr>
11071187
" Toggle checkbox
11081188
nnoremap <silent> <Plug>(bullets-toggle-checkbox) :ToggleCheckbox<cr>
11091189
1190+
" Recompute checkbox list
1191+
nnoremap <silent> <Plug>(bullets-recompute-checkboxes) :RecomputeCheckboxes<cr>
1192+
11101193
" Promote and Demote outline level
11111194
inoremap <silent> <Plug>(bullets-demote) <C-o>:BulletDemote<cr>
11121195
nnoremap <silent> <Plug>(bullets-demote) :BulletDemote<cr>

spec/checkboxes_spec.rb

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,4 +248,139 @@
248248
249249
TEXT
250250
end
251+
252+
it 'recomputes checkboxes recursively on RecomputeCheckboxes' do
253+
filename = "#{SecureRandom.hex(6)}.txt"
254+
write_file(filename, <<-TEXT)
255+
# Hello there
256+
- [ ] EXPECTED: ¼
257+
- [X] checkbox leaf
258+
- [ ] EXPECTED: CHECKED
259+
- [ ] EXPECTED: CHECKED
260+
- [ ] EXPECTED: CHECKED
261+
- [X] checkbox leaf
262+
- [X] checkbox leaf
263+
- [X] EXPECTED: ¾
264+
- [X] checkbox leaf
265+
- [X] checkbox leaf
266+
- [X] checkbox leaf
267+
- [ ] checkbox leaf
268+
- [X] EXPECTED: ½
269+
- [ ] EXPECTED: CHECKED
270+
- [ ] EXPECTED: CHECKED
271+
- [X] checkbox leaf
272+
- [½] checkbox leaf (EXPECTED: UNCHECKED)
273+
- [½] EXPECTED: UNCHECKED
274+
- [ ] checkbox leaf
275+
- [½] checkbox leaf (EXPECTED: UNCHECKED)
276+
TEXT
277+
278+
vim.edit filename
279+
vim.command 'let g:bullets_checkbox_markers=" .¼½¾X"'
280+
vim.type '9j'
281+
vim.command 'RecomputeCheckboxes'
282+
vim.write
283+
284+
file_contents = IO.read(filename)
285+
286+
expect(file_contents).to eq normalize_string_indent(<<-TEXT)
287+
# Hello there
288+
- [¼] EXPECTED: ¼
289+
- [X] checkbox leaf
290+
- [X] EXPECTED: CHECKED
291+
- [X] EXPECTED: CHECKED
292+
- [X] EXPECTED: CHECKED
293+
- [X] checkbox leaf
294+
- [X] checkbox leaf
295+
- [¾] EXPECTED: ¾
296+
- [X] checkbox leaf
297+
- [X] checkbox leaf
298+
- [X] checkbox leaf
299+
- [ ] checkbox leaf
300+
- [½] EXPECTED: ½
301+
- [X] EXPECTED: CHECKED
302+
- [X] EXPECTED: CHECKED
303+
- [X] checkbox leaf
304+
- [ ] checkbox leaf (EXPECTED: UNCHECKED)
305+
- [ ] EXPECTED: UNCHECKED
306+
- [ ] checkbox leaf
307+
- [ ] checkbox leaf (EXPECTED: UNCHECKED)
308+
309+
TEXT
310+
end
311+
312+
it 'recomputes checkboxes correctly on reindents' do
313+
filename = "#{SecureRandom.hex(6)}.txt"
314+
write_file(filename, <<-TEXT)
315+
# Hello there
316+
- [X] parent bullet
317+
- [X] first child bullet
318+
TEXT
319+
320+
vim.edit filename
321+
vim.command 'let g:bullets_checkbox_markers=" /X"'
322+
vim.type 'GA'
323+
vim.feedkeys '\<cr>'
324+
vim.command 'RecomputeCheckboxes'
325+
vim.write
326+
327+
file_contents = IO.read(filename)
328+
329+
expect(file_contents).to eq normalize_string_indent(<<-TEXT)
330+
# Hello there
331+
- [/] parent bullet
332+
- [X] first child bullet
333+
- [ ]
334+
335+
TEXT
336+
337+
vim.command 'let g:bullets_delete_last_bullet_if_empty = 2'
338+
vim.feedkeys '\<cr>'
339+
vim.command 'RecomputeCheckboxes'
340+
vim.write
341+
342+
file_contents = IO.read(filename)
343+
344+
expect(file_contents).to eq normalize_string_indent(<<-TEXT)
345+
# Hello there
346+
- [X] parent bullet
347+
- [X] first child bullet
348+
- [ ]
349+
350+
TEXT
351+
end
352+
353+
it 'handles skip-level checkbox trees' do
354+
filename = "#{SecureRandom.hex(6)}.txt"
355+
write_file(filename, <<-TEXT)
356+
# Hello there
357+
- [X] parent bullet (EXPECTED: /)
358+
- skip: not checkbox content
359+
- [ ] new root bullet (EXPECTED: /)
360+
- [ ] first child bullet
361+
- [X] first child bullet
362+
- [X] first child bullet
363+
- [ ] first child bullet
364+
TEXT
365+
366+
vim.edit filename
367+
vim.command 'let g:bullets_checkbox_markers=" /X"'
368+
vim.type '2j'
369+
vim.command 'RecomputeCheckboxes'
370+
vim.write
371+
372+
file_contents = IO.read(filename)
373+
374+
expect(file_contents).to eq normalize_string_indent(<<-TEXT)
375+
# Hello there
376+
- [/] parent bullet (EXPECTED: /)
377+
- skip: not checkbox content
378+
- [/] new root bullet (EXPECTED: /)
379+
- [ ] first child bullet
380+
- [X] first child bullet
381+
- [X] first child bullet
382+
- [ ] first child bullet
383+
384+
TEXT
385+
end
251386
end

0 commit comments

Comments
 (0)