Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/bullets.txt
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ GENERAL COMMANDS *bullets-commands*
A blank line before/after the first/last bullet denotes
the end of the list.

*bullets-:RecomputeCheckboxes*
:RecomputeCheckboxes Recomputes all partial checkboxes in the current list.
Preserves state for all checkboxes with no children and
recomputes all checkboxes up to the top of the list.

*bullets-:BulletDemote*
:BulletDemote Demotes the current bullet by indenting it and changing
its bullet type to the next level defined in
Expand Down
83 changes: 83 additions & 0 deletions plugin/bullets.vim
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,85 @@ fun! s:set_child_checkboxes(lnum, checked)
endif
endfun

" Recompute partial checkboxes of a full checkbox tree given the root lnum
fun! s:recompute_checkbox_tree(lnum)
if !g:bullets_nested_checkboxes
return
endif

let l:indent = indent(a:lnum)
let l:bullet = s:closest_bullet_types(a:lnum, l:indent)
let l:bullet = s:resolve_bullet_type(l:bullet)

if l:bullet.bullet_type !=# 'chk'
return
endif

" recursively recompute checkbox tree for all children, then finally self

let l:children = s:get_children_line_numbers(a:lnum)
for l:child_nr in l:children
" nb: this skips 'grandchildren' checkboxes (i.e., children who aren't
" checkboxes but have checkbox children themselves), but those grandkids
" will be targeted by s:recompute_checkboxes_in_range anyway
call s:recompute_checkbox_tree(l:child_nr)
endfor


if empty(l:children)
" if no children, preserve previous checked state
" partially completed checkboxes become unchecked
if empty(l:bullet) || !has_key(l:bullet, 'checkbox_marker')
return
endif

let l:checkbox_markers = split(g:bullets_checkbox_markers, '\zs')
let l:partial_markers = join(l:checkbox_markers[1:-2], '')

if l:bullet.checkbox_marker =~# '\v[' . l:partial_markers . ']'
call s:set_checkbox(a:lnum, l:checkbox_markers[0])
endif
else
" if children exist, recompute this checkbox status
let l:first_child = l:children[0]
let l:completion_marker = s:sibling_checkbox_status(l:first_child)
call s:set_checkbox(a:lnum, l:completion_marker)
endif
endfun

fun! s:recompute_checkboxes_in_range(start, end)
if !g:bullets_nested_checkboxes
return
endif

call s:enable_bullet_cache()
for l:nr in range(a:start, a:end)
" find all bullets who do not have a checkbox parent
let l:parent = s:get_parent(l:nr)
if !empty(l:parent) && l:parent.bullet_type ==# 'chk'
continue
end

call s:recompute_checkbox_tree(l:nr)
endfor
call s:disable_bullet_cache()
endfun

" Recomputes checkboxes for the whole list containing the cursor.
fun! s:recompute_checkboxes()
if !g:bullets_nested_checkboxes
return
endif

call s:enable_bullet_cache()
let l:first_line = s:first_bullet_line(line('.'))
let l:last_line = s:last_bullet_line(line('.'))
if l:first_line > 0 && l:last_line > 0
call s:recompute_checkboxes_in_range(l:first_line, l:last_line)
endif
call s:disable_bullet_cache()
endfun

command! SelectCheckboxInside call <SID>select_checkbox(1)
command! SelectCheckbox call <SID>select_checkbox(0)
command! ToggleCheckbox call <SID>toggle_checkboxes_nested()
Expand Down Expand Up @@ -961,6 +1040,7 @@ endfun

command! -range=% RenumberSelection call <SID>renumber_selection()
command! RenumberList call <SID>renumber_whole_list()
command! RecomputeCheckboxes call <SID>recompute_checkboxes()

" --------------------------------------------------------- }}}

Expand Down Expand Up @@ -1107,6 +1187,9 @@ nnoremap <silent> <Plug>(bullets-renumber) :RenumberList<cr>
" Toggle checkbox
nnoremap <silent> <Plug>(bullets-toggle-checkbox) :ToggleCheckbox<cr>

" Recompute checkbox list
nnoremap <silent> <Plug>(bullets-recompute-checkboxes) :RecomputeCheckboxes<cr>

" Promote and Demote outline level
inoremap <silent> <Plug>(bullets-demote) <C-o>:BulletDemote<cr>
nnoremap <silent> <Plug>(bullets-demote) :BulletDemote<cr>
Expand Down
135 changes: 135 additions & 0 deletions spec/checkboxes_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,139 @@

TEXT
end

it 'recomputes checkboxes recursively on RecomputeCheckboxes' do
filename = "#{SecureRandom.hex(6)}.txt"
write_file(filename, <<-TEXT)
# Hello there
- [ ] EXPECTED: ¼
- [X] checkbox leaf
- [ ] EXPECTED: CHECKED
- [ ] EXPECTED: CHECKED
- [ ] EXPECTED: CHECKED
- [X] checkbox leaf
- [X] checkbox leaf
- [X] EXPECTED: ¾
- [X] checkbox leaf
- [X] checkbox leaf
- [X] checkbox leaf
- [ ] checkbox leaf
- [X] EXPECTED: ½
- [ ] EXPECTED: CHECKED
- [ ] EXPECTED: CHECKED
- [X] checkbox leaf
- [½] checkbox leaf (EXPECTED: UNCHECKED)
- [½] EXPECTED: UNCHECKED
- [ ] checkbox leaf
- [½] checkbox leaf (EXPECTED: UNCHECKED)
TEXT

vim.edit filename
vim.command 'let g:bullets_checkbox_markers=" .¼½¾X"'
vim.type '9j'
vim.command 'RecomputeCheckboxes'
vim.write

file_contents = IO.read(filename)

expect(file_contents).to eq normalize_string_indent(<<-TEXT)
# Hello there
- [¼] EXPECTED: ¼
- [X] checkbox leaf
- [X] EXPECTED: CHECKED
- [X] EXPECTED: CHECKED
- [X] EXPECTED: CHECKED
- [X] checkbox leaf
- [X] checkbox leaf
- [¾] EXPECTED: ¾
- [X] checkbox leaf
- [X] checkbox leaf
- [X] checkbox leaf
- [ ] checkbox leaf
- [½] EXPECTED: ½
- [X] EXPECTED: CHECKED
- [X] EXPECTED: CHECKED
- [X] checkbox leaf
- [ ] checkbox leaf (EXPECTED: UNCHECKED)
- [ ] EXPECTED: UNCHECKED
- [ ] checkbox leaf
- [ ] checkbox leaf (EXPECTED: UNCHECKED)

TEXT
end

it 'recomputes checkboxes correctly on reindents' do
filename = "#{SecureRandom.hex(6)}.txt"
write_file(filename, <<-TEXT)
# Hello there
- [X] parent bullet
- [X] first child bullet
TEXT

vim.edit filename
vim.command 'let g:bullets_checkbox_markers=" /X"'
vim.type 'GA'
vim.feedkeys '\<cr>'
vim.command 'RecomputeCheckboxes'
vim.write

file_contents = IO.read(filename)

expect(file_contents).to eq normalize_string_indent(<<-TEXT)
# Hello there
- [/] parent bullet
- [X] first child bullet
- [ ]

TEXT

vim.command 'let g:bullets_delete_last_bullet_if_empty = 2'
vim.feedkeys '\<cr>'
vim.command 'RecomputeCheckboxes'
vim.write

file_contents = IO.read(filename)

expect(file_contents).to eq normalize_string_indent(<<-TEXT)
# Hello there
- [X] parent bullet
- [X] first child bullet
- [ ]

TEXT
end

it 'handles skip-level checkbox trees' do
filename = "#{SecureRandom.hex(6)}.txt"
write_file(filename, <<-TEXT)
# Hello there
- [X] parent bullet (EXPECTED: /)
- skip: not checkbox content
- [ ] new root bullet (EXPECTED: /)
- [ ] first child bullet
- [X] first child bullet
- [X] first child bullet
- [ ] first child bullet
TEXT

vim.edit filename
vim.command 'let g:bullets_checkbox_markers=" /X"'
vim.type '2j'
vim.command 'RecomputeCheckboxes'
vim.write

file_contents = IO.read(filename)

expect(file_contents).to eq normalize_string_indent(<<-TEXT)
# Hello there
- [/] parent bullet (EXPECTED: /)
- skip: not checkbox content
- [/] new root bullet (EXPECTED: /)
- [ ] first child bullet
- [X] first child bullet
- [X] first child bullet
- [ ] first child bullet

TEXT
end
end
Loading