Skip to content

Commit 5454d92

Browse files
authored
Merge pull request #47 from kaymmm/alpha
add alphabetic ordinal support
2 parents ad4d3ec + a6d7c32 commit 5454d92

File tree

3 files changed

+209
-2
lines changed

3 files changed

+209
-2
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ Capybara integration testing. ❤️
9393
- [x] attempt to keep the same total bullet width even as number width varies (right padding)
9494
- [x] detect lists that have multiline bullets (should have no empty lines between
9595
lines).
96-
- [ ] add alphabetic list
96+
- [x] add alphabetic list
9797
- [ ] allow user to define a global var with custom bullets
9898
- [ ] create a text object for bullet list indentation
9999
- [ ] support for intelligent alphanumeric indented bullets e.g. 1. \t a. \t 1.

plugin/bullets.vim

+85-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@ end
4444
if !exists('g:bullets_pad_right')
4545
let g:bullets_pad_right = 1
4646
end
47+
48+
if !exists('g:bullets_max_alpha_characters')
49+
let g:bullets_max_alpha_characters = 2
50+
end
51+
" calculate the decimal equivalent to the last alphabetical list item
52+
let s:power = g:bullets_max_alpha_characters
53+
let s:abc_max = -1
54+
while s:power >= 0
55+
let s:abc_max += pow(26,s:power)
56+
let s:power -= 1
57+
endwhile
4758
" ------------------------------------------------------ }}}
4859

4960
" Bullet type detection ---------------------------------------- {{{
@@ -110,6 +121,37 @@ fun! s:match_roman_list_item(input_text)
110121
\ }
111122
endfun
112123

124+
fun! s:match_alphabetical_list_item(input_text)
125+
let l:abc_bullet_regex = join([
126+
\ '\v^((\s*)(\u{1,',
127+
\ string(g:bullets_max_alpha_characters),
128+
\ '}|\l{1,',
129+
\ string(g:bullets_max_alpha_characters),
130+
\ '})(\.|\))(\s+))(.*)'], '')
131+
let l:matches = matchlist(a:input_text, l:abc_bullet_regex)
132+
133+
if empty(l:matches)
134+
return {}
135+
endif
136+
137+
let l:bullet_length = strlen(l:matches[1])
138+
let l:leading_space = l:matches[2]
139+
let l:abc = l:matches[3]
140+
let l:closure = l:matches[4]
141+
let l:trailing_space = l:matches[5]
142+
let l:text_after_bullet = l:matches[6]
143+
144+
return {
145+
\ 'bullet_type': 'abc',
146+
\ 'bullet_length': l:bullet_length,
147+
\ 'leading_space': l:leading_space,
148+
\ 'trailing_space': l:trailing_space,
149+
\ 'bullet': l:abc,
150+
\ 'closure': l:closure,
151+
\ 'text_after_bullet': l:text_after_bullet
152+
\ }
153+
endfun
154+
113155
fun! s:match_checkbox_bullet_item(input_text)
114156
let l:checkbox_bullet_regex = '\v(^(\s*)- \[[x ]?\](\s+))(.*)'
115157
let l:matches = matchlist(a:input_text, l:checkbox_bullet_regex)
@@ -159,6 +201,7 @@ fun! s:parse_bullet(line_text)
159201
let l:chk_bullet_matches = s:match_checkbox_bullet_item(a:line_text)
160202
let l:num_bullet_matches = s:match_numeric_list_item(a:line_text)
161203
let l:rom_bullet_matches = s:match_roman_list_item(a:line_text)
204+
let l:abc_bullet_matches = s:match_alphabetical_list_item(a:line_text)
162205

163206
if !empty(l:chk_bullet_matches)
164207
return l:chk_bullet_matches
@@ -168,6 +211,8 @@ fun! s:parse_bullet(line_text)
168211
return l:num_bullet_matches
169212
elseif !empty(l:rom_bullet_matches)
170213
return l:rom_bullet_matches
214+
elseif !empty(l:abc_bullet_matches)
215+
return l:abc_bullet_matches
171216
else
172217
return {}
173218
endif
@@ -209,6 +254,10 @@ fun! s:next_bullet_str(bullet)
209254
let l:islower = a:bullet.bullet ==# tolower(a:bullet.bullet)
210255
let l:next_num = s:arabic2roman(s:roman2arabic(a:bullet.bullet) + 1, l:islower)
211256
return a:bullet.leading_space . l:next_num . a:bullet.closure . ' '
257+
elseif a:bullet.bullet_type ==# 'abc'
258+
let l:islower = a:bullet.bullet ==# tolower(a:bullet.bullet)
259+
let l:next_num = s:dec2abc(s:abc2dec(a:bullet.bullet) + 1, l:islower)
260+
return a:bullet.leading_space . l:next_num . a:bullet.closure . ' '
212261
elseif a:bullet.bullet_type ==# 'num'
213262
let l:next_num = a:bullet.bullet + 1
214263
return a:bullet.leading_space . l:next_num . a:bullet.closure . ' '
@@ -247,6 +296,14 @@ fun! s:insert_new_bullet()
247296
let l:curr_line_num = line('.')
248297
let l:next_line_num = l:curr_line_num + g:bullets_line_spacing
249298
let l:bullet = s:detect_bullet_line(l:curr_line_num)
299+
if l:bullet != {} && l:curr_line_num > 1 &&
300+
\ (l:bullet.bullet_type ==# 'rom' || l:bullet.bullet_type ==# 'abc')
301+
let l:bullet_prev = s:detect_bullet_line(l:curr_line_num - 1)
302+
if l:bullet_prev != {} && l:bullet.bullet_type ==# 'rom' &&
303+
\ (s:roman2arabic(l:bullet.bullet) != (s:roman2arabic(l:bullet_prev.bullet) + 1))
304+
let l:bullet.bullet_type = 'abc'
305+
endif
306+
endif
250307
let l:send_return = 1
251308
let l:normal_mode = mode() ==# 'n'
252309

@@ -258,7 +315,7 @@ fun! s:insert_new_bullet()
258315
" We don't want to create a new bullet if the previous one was not used,
259316
" instead we want to delete the empty bullet - like word processors do
260317
call s:delete_empty_bullet(l:curr_line_num)
261-
else
318+
elseif !(l:bullet.bullet_type ==# 'abc' && s:abc2dec(l:bullet.bullet) + 1 > s:abc_max)
262319

263320
let l:next_bullet_list = [s:pad_to_length(s:next_bullet_str(l:bullet), l:bullet.bullet_length)]
264321

@@ -392,6 +449,33 @@ endfunction
392449

393450
" Roman numerals ---------------------------------------------- }}}
394451

452+
" Alphabetic ordinals ----------------------------------------- {{{
453+
454+
" Alphabetic ordinal functions
455+
" Treat alphabetic ordinals as base-26 numbers to make things easy
456+
"
457+
fun! s:abc2dec(abc)
458+
let l:abc = tolower(a:abc)
459+
let l:dec = char2nr(l:abc[0]) - char2nr('a') + 1
460+
if len(l:abc) == 1
461+
return l:dec
462+
else
463+
return float2nr(pow(26, len(l:abc) - 1)) * l:dec + s:abc2dec(l:abc[1:len(l:abc) - 1])
464+
endif
465+
endfun
466+
467+
fun! s:dec2abc(dec, islower)
468+
let l:a = a:islower ? 'a' : 'A'
469+
let l:rem = (a:dec - 1) % 26
470+
let l:abc = nr2char(l:rem + char2nr(l:a))
471+
if a:dec <= 26
472+
return l:abc
473+
else
474+
return s:dec2abc((a:dec - 1)/ 26, a:islower) . l:abc
475+
endif
476+
endfun
477+
" Alphabetic ordinals ----------------------------------------- }}}
478+
395479
" Renumbering --------------------------------------------- {{{
396480
fun! s:renumber_selection()
397481
let l:selection_lines = s:get_visual_selection_lines()

spec/bullets_spec.rb

+123
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,129 @@
249249
TEXT
250250
end
251251

252+
it 'adds a new alphabetical list bullet' do
253+
filename = "#{SecureRandom.hex(6)}.txt"
254+
write_file(filename, <<-TEXT)
255+
# Hello there
256+
A. this is the first bullet
257+
TEXT
258+
259+
vim.edit filename
260+
vim.type 'GA'
261+
vim.feedkeys '\<cr>'
262+
vim.type 'second bullet'
263+
vim.feedkeys '\<cr>'
264+
vim.type 'third bullet'
265+
vim.feedkeys '\<cr>'
266+
vim.type 'fourth bullet'
267+
vim.feedkeys '\<cr>'
268+
vim.type 'fifth bullet'
269+
vim.write
270+
271+
file_contents = IO.read(filename)
272+
273+
expect(file_contents).to eq normalize_string_indent(<<-TEXT)
274+
# Hello there
275+
A. this is the first bullet
276+
B. second bullet
277+
C. third bullet
278+
D. fourth bullet
279+
E. fifth bullet\n
280+
TEXT
281+
end
282+
283+
it 'adds a new lower case alphabetical list bullet' do
284+
filename = "#{SecureRandom.hex(6)}.txt"
285+
write_file(filename, <<-TEXT)
286+
# Hello there
287+
a. this is the first bullet
288+
TEXT
289+
290+
vim.edit filename
291+
vim.type 'GA'
292+
vim.feedkeys '\<cr>'
293+
vim.type 'second bullet'
294+
vim.feedkeys '\<cr>'
295+
vim.type 'third bullet'
296+
vim.feedkeys '\<cr>'
297+
vim.type 'fourth bullet'
298+
vim.feedkeys '\<cr>'
299+
vim.type 'fifth bullet'
300+
vim.write
301+
302+
file_contents = IO.read(filename)
303+
304+
expect(file_contents).to eq normalize_string_indent(<<-TEXT)
305+
# Hello there
306+
a. this is the first bullet
307+
b. second bullet
308+
c. third bullet
309+
d. fourth bullet
310+
e. fifth bullet\n
311+
TEXT
312+
end
313+
314+
it 'adds a new alphabetical list bullet and loops at z' do
315+
filename = "#{SecureRandom.hex(6)}.txt"
316+
write_file(filename, <<-TEXT)
317+
# Hello there
318+
y. this is the first bullet
319+
TEXT
320+
321+
vim.edit filename
322+
vim.type 'GA'
323+
vim.feedkeys '\<cr>'
324+
vim.type 'second bullet'
325+
vim.feedkeys '\<cr>'
326+
vim.type 'third bullet'
327+
vim.feedkeys '\<cr>'
328+
vim.feedkeys '\<cr>'
329+
vim.type 'AY. fourth bullet'
330+
vim.feedkeys '\<cr>'
331+
vim.type 'fifth bullet'
332+
vim.feedkeys '\<cr>'
333+
vim.type 'sixth bullet'
334+
vim.write
335+
336+
file_contents = IO.read(filename)
337+
338+
expect(file_contents).to eq normalize_string_indent(<<-TEXT)
339+
# Hello there
340+
y. this is the first bullet
341+
z. second bullet
342+
aa. third bullet
343+
344+
AY. fourth bullet
345+
AZ. fifth bullet
346+
BA. sixth bullet\n
347+
TEXT
348+
end
349+
350+
it 'stops adding more alphabetical items after g:bullets_max_alpha_characters (2)' do
351+
filename = "#{SecureRandom.hex(6)}.txt"
352+
write_file(filename, <<-TEXT)
353+
# Hello there
354+
zy. this is the first bullet
355+
TEXT
356+
357+
vim.edit filename
358+
vim.type 'GA'
359+
vim.feedkeys '\<cr>'
360+
vim.type 'second bullet'
361+
vim.feedkeys '\<cr>'
362+
vim.type 'not a bullet'
363+
vim.write
364+
365+
file_contents = IO.read(filename)
366+
367+
expect(file_contents).to eq normalize_string_indent(<<-TEXT)
368+
# Hello there
369+
zy. this is the first bullet
370+
zz. second bullet
371+
not a bullet\n
372+
TEXT
373+
end
374+
252375
it 'deletes the last bullet if it is empty' do
253376
filename = "#{SecureRandom.hex(6)}.txt"
254377
write_file(filename, <<-TEXT)

0 commit comments

Comments
 (0)