Skip to content

Commit 1eed3b0

Browse files
authored
Merge branch 'master' into renovate/configure
2 parents 5c915aa + 455fc88 commit 1eed3b0

5 files changed

Lines changed: 298 additions & 10 deletions

File tree

README.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,34 @@ let g:bullets_custom_mappings = [
107107
\ ]
108108
```
109109

110-
Enable/disable deleting the last empty bullet when hitting `<cr>` (insert mode) or `o` (normal mode):
110+
Enable/disable deleting or promoting the last empty bullet when hitting `<cr>` (insert mode) or `o` (normal mode):
111111

112112
```vim
113-
let g:bullets_delete_last_bullet_if_empty = 0 " default = 1
113+
" Example (| is cursor):
114+
" - text
115+
" - text
116+
" - |
117+
118+
let g:bullets_delete_last_bullet_if_empty = 1 " default = 1
119+
" - text
120+
" - text
121+
" |
122+
123+
let g:bullets_delete_last_bullet_if_empty = 0
124+
" - text
125+
" - text
126+
" -
127+
" |
128+
129+
let g:bullets_delete_last_bullet_if_empty = 2
130+
" - text
131+
" - text
132+
" - |
133+
"
134+
" again:
135+
" - text
136+
" - text
137+
" |
114138
```
115139

116140
Line spacing between bullets (1 = no blank lines, 2 = one blank line, etc.):
@@ -295,7 +319,7 @@ let g:bullets_checkbox_partials_toggle = 0
295319
* Promote a bullet (unindent it and increase the bullet level):
296320
+ NORMAL mode: `<<`
297321
+ INSERT mode: `<C-d>`
298-
+ VISUAL mode: `>`
322+
+ VISUAL mode: `<`
299323

300324
Disable default mappings:
301325

doc/bullets.txt

Lines changed: 10 additions & 1 deletion
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
@@ -148,7 +153,7 @@ To add a leader key to all mappings set the following:
148153
This will set the <space> key as leader to all default mappings.
149154

150155

151-
Disabling empty bullet deletion
156+
Empty bullet deletion
152157
-------------------------------
153158
By default bullets.vim will delete trailing empty bullets when the return key
154159
is pressed, just like modern word processors.
@@ -157,6 +162,10 @@ If you would like to turn this feature off add the following to your .vimrc
157162

158163
`let g:bullets_delete_last_bullet_if_empty = 0`
159164

165+
If you would like to promote bullet instead of deleting it, add the following to your .vimrc
166+
167+
`let g:bullets_delete_last_bullet_if_empty = 2`
168+
160169

161170
Maintain right padding on bullets
162171
---------------------------------

plugin/bullets.vim

Lines changed: 101 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ while s:power >= 0
7272
let s:power -= 1
7373
endwhile
7474

75+
if !exists('g:bullets_list_item_styles')
76+
" A list of regex patterns that are recognized as bullet points for
77+
" bullet items.
78+
let g:bullets_list_item_styles = ['-', '\*+', '\.+', '#\.', '\+', '\\item']
79+
endif
80+
7581
if !exists('g:bullets_outline_levels')
7682
" Capitalization matters: all caps will make the symbol caps, lower = lower
7783
" Standard bullets should include the marker symbol after 'std'
@@ -161,7 +167,7 @@ fun! s:parse_bullet_text(line_text)
161167
if s:bullet_cache isnot v:null
162168
let l:cached = get(s:bullet_cache, a:line_text, v:null)
163169
if l:cached isnot v:null
164-
" Return a copy so as not to break the referene
170+
" Return a copy so as not to break the reference
165171
return copy(l:cached)
166172
endif
167173
endif
@@ -177,11 +183,11 @@ fun! s:parse_bullet_text(line_text)
177183
let l:roman = empty(l:bullet) && empty(l:num) ? s:match_roman_list_item(a:line_text) : {}
178184

179185
let l:kinds = s:filter([l:bullet, l:check, l:num, l:alpha, l:roman], '!empty(v:val)')
180-
186+
181187
if s:bullet_cache isnot v:null
182188
let s:bullet_cache[a:line_text] = l:kinds
183189
endif
184-
190+
185191
return l:kinds
186192
endfun
187193

@@ -324,7 +330,9 @@ fun! s:match_checkbox_bullet_item(input_text)
324330
endfun
325331

326332
fun! s:match_bullet_list_item(input_text)
327-
let l:std_bullet_regex = '\v(^(\s*)(-|\*+|\.+|#\.|\+|\\item)(\s+))(.*)'
333+
let l:std_bullet_regex = '\v(^(\s*)('
334+
\ . join(g:bullets_list_item_styles, '|')
335+
\ . ')(\s+))(.*)'
328336
let l:matches = matchlist(a:input_text, l:std_bullet_regex)
329337

330338
if empty(l:matches)
@@ -574,7 +582,11 @@ fun! s:insert_new_bullet()
574582
" We don't want to create a new bullet if the previous one was not used,
575583
" instead we want to delete the empty bullet - like word processors do
576584
if g:bullets_delete_last_bullet_if_empty
577-
call setline(l:curr_line_num, '')
585+
if g:bullets_delete_last_bullet_if_empty == 1
586+
call setline(l:curr_line_num, '')
587+
elseif g:bullets_delete_last_bullet_if_empty == 2
588+
call <SID>change_bullet_level(1, 0)
589+
endif
578590
let l:send_return = 0
579591
endif
580592
elseif !(l:bullet.bullet_type ==# 'abc' && s:abc2dec(l:bullet.bullet) + 1 > s:abc_max)
@@ -775,6 +787,85 @@ fun! s:set_child_checkboxes(lnum, checked)
775787
endif
776788
endfun
777789

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

9581049
command! -range=% RenumberSelection call <SID>renumber_selection()
9591050
command! RenumberList call <SID>renumber_whole_list()
1051+
command! RecomputeCheckboxes call <SID>recompute_checkboxes()
9601052

9611053
" --------------------------------------------------------- }}}
9621054

@@ -967,7 +1059,7 @@ fun! s:change_line_bullet_level(direction, lnum)
9671059
if a:direction == 1
9681060
if l:curr_line != [] && indent(a:lnum) == 0
9691061
" Promoting a bullet at the highest level will delete the bullet
970-
call setline(a:lnum, l:curr_line[0].text_after_bullet)
1062+
call setline(a:lnum, l:curr_line[-1].text_after_bullet)
9711063
return
9721064
else
9731065
execute a:lnum . 'normal! <<'
@@ -1103,6 +1195,9 @@ nnoremap <silent> <Plug>(bullets-renumber) :RenumberList<cr>
11031195
" Toggle checkbox
11041196
nnoremap <silent> <Plug>(bullets-toggle-checkbox) :ToggleCheckbox<cr>
11051197
1198+
" Recompute checkbox list
1199+
nnoremap <silent> <Plug>(bullets-recompute-checkboxes) :RecomputeCheckboxes<cr>
1200+
11061201
" Promote and Demote outline level
11071202
inoremap <silent> <Plug>(bullets-demote) <C-o>:BulletDemote<cr>
11081203
nnoremap <silent> <Plug>(bullets-demote) :BulletDemote<cr>

spec/bullets_spec.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,31 @@
271271
TEXT
272272
end
273273

274+
it 'promote the last bullet when configured to' do
275+
filename = "#{SecureRandom.hex(6)}.txt"
276+
write_file(filename, <<-TEXT)
277+
# Hello there
278+
- this is the first bullet
279+
- this is the second bullet
280+
TEXT
281+
282+
vim.command 'let g:bullets_delete_last_bullet_if_empty = 2'
283+
vim.edit filename
284+
vim.type 'GA'
285+
vim.feedkeys '\<cr>'
286+
vim.feedkeys '\<cr>'
287+
vim.write
288+
289+
file_contents = IO.read(filename)
290+
291+
expect(file_contents.strip).to eq normalize_string_indent(<<-TEXT)
292+
# Hello there
293+
- this is the first bullet
294+
- this is the second bullet
295+
-
296+
TEXT
297+
end
298+
274299
it 'does not delete the last bullet when configured not to' do
275300
filename = "#{SecureRandom.hex(6)}.txt"
276301
write_file(filename, <<-TEXT)

0 commit comments

Comments
 (0)