From 23d32d968a503c1cc6b923711b90ace5802fd77c Mon Sep 17 00:00:00 2001 From: Keith Miyake Date: Fri, 21 Feb 2020 23:31:42 -0800 Subject: [PATCH 1/5] add support for alphabetical ordinal lists --- plugin/bullets.vim | 71 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/plugin/bullets.vim b/plugin/bullets.vim index 55c08f7..7fc83a8 100644 --- a/plugin/bullets.vim +++ b/plugin/bullets.vim @@ -110,6 +110,32 @@ fun! s:match_roman_list_item(input_text) \ } endfun +fun! s:match_alphabetical_list_item(input_text) + let l:abc_bullet_regex = '\v^((\s*)([A-Za-z]{1,3})(\.|\))(\s+))(.*)' + let l:matches = matchlist(a:input_text, l:abc_bullet_regex) + + if empty(l:matches) + return {} + endif + + let l:bullet_length = strlen(l:matches[1]) + let l:leading_space = l:matches[2] + let l:abc = l:matches[3] + let l:closure = l:matches[4] + let l:trailing_space = l:matches[5] + let l:text_after_bullet = l:matches[6] + + return { + \ 'bullet_type': 'abc', + \ 'bullet_length': l:bullet_length, + \ 'leading_space': l:leading_space, + \ 'trailing_space': l:trailing_space, + \ 'bullet': l:abc, + \ 'closure': l:closure, + \ 'text_after_bullet': l:text_after_bullet + \ } +endfun + fun! s:match_checkbox_bullet_item(input_text) let l:checkbox_bullet_regex = '\v(^(\s*)- \[[x ]?\](\s+))(.*)' let l:matches = matchlist(a:input_text, l:checkbox_bullet_regex) @@ -159,6 +185,7 @@ fun! s:parse_bullet(line_text) let l:chk_bullet_matches = s:match_checkbox_bullet_item(a:line_text) let l:num_bullet_matches = s:match_numeric_list_item(a:line_text) let l:rom_bullet_matches = s:match_roman_list_item(a:line_text) + let l:abc_bullet_matches = s:match_alphabetical_list_item(a:line_text) if !empty(l:chk_bullet_matches) return l:chk_bullet_matches @@ -168,6 +195,8 @@ fun! s:parse_bullet(line_text) return l:num_bullet_matches elseif !empty(l:rom_bullet_matches) return l:rom_bullet_matches + elseif !empty(l:abc_bullet_matches) + return l:abc_bullet_matches else return {} endif @@ -209,6 +238,10 @@ fun! s:next_bullet_str(bullet) let l:islower = a:bullet.bullet ==# tolower(a:bullet.bullet) let l:next_num = s:arabic2roman(s:roman2arabic(a:bullet.bullet) + 1, l:islower) return a:bullet.leading_space . l:next_num . a:bullet.closure . ' ' + elseif a:bullet.bullet_type ==# 'abc' + let l:islower = a:bullet.bullet ==# tolower(a:bullet.bullet) + let l:next_num = s:dec2abc(s:abc2dec(a:bullet.bullet) + 1, l:islower) + return a:bullet.leading_space . l:next_num . a:bullet.closure . ' ' elseif a:bullet.bullet_type ==# 'num' let l:next_num = a:bullet.bullet + 1 return a:bullet.leading_space . l:next_num . a:bullet.closure . ' ' @@ -247,6 +280,17 @@ fun! s:insert_new_bullet() let l:curr_line_num = line('.') let l:next_line_num = l:curr_line_num + g:bullets_line_spacing let l:bullet = s:detect_bullet_line(l:curr_line_num) + if l:bullet != {} && l:curr_line_num > 1 && + \ (l:bullet.bullet_type == 'rom' || l:bullet.bullet_type == 'abc') + let l:bullet_prev = s:detect_bullet_line(l:curr_line_num - 1) + if l:bullet != {} && l:bullet_prev != {} + if l:bullet.bullet_type == 'rom' + if (s:roman2arabic(l:bullet.bullet) != (s:roman2arabic(l:bullet_prev.bullet) + 1)) + let l:bullet.bullet_type = 'abc' + endif + endif + endif + endif let l:send_return = 1 let l:normal_mode = mode() ==# 'n' @@ -392,6 +436,33 @@ endfunction " Roman numerals ---------------------------------------------- }}} +" Alphabetic ordinals ----------------------------------------- {{{ + +" Alphabetic ordinal functions +" Treat alphabetic ordinals as base-26 numbers to make things easy +" +fun! s:abc2dec(abc) + let l:abc = tolower(a:abc) + let l:dec = char2nr(l:abc[0]) - char2nr('a') + 1 + if len(l:abc) == 1 + return l:dec + else + return float2nr(pow(26, len(l:abc) - 1)) * l:dec + s:abc2dec(l:abc[1:len(l:abc) - 1]) + endif +endfun + +fun! s:dec2abc(dec, islower) + let l:a = a:islower ? 'a' : 'A' + let l:abc = nr2char(a:dec % 26 + char2nr(l:a) - 1) + let l:dec = a:dec / 26 + if l:dec == 0 + return l:abc + else + return s:dec2abc(l:dec, a:islower) . l:abc + endif +endfun +" Alphabetic ordinals ----------------------------------------- }}} + " Renumbering --------------------------------------------- {{{ fun! s:renumber_selection() let l:selection_lines = s:get_visual_selection_lines() From d8fa65448857d954d2aab78a48c4149b265c9b81 Mon Sep 17 00:00:00 2001 From: Keith Miyake Date: Sat, 22 Feb 2020 00:48:01 -0800 Subject: [PATCH 2/5] reduce alpha to 2 character max and fix regexp to filter mixed case bullets --- plugin/bullets.vim | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/plugin/bullets.vim b/plugin/bullets.vim index 7fc83a8..c383421 100644 --- a/plugin/bullets.vim +++ b/plugin/bullets.vim @@ -111,7 +111,7 @@ fun! s:match_roman_list_item(input_text) endfun fun! s:match_alphabetical_list_item(input_text) - let l:abc_bullet_regex = '\v^((\s*)([A-Za-z]{1,3})(\.|\))(\s+))(.*)' + let l:abc_bullet_regex = '\v^((\s*)(\u{1,2}|\l{1,2})(\.|\))(\s+))(.*)' let l:matches = matchlist(a:input_text, l:abc_bullet_regex) if empty(l:matches) @@ -120,7 +120,7 @@ fun! s:match_alphabetical_list_item(input_text) let l:bullet_length = strlen(l:matches[1]) let l:leading_space = l:matches[2] - let l:abc = l:matches[3] + let l:abc = l:matches[3] let l:closure = l:matches[4] let l:trailing_space = l:matches[5] let l:text_after_bullet = l:matches[6] @@ -281,14 +281,11 @@ fun! s:insert_new_bullet() let l:next_line_num = l:curr_line_num + g:bullets_line_spacing let l:bullet = s:detect_bullet_line(l:curr_line_num) if l:bullet != {} && l:curr_line_num > 1 && - \ (l:bullet.bullet_type == 'rom' || l:bullet.bullet_type == 'abc') + \ (l:bullet.bullet_type ==# 'rom' || l:bullet.bullet_type ==# 'abc') let l:bullet_prev = s:detect_bullet_line(l:curr_line_num - 1) - if l:bullet != {} && l:bullet_prev != {} - if l:bullet.bullet_type == 'rom' - if (s:roman2arabic(l:bullet.bullet) != (s:roman2arabic(l:bullet_prev.bullet) + 1)) - let l:bullet.bullet_type = 'abc' - endif - endif + if l:bullet_prev != {} && l:bullet.bullet_type ==# 'rom' && + \ (s:roman2arabic(l:bullet.bullet) != (s:roman2arabic(l:bullet_prev.bullet) + 1)) + let l:bullet.bullet_type = 'abc' endif endif let l:send_return = 1 From 4619ed31a49ea34299a879161dc0f25eb6af2cb9 Mon Sep 17 00:00:00 2001 From: Keith Miyake Date: Sat, 22 Feb 2020 18:03:09 -0800 Subject: [PATCH 3/5] fix off-by-one error on alpha lists and add option for maximum number of characters in alpha list numbers (def: 2) --- plugin/bullets.vim | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/plugin/bullets.vim b/plugin/bullets.vim index c383421..93ca133 100644 --- a/plugin/bullets.vim +++ b/plugin/bullets.vim @@ -44,6 +44,17 @@ end if !exists('g:bullets_pad_right') let g:bullets_pad_right = 1 end + +if !exists('g:bullets_max_alpha_characters') + let g:bullets_max_alpha_characters = 2 +end +" calculate the decimal equivalent to the last alphabetical list item +let s:power = g:bullets_max_alpha_characters +let s:abc_max = -1 +while s:power >= 0 + let s:abc_max += pow(26,s:power) + let s:power -= 1 +endwhile " ------------------------------------------------------ }}} " Bullet type detection ---------------------------------------- {{{ @@ -111,7 +122,12 @@ fun! s:match_roman_list_item(input_text) endfun fun! s:match_alphabetical_list_item(input_text) - let l:abc_bullet_regex = '\v^((\s*)(\u{1,2}|\l{1,2})(\.|\))(\s+))(.*)' + let l:abc_bullet_regex = join([ + \ '\v^((\s*)(\u{1,', + \ string(g:bullets_max_alpha_characters), + \ '}|\l{1,', + \ string(g:bullets_max_alpha_characters), + \ '})(\.|\))(\s+))(.*)'], '') let l:matches = matchlist(a:input_text, l:abc_bullet_regex) if empty(l:matches) @@ -299,7 +315,7 @@ fun! s:insert_new_bullet() " We don't want to create a new bullet if the previous one was not used, " instead we want to delete the empty bullet - like word processors do call s:delete_empty_bullet(l:curr_line_num) - else + elseif !(l:bullet.bullet_type ==# 'abc' && s:abc2dec(l:bullet.bullet) + 1 > s:abc_max) let l:next_bullet_list = [s:pad_to_length(s:next_bullet_str(l:bullet), l:bullet.bullet_length)] @@ -450,12 +466,12 @@ endfun fun! s:dec2abc(dec, islower) let l:a = a:islower ? 'a' : 'A' - let l:abc = nr2char(a:dec % 26 + char2nr(l:a) - 1) - let l:dec = a:dec / 26 - if l:dec == 0 + let l:rem = (a:dec - 1) % 26 + let l:abc = nr2char(l:rem + char2nr(l:a)) + if a:dec <= 26 return l:abc else - return s:dec2abc(l:dec, a:islower) . l:abc + return s:dec2abc((a:dec - 1)/ 26, a:islower) . l:abc endif endfun " Alphabetic ordinals ----------------------------------------- }}} From 44e97536faa58156ccaaefa48f018bd8dd24a98a Mon Sep 17 00:00:00 2001 From: Keith Miyake Date: Sat, 22 Feb 2020 18:12:46 -0800 Subject: [PATCH 4/5] README: check off alphabetic lists on TO-DO --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 894b28d..645aeb3 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ Capybara integration testing. ❤️ - [x] attempt to keep the same total bullet width even as number width varies (right padding) - [x] detect lists that have multiline bullets (should have no empty lines between lines). -- [ ] add alphabetic list +- [x] add alphabetic list - [ ] allow user to define a global var with custom bullets - [ ] create a text object for bullet list indentation - [ ] support for intelligent alphanumeric indented bullets e.g. 1. \t a. \t 1. From 8b8c1a23e6fc223b982c2351bbf96d6b25c3bd28 Mon Sep 17 00:00:00 2001 From: Keith Miyake Date: Sat, 22 Feb 2020 18:13:18 -0800 Subject: [PATCH 5/5] add some alphabetic list tests --- spec/bullets_spec.rb | 123 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/spec/bullets_spec.rb b/spec/bullets_spec.rb index 84b79db..e9b2c1f 100644 --- a/spec/bullets_spec.rb +++ b/spec/bullets_spec.rb @@ -249,6 +249,129 @@ TEXT end + it 'adds a new alphabetical list bullet' do + filename = "#{SecureRandom.hex(6)}.txt" + write_file(filename, <<-TEXT) + # Hello there + A. this is the first bullet + TEXT + + vim.edit filename + vim.type 'GA' + vim.feedkeys '\' + vim.type 'second bullet' + vim.feedkeys '\' + vim.type 'third bullet' + vim.feedkeys '\' + vim.type 'fourth bullet' + vim.feedkeys '\' + vim.type 'fifth bullet' + vim.write + + file_contents = IO.read(filename) + + expect(file_contents).to eq normalize_string_indent(<<-TEXT) + # Hello there + A. this is the first bullet + B. second bullet + C. third bullet + D. fourth bullet + E. fifth bullet\n + TEXT + end + + it 'adds a new lower case alphabetical list bullet' do + filename = "#{SecureRandom.hex(6)}.txt" + write_file(filename, <<-TEXT) + # Hello there + a. this is the first bullet + TEXT + + vim.edit filename + vim.type 'GA' + vim.feedkeys '\' + vim.type 'second bullet' + vim.feedkeys '\' + vim.type 'third bullet' + vim.feedkeys '\' + vim.type 'fourth bullet' + vim.feedkeys '\' + vim.type 'fifth bullet' + vim.write + + file_contents = IO.read(filename) + + expect(file_contents).to eq normalize_string_indent(<<-TEXT) + # Hello there + a. this is the first bullet + b. second bullet + c. third bullet + d. fourth bullet + e. fifth bullet\n + TEXT + end + + it 'adds a new alphabetical list bullet and loops at z' do + filename = "#{SecureRandom.hex(6)}.txt" + write_file(filename, <<-TEXT) + # Hello there + y. this is the first bullet + TEXT + + vim.edit filename + vim.type 'GA' + vim.feedkeys '\' + vim.type 'second bullet' + vim.feedkeys '\' + vim.type 'third bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'AY. fourth bullet' + vim.feedkeys '\' + vim.type 'fifth bullet' + vim.feedkeys '\' + vim.type 'sixth bullet' + vim.write + + file_contents = IO.read(filename) + + expect(file_contents).to eq normalize_string_indent(<<-TEXT) + # Hello there + y. this is the first bullet + z. second bullet + aa. third bullet + + AY. fourth bullet + AZ. fifth bullet + BA. sixth bullet\n + TEXT + end + + it 'stops adding more alphabetical items after g:bullets_max_alpha_characters (2)' do + filename = "#{SecureRandom.hex(6)}.txt" + write_file(filename, <<-TEXT) + # Hello there + zy. this is the first bullet + TEXT + + vim.edit filename + vim.type 'GA' + vim.feedkeys '\' + vim.type 'second bullet' + vim.feedkeys '\' + vim.type 'not a bullet' + vim.write + + file_contents = IO.read(filename) + + expect(file_contents).to eq normalize_string_indent(<<-TEXT) + # Hello there + zy. this is the first bullet + zz. second bullet + not a bullet\n + TEXT + end + it 'deletes the last bullet if it is empty' do filename = "#{SecureRandom.hex(6)}.txt" write_file(filename, <<-TEXT)