diff --git a/README.md b/README.md index b008d58..b6fcf3d 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,12 @@ Capybara integration testing. ❤️ - [x] detect lists that have multiline bullets (should have no empty lines between lines). - [x] add alphabetic list -- [ ] support for intelligent alphanumeric indented bullets e.g. 1. \t a. \t 1. +- [x] support for intelligent alphanumeric indented bullets e.g. 1. \t a. \t 1. +- [ ] update documentation for nested bullets +- [ ] support for nested numerical bullets, e.g., 1. -> 1.1 -> 1.1.1, 1.1.2 +- [ ] change nested outline levels in visual mode +- [ ] support renumbering of alphabetical, roman numerals, and nested lists +- [ ] add option to turn non-bullet lines into new bullets with `C-t`/`>>` --- diff --git a/plugin/bullets.vim b/plugin/bullets.vim index 5dce508..ac17001 100644 --- a/plugin/bullets.vim +++ b/plugin/bullets.vim @@ -1,5 +1,5 @@ " Vim plugin for automated bulleted lists -" Last Change: February 23, 2020 +" Last Change: March 2, 2020 " Maintainer: Dorian Karter " License: MIT " FileTypes: markdown, text, gitcommit @@ -55,6 +55,13 @@ while s:power >= 0 let s:abc_max += pow(26,s:power) let s:power -= 1 endwhile + +if !exists('g:bullets_outline_levels') + " Capitalization matters: all caps will make the symbol caps, lower = lower + " Standard bullets should include the marker symbol after 'std' + let g:bullets_outline_levels = ['ROM', 'ABC', 'num', 'abc', 'rom', 'std-', 'std*', 'std+'] +endif + " ------------------------------------------------------ }}} " Parse Bullet Type ------------------------------------------- {{{ @@ -197,7 +204,7 @@ fun! s:match_checkbox_bullet_item(input_text) endfun fun! s:match_bullet_list_item(input_text) - let l:std_bullet_regex = '\v(^\s*(-|\*+|\.+|#\.|\\item)(\s+))(.*)' + let l:std_bullet_regex = '\v(^(\s*)(-|\*+|\.+|#\.|\+|\\item)(\s+))(.*)' let l:matches = matchlist(a:input_text, l:std_bullet_regex) if empty(l:matches) @@ -205,14 +212,17 @@ fun! s:match_bullet_list_item(input_text) endif let l:bullet_length = strlen(l:matches[1]) - let l:whole_bullet = l:matches[1] - let l:trailing_space = l:matches[3] - let l:text_after_bullet = l:matches[4] + let l:leading_space = l:matches[2] + let l:bullet = l:matches[3] + let l:trailing_space = l:matches[4] + let l:text_after_bullet = l:matches[5] return { \ 'bullet_type': 'std', \ 'bullet_length': l:bullet_length, - \ 'whole_bullet': l:whole_bullet, + \ 'leading_space': l:leading_space, + \ 'bullet': l:bullet, + \ 'closure': '', \ 'trailing_space': l:trailing_space, \ 'text_after_bullet': l:text_after_bullet \ } @@ -220,17 +230,24 @@ endfun " ------------------------------------------------------- }}} " Resolve Bullet Type ----------------------------------- {{{ -fun! s:closest_bullet_types(from_line_num) +fun! s:closest_bullet_types(from_line_num, max_indent) let l:lnum = a:from_line_num let l:ltxt = getline(l:lnum) + let l:curr_indent = indent(l:lnum) let l:bullet_kinds = s:parse_bullet(l:lnum, l:ltxt) " Support for wrapped text bullets " DEMO: https://raw.githubusercontent.com/dkarter/bullets.vim/master/img/wrapped-bullets.gif - while l:lnum > 1 && s:is_indented(l:ltxt) && l:bullet_kinds == [] - let l:lnum = l:lnum - 1 + while l:lnum > 1 && (l:curr_indent != 0 || l:bullet_kinds != []) + \ && (a:max_indent < l:curr_indent || l:bullet_kinds == []) + if l:bullet_kinds != [] + let l:lnum = l:lnum - g:bullets_line_spacing + else + let l:lnum = l:lnum - 1 + endif let l:ltxt = getline(l:lnum) let l:bullet_kinds = s:parse_bullet(l:lnum, l:ltxt) + let l:curr_indent = indent(l:lnum) endwhile return l:bullet_kinds @@ -259,8 +276,14 @@ endfun " Roman Numeral vs Alphabetic Bullets ---------------------------------- {{{ fun! s:resolve_rom_or_abc(bullet_types) let l:first_type = a:bullet_types[0] - let l:prev_search_starting_line = get(l:first_type, 'starting_at_line_num') - 1 - let l:prev_bullet_types = s:closest_bullet_types(l:prev_search_starting_line) + let l:prev_search_starting_line = l:first_type.starting_at_line_num - g:bullets_line_spacing + let l:bullet_indent = indent(l:first_type.starting_at_line_num) + let l:prev_bullet_types = s:closest_bullet_types(l:prev_search_starting_line, l:bullet_indent) + + while l:prev_bullet_types != [] && l:bullet_indent > indent(l:prev_search_starting_line) + let l:prev_search_starting_line -= g:bullets_line_spacing + let l:prev_bullet_types = s:closest_bullet_types(l:prev_search_starting_line, l:bullet_indent) + endwhile if len(l:prev_bullet_types) == 0 @@ -319,37 +342,36 @@ fun! s:next_bullet_str(bullet) let l:bullet_type = get(a:bullet, 'bullet_type') if l:bullet_type ==# 'rom' - return s:next_rom_bullet(a:bullet) + let l:next_bullet_marker = s:next_rom_bullet(a:bullet) elseif l:bullet_type ==# 'abc' - return s:next_abc_bullet(a:bullet) + let l:next_bullet_marker = s:next_abc_bullet(a:bullet) elseif l:bullet_type ==# 'num' - return s:next_num_bullet(a:bullet) + let l:next_bullet_marker = s:next_num_bullet(a:bullet) elseif l:bullet_type ==# 'chk' - return s:next_chk_bullet(a:bullet) + let l:next_bullet_marker = s:next_chk_bullet(a:bullet) else - return a:bullet.whole_bullet + let l:next_bullet_marker = a:bullet.bullet endif + let l:closure = has_key(a:bullet, 'closure') ? a:bullet.closure : '' + return a:bullet.leading_space . l:next_bullet_marker . l:closure . ' ' endfun fun! s:next_rom_bullet(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 . ' ' + return s:arabic2roman(s:roman2arabic(a:bullet.bullet) + 1, l:islower) endfun fun! s:next_abc_bullet(bullet) 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 . ' ' + return s:dec2abc(s:abc2dec(a:bullet.bullet) + 1, l:islower) endfun fun! s:next_num_bullet(bullet) - let l:next_num = a:bullet.bullet + 1 - return a:bullet.leading_space . l:next_num . a:bullet.closure . ' ' + return a:bullet.bullet + 1 endfun fun! s:next_chk_bullet(bullet) - return a:bullet.leading_space . '- [ ] ' + return '- [ ]' endfun " }}} @@ -363,7 +385,8 @@ endfun 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:closest_bullet_types = s:closest_bullet_types(l:curr_line_num) + let l:curr_indent = indent(l:curr_line_num) + let l:closest_bullet_types = s:closest_bullet_types(l:curr_line_num, l:curr_indent) let l:bullet = s:resolve_bullet_type(l:closest_bullet_types) " need to find which line starts the previous bullet started at and start " searching up from there @@ -573,6 +596,137 @@ endfun command! -range=% RenumberSelection call renumber_selection() " --------------------------------------------------------- }}} +" Changing outline level ---------------------------------- {{{ +fun! s:change_bullet_level(direction) + let l:lnum = line('.') + let l:curr_line = s:parse_bullet(l:lnum, getline(l:lnum)) + + if a:direction == 1 + if l:curr_line != [] && indent(l:lnum) == 0 + " Promoting a bullet at the highest level will delete the bullet + call setline(l:lnum, l:curr_line[0].text_after_bullet) + else + execute "normal! a\" + endif + else + execute "normal! a\" + endif + + let l:curr_indent = indent(l:lnum) + let l:curr_bullet= s:closest_bullet_types(l:lnum, l:curr_indent) + let l:curr_bullet = s:resolve_bullet_type(l:curr_bullet) + + if l:curr_bullet == {} + " Only change the bullet level if it's currently a bullet. + return + endif + + let l:curr_line = l:curr_bullet.starting_at_line_num + let l:closest_bullet = s:closest_bullet_types(l:curr_line - g:bullets_line_spacing, l:curr_indent) + let l:closest_bullet = s:resolve_bullet_type(l:closest_bullet) + + if l:closest_bullet == {} + " If there is no parent/sibling bullet then this bullet shouldn't change. + return + endif + + let l:islower = l:closest_bullet.bullet ==# tolower(l:closest_bullet.bullet) + let l:closest_type = l:islower ? l:closest_bullet.bullet_type : + \ toupper(l:closest_bullet.bullet_type) + + if l:closest_bullet.bullet_type ==# 'std' + " Append the bullet marker to the type, e.g., 'std*' + + let l:closest_type = l:closest_type . l:closest_bullet.bullet + endif + + let l:closest_index = index(g:bullets_outline_levels, l:closest_type) + + if l:closest_index == -1 + " We are in a list using markers that aren't specified in + " g:bullets_outline_levels so we shouldn't try to change the current + " bullet. + return + endif + + let l:closest_indent = indent(l:closest_bullet.starting_at_line_num) + + if (l:curr_indent == l:closest_indent) + " The closest bullet is a sibling so the current bullet should + " increment to the next bullet marker. + + let l:next_bullet = s:next_bullet_str(l:closest_bullet) + let l:next_bullet_str = s:pad_to_length(l:next_bullet, l:closest_bullet.bullet_length) + \ . l:curr_bullet.text_after_bullet + + elseif l:closest_index + 1 < len(g:bullets_outline_levels) || l:curr_indent < l:closest_indent + " The current bullet is a child of the closest bullet so figure out + " what bullet type it should have and set its marker to the first + " character of that type. + + let l:next_index = l:closest_index + 1 + let l:next_type = g:bullets_outline_levels[l:next_index] + let l:next_islower = l:next_type ==# tolower(l:next_type) + let l:trailing_space = ' ' + + let l:curr_bullet.closure = l:closest_bullet.closure + + " set the bullet marker to the first character of that type + if l:next_type ==? 'rom' + let l:next_num = s:arabic2roman(1, l:next_islower) + elseif l:next_type ==? 'abc' + let l:next_num = s:dec2abc(1, l:next_islower) + elseif l:next_type ==# 'num' + let l:next_num = '1' + else + " standard bullet; l:next_type contains the bullet symbol to use + let l:next_num = strpart(l:next_type, len(l:next_type) - 1) + let l:curr_bullet.closure = '' + endif + + let l:next_bullet_str = + \ l:curr_bullet.leading_space + \ . l:next_num + \ . l:curr_bullet.closure + \ . l:trailing_space + \ . l:curr_bullet.text_after_bullet + + else + " We're outside of the defined outline levels + let l:next_bullet_str = + \ l:curr_bullet.leading_space + \ . l:curr_bullet.text_after_bullet + endif + + " Apply the new bullet + if l:next_bullet_str !=# '' + call setline(l:lnum, l:next_bullet_str) + execute 'normal! $' + + elseif g:bullets_delete_last_bullet_if_empty + let l:orig_line = s:parse_bullet(l:lnum, getline(l:lnum)) + if l:orig_line != [] + call setline(l:lnum, l:orig_line[0].leading_space . l:orig_line[0].text_after_bullet) + endif + endif + + return +endfun + +fun! s:bullet_demote() + call s:change_bullet_level(-1) +endfun + +fun! s:bullet_promote() + call s:change_bullet_level(1) +endfun + +command! BulletDemote call bullet_demote() +command! BulletPromote call bullet_promote() + + +" --------------------------------------------------------- }}} + " Keyboard mappings --------------------------------------- {{{ fun! s:add_local_mapping(mapping_type, mapping, action) let l:file_types = join(g:bullets_enabled_file_types, ',') @@ -613,6 +767,13 @@ augroup TextBulletsMappings " Toggle checkbox call s:add_local_mapping('nnoremap', 'x', ':ToggleCheckbox') + + " Promote and Demote outline level + call s:add_local_mapping('inoremap', '', ':call bullet_demote()') + call s:add_local_mapping('nnoremap', '>>', ':call bullet_demote()') + call s:add_local_mapping('inoremap', '', ':call bullet_promote()') + call s:add_local_mapping('nnoremap', '<<', ':call bullet_promote()') + end augroup END " --------------------------------------------------------- }}} diff --git a/spec/alphabetic_bullets_spec.rb b/spec/alphabetic_bullets_spec.rb index 303dfd0..c175d0d 100644 --- a/spec/alphabetic_bullets_spec.rb +++ b/spec/alphabetic_bullets_spec.rb @@ -132,6 +132,49 @@ TEXT end + it 'does not add a new bullet when mixed case' do + test_bullet_inserted('not a bullet', <<-INIT, <<-EXPECTED) + # Hello there + Ab. this is the first bullet + INIT + # Hello there + Ab. this is the first bullet + not a bullet + EXPECTED + end + + # it 'correctly numbers after wrapped lines starting with short words' do + # # TODO: maybe take guidance from Pandoc and require two spaces after the + # closure to allow us to differentiate between bullets and abbreviations + # and words. Might also consider only allowing single letters. + # test_bullet_inserted('second bullet', <<-INIT, <<-EXPECTED) + # # Hello there + # a. first bullet might not catch + # me. second line. + # INIT + # # Hello there + # a. first bullet might not catch + # \tme. second line. + # b. second bullet + # EXPECTED + # end + + # it 'correctly numbers after lines beginning with initialized names' do + # # TODO: maybe take guidance from Pandoc and require two spaces after the + # closure to allow us to differentiate between bullets and abbreviations + # and words. Might also consider only allowing single letters. + # test_bullet_inserted('Second bullet', <<-INIT, <<-EXPECTED) + # # Hello there + # I. The first president of the USA was + # G. Washington. + # INIT + # # Hello there + # I. The first president of the USA was + # G. Washington. + # II. Second bullet + # EXPECTED + # end + describe 'g:bullets_max_alpha_characters' do it 'stops adding items after configured max (default 2)' do filename = "#{SecureRandom.hex(6)}.txt" diff --git a/spec/nested_bullets_spec.rb b/spec/nested_bullets_spec.rb new file mode 100644 index 0000000..5f5f2a7 --- /dev/null +++ b/spec/nested_bullets_spec.rb @@ -0,0 +1,641 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Bullets.vim' do + describe 'nested bullets' do + it 'demotes an existing bullet' do + filename = "#{SecureRandom.hex(6)}.txt" + write_file(filename, <<-TEXT) + # Hello there + I. this is the first bullet + II. second bullet + III. third bullet + IV. fourth bullet + V. fifth bullet + VI. sixth bullet + VII. seventh bullet + VIII. eighth bullet + IX. ninth bullet + TEXT + + vim.edit filename + vim.normal '2ji' + vim.feedkeys '\' + vim.normal 'j' + vim.feedkeys '>>>>>>' + vim.normal 'j' + vim.feedkeys '>>>>>>>>' + vim.normal 'j' + vim.feedkeys '>>>>>>>>>>' + vim.normal 'j' + vim.feedkeys '>>>>>>>>>>>>' + vim.normal 'j' + vim.feedkeys '>>>>>>>>>>>>>>' + vim.normal 'j' + vim.feedkeys '>>>>>>>>>>>>>>>>' + vim.normal 'j' + vim.feedkeys '>>>>>>>>>>>>>>>>>>' + vim.write + + file_contents = IO.read(filename) + + expect(file_contents).to eq normalize_string_indent(<<-TEXT) + # Hello there + I. this is the first bullet + \tA. second bullet + \t\t\t1. third bullet + \t\t\t\ta. fourth bullet + \t\t\t\t\ti. fifth bullet + \t\t\t\t\t\t- sixth bullet + \t\t\t\t\t\t\t* seventh bullet + \t\t\t\t\t\t\t\t+ eighth bullet + \t\t\t\t\t\t\t\t\tninth bullet + + TEXT + end + + it 'promotes an existing bullet' do + filename = "#{SecureRandom.hex(6)}.txt" + write_file(filename, <<-TEXT) + # Hello there + I. this is the first bullet + \tA. second bullet + \t\t\t1. third bullet + \t\t\t\ta. fourth bullet + \t\t\t\t\ti. fifth bullet + \t\t\t\t\t\t- sixth bullet + \t\t\t\t\t\t\t* seventh bullet + \t\t\t\t\t\t\t\t+ eighth bullet + TEXT + + vim.edit filename + vim.normal '2j' + vim.feedkeys '<<' + vim.normal 'ji' + vim.feedkeys '\' + vim.feedkeys '\' + vim.normal 'j' + vim.feedkeys '<<<<<<' + vim.normal 'j' + vim.feedkeys '<<<<<<<<<<' + vim.normal 'j' + vim.feedkeys '<<<<<<<<<<<<' + vim.normal 'j' + vim.feedkeys '<<<<<<<<<<<<<<' + vim.normal 'j' + vim.feedkeys '<<<<<<<<<<<<<<<<' + vim.write + + file_contents = IO.read(filename) + + expect(file_contents).to eq normalize_string_indent(<<-TEXT) + # Hello there + I. this is the first bullet + II. second bullet + \tA. third bullet + \tB. fourth bullet + III. fifth bullet + IV. sixth bullet + V. seventh bullet + VI. eighth bullet + + TEXT + end + + it 'demotes an empty bullet' do + filename = "#{SecureRandom.hex(6)}.txt" + write_file(filename, <<-TEXT) + # Hello there + I. this is the first bullet + TEXT + + vim.edit filename + vim.normal 'GA' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'second bullet' + vim.feedkeys '\' + vim.feedkeys '\\' + vim.type 'third bullet' + vim.feedkeys '\' + vim.feedkeys 'o' + vim.feedkeys '\' + vim.type 'fourth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'fifth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'sixth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'seventh bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'eighth bullet' + vim.write + + file_contents = IO.read(filename) + + expect(file_contents).to eq normalize_string_indent(<<-TEXT) + # Hello there + I. this is the first bullet + \tA. second bullet + \t\t\t1. third bullet + \t\t\t\ta. fourth bullet + \t\t\t\t\ti. fifth bullet + \t\t\t\t\t\t- sixth bullet + \t\t\t\t\t\t\t* seventh bullet + \t\t\t\t\t\t\t\t+ eighth bullet + + TEXT + end + + it 'promotes an empty bullet' do + filename = "#{SecureRandom.hex(6)}.txt" + write_file(filename, <<-TEXT) + # Hello there + I. this is the first bullet + \tA. second bullet + \t\t1. third bullet + \t\t\ta. fourth bullet + \t\t\t\ti. fifth bullet + \t\t\t\t\t- sixth bullet + TEXT + + vim.edit filename + vim.normal 'GA' + vim.feedkeys '\' + vim.type 'seventh bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'eighth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'ninth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'tenth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'eleventh bullet' + vim.write + + file_contents = IO.read(filename) + + expect(file_contents).to eq normalize_string_indent(<<-TEXT) + # Hello there + I. this is the first bullet + \tA. second bullet + \t\t1. third bullet + \t\t\ta. fourth bullet + \t\t\t\ti. fifth bullet + \t\t\t\t\t- sixth bullet + \t\t\t\t\t- seventh bullet + \t\t\t\tii. eighth bullet + \t\t\tb. ninth bullet + \t\t2. tenth bullet + II. eleventh bullet + + TEXT + end + + it 'restarts numbering with multiple outlines' do + filename = "#{SecureRandom.hex(6)}.txt" + write_file(filename, <<-TEXT) + # Hello there + I. this is the first bullet + \tA. second bullet + \t\t1. third bullet + TEXT + + vim.edit filename + vim.normal 'GA' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'A. first bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'second bullet' + vim.feedkeys '\' + vim.type 'third bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type '1. first bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'second bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'third bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type '- first bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'second bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'third bullet' + vim.write + + file_contents = IO.read(filename) + + expect(file_contents).to eq normalize_string_indent(<<-TEXT) + # Hello there + I. this is the first bullet + \tA. second bullet + \t\t1. third bullet + + A. first bullet + \t1. second bullet + \t\ta. third bullet + + 1. first bullet + \ta. second bullet + \t\ti. third bullet + + - first bullet + \t* second bullet + \t\t+ third bullet + + TEXT + end + + it 'works with custom outline level definitions' do + filename = "#{SecureRandom.hex(6)}.txt" + write_file(filename, <<-TEXT) + # Hello there + TEXT + + vim.edit filename + vim.command "let g:bullets_outline_levels=['num','ABC','std*']" + vim.normal 'GA' + vim.feedkeys '\' + vim.type '1. first bullet' + vim.feedkeys '\' + vim.type 'second bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'third bullet' + vim.feedkeys '\' + vim.type 'fourth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'fifth bullet' + vim.feedkeys '\' + vim.type 'sixth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'not a bullet' + vim.feedkeys '\' + vim.type 'seventh bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'eighth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'ninth bullet' + vim.feedkeys '\' + vim.type 'tenth bullet' + + vim.write + + file_contents = IO.read(filename) + + expect(file_contents).to eq normalize_string_indent(<<-TEXT) + # Hello there + 1. first bullet + 2. second bullet + \tA. third bullet + \tB. fourth bullet + \t\t* fifth bullet + \t\t* sixth bullet + \t\t\tnot a bullet + \t\t* seventh bullet + \tC. eighth bullet + 3. ninth bullet + 4. tenth bullet + + TEXT + end + + it 'promotes and demotes from different starting levels' do + filename = "#{SecureRandom.hex(6)}.txt" + write_file(filename, <<-TEXT) + # Hello there + 1. this is the first bullet + 2. second bullet + TEXT + + vim.edit filename + vim.normal 'GA' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'third bullet' + vim.normal '3hi' + vim.feedkeys '\' + vim.feedkeys '\' + vim.feedkeys '\' + vim.feedkeys '>>' + vim.type 'astandard bullet' + vim.feedkeys '\' + vim.type 'fourth bullet' + vim.feedkeys '\' + vim.type 'fifth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.feedkeys '\' + vim.feedkeys '<<' + vim.feedkeys 'i\' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type '+ sixth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'not a bullet' + vim.feedkeys '\' + vim.type 'seventh bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type '* eighth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'ninth bullet' + + vim.write + + file_contents = IO.read(filename) + + expect(file_contents).to eq normalize_string_indent(<<-TEXT) + # Hello there + 1. this is the first bullet + \ta. second bullet + \t\ti. third bullet + \t\t\t- standard bullet + \t\t\t- fourth bullet + \tb. fifth bullet + + + sixth bullet + \tnot a bullet + + seventh bullet + + * eighth bullet + \t+ ninth bullet + + TEXT + end + + it 'does not nest below defined levels' do + filename = "#{SecureRandom.hex(6)}.txt" + write_file(filename, <<-TEXT) + # Hello there + I. this is the first bullet + \tA. second bullet + \t\t1. third bullet + \t\t\ta. fourth bullet + \t\t\t\ti. fifth bullet + \t\t\t\tii. sixth bullet + \t\t\t\t\t- seventh bullet + \t\t\t\t\t\t* eighth bullet + \t\t\t\t\t\t\t+ ninth bullet + TEXT + + vim.edit filename + vim.normal 'GA' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'not a bullet' + vim.feedkeys '\' + vim.type 'tenth bullet' + vim.write + + file_contents = IO.read(filename) + + expect(file_contents).to eq normalize_string_indent(<<-TEXT) + # Hello there + I. this is the first bullet + \tA. second bullet + \t\t1. third bullet + \t\t\ta. fourth bullet + \t\t\t\ti. fifth bullet + \t\t\t\tii. sixth bullet + \t\t\t\t\t- seventh bullet + \t\t\t\t\t\t* eighth bullet + \t\t\t\t\t\t\t+ ninth bullet + \t\t\t\t\t\t\t\tnot a bullet + \t\t\t\t\t\t\t+ tenth bullet + + TEXT + end + + it 'removes bullet when promoting top level bullet' do + filename = "#{SecureRandom.hex(6)}.txt" + write_file(filename, <<-TEXT) + # Hello there + A. this is the first bullet + + I. second bullet + \tA. third bullet + TEXT + + vim.edit filename + vim.normal 'j' + vim.feedkeys '<<' + vim.normal '3ji' + vim.feedkeys '\' + vim.feedkeys '\' + vim.write + + file_contents = IO.read(filename) + + expect(file_contents).to eq normalize_string_indent(<<-TEXT) + # Hello there + this is the first bullet + + I. second bullet + third bullet + + TEXT + end + + it 'handle standard bullets when they are not in outline list' do + filename = "#{SecureRandom.hex(6)}.txt" + write_file(filename, <<-TEXT) + # Hello there + 1. this is the first bullet + \t- standard bullet + TEXT + + vim.edit filename + vim.command "let g:bullets_outline_levels=['num','ABC']" + vim.normal 'GA' + vim.feedkeys '\' + vim.type 'second standard bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'second bullet' + vim.feedkeys '\' + vim.type 'third bullet' + vim.write + + file_contents = IO.read(filename) + + expect(file_contents).to eq normalize_string_indent(<<-TEXT) + # Hello there + 1. this is the first bullet + \t- standard bullet + \t- second standard bullet + 2. second bullet + 3. third bullet + + TEXT + end + + it 'adds new nested bullets with correct alpha/roman numerals' do + filename = "#{SecureRandom.hex(6)}.txt" + write_file(filename, <<-TEXT) + # Hello there + I. this is the first bullet + + \tA. second bullet + + \tB. third bullet + + \tC. fourth bullet + TEXT + + vim.edit filename + vim.command 'let g:bullets_line_spacing=2' + vim.normal 'GA' + vim.feedkeys '\' + vim.type 'fifth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'sixth bullet' + vim.feedkeys '\' + vim.type 'seventh bullet' + vim.feedkeys '\' + vim.type 'eighth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'ninth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'tenth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'eleventh bullet' + vim.feedkeys '\' + vim.type 'twelfth bullet' + vim.feedkeys '\' + vim.type 'thirteenth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'fourteenth bullet' + vim.feedkeys '\' + vim.type 'fifteenth bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'sixteenth bullet' + vim.feedkeys '\' + vim.normal 'dd' + vim.insert ' wrapped line' + vim.feedkeys '\' + vim.type 'seventeenth bullet' + vim.feedkeys '\' + vim.normal 'dd' + vim.insert ' wrapped line' + vim.feedkeys '\' + vim.type 'eighteenth bullet' + vim.write + + file_contents = IO.read(filename) + + expect(file_contents).to eq normalize_string_indent(<<-TEXT) + # Hello there + I. this is the first bullet + + \tA. second bullet + + \tB. third bullet + + \tC. fourth bullet + + \tD. fifth bullet + + II. sixth bullet + + III. seventh bullet + + \tA. eighth bullet + + \t\t1. ninth bullet + + \t\t\ta. tenth bullet + + \t\t\t\ti. eleventh bullet + + \t\t\t\tii. twelfth bullet + + \t\t\t\t\t- thirteenth bullet + + \t\t\t\t\t- fourteenth bullet + + \t\t\t\tiii. fifteenth bullet + + \t\t\tb. sixteenth bullet + \t\t\t\twrapped line + + \t\t\tc. seventeenth bullet + \t\t\t\twrapped line + + \t\t\td. eighteenth bullet + + TEXT + end + + it 'add and change bullets with multiple line spacing and wrapped lines' do + filename = "#{SecureRandom.hex(6)}.txt" + write_file(filename, <<-TEXT) + # Hello there + I. this is the first bullet + TEXT + + vim.edit filename + vim.command 'let g:bullets_line_spacing=2' + vim.normal 'GA' + vim.feedkeys '\' + vim.type 'second bullet' + vim.feedkeys '\' + vim.feedkeys '\' + vim.type 'third bullet' + vim.feedkeys '\' + vim.normal 'dd' + vim.insert ' wrapped bullet' + vim.feedkeys '\' + vim.type 'fourth bullet' + vim.write + + file_contents = IO.read(filename) + + expect(file_contents).to eq normalize_string_indent(<<-TEXT) + # Hello there + I. this is the first bullet + + II. second bullet + + \tA. third bullet + \twrapped bullet + + \tB. fourth bullet + + TEXT + end + end +end