From 41a87f5454b5e0a4f8419f8700a0ff3d5a9751dc Mon Sep 17 00:00:00 2001 From: Dorian Karter Date: Wed, 3 Jun 2026 11:56:50 -0500 Subject: [PATCH 1/2] feat: support outline promotion --- lua/bullets/actions.lua | 171 ++++++++++++++++++++++++++++++++++- lua/bullets/init.lua | 8 +- test/nested_bullets_spec.lua | 159 ++++++-------------------------- 3 files changed, 199 insertions(+), 139 deletions(-) diff --git a/lua/bullets/actions.lua b/lua/bullets/actions.lua index d16e71a..d078e8e 100644 --- a/lua/bullets/actions.lua +++ b/lua/bullets/actions.lua @@ -327,6 +327,161 @@ local function child_bullet(bullet) return nil end +local function outline_index(style) + for index, outline_style in ipairs(config.options.outline_levels) do + if outline_style == style then + return index + end + end + + return nil +end + +local function indent_depth(indent) + local unit = indent_unit() + local depth = 0 + + while indent:sub(1, #unit) == unit do + depth = depth + 1 + indent = indent:sub(#unit + 1) + end + + return depth +end + +local function parent_indent(indent) + local unit = indent_unit() + if indent:sub(-#unit) == unit then + return indent:sub(1, -#unit - 1) + end + + return indent + :gsub("\t$", "") + :gsub(string.rep(" ", vim.o.shiftwidth > 0 and vim.o.shiftwidth or vim.o.tabstop) .. "$", "") +end + +local function first_bullet_for_style(style, indent) + return bullet_for_style(style, indent) +end + +local function previous_bullet_with_style(lnum, style, indent) + for row = lnum - 1, 1, -1 do + local line = vim.api.nvim_buf_get_lines(0, row - 1, row, false)[1] + if line == "" then + return nil + end + + local bullet = resolve_bullet(parse_line(line), row) + if bullet and bullet.indent == indent and style_for_bullet(bullet) == style then + return bullet + end + end + + return nil +end + +local function bullet_for_level(style, indent, lnum) + local bullet = first_bullet_for_style(style, indent) + if not bullet then + return nil + end + + local previous = previous_bullet_with_style(lnum, style, indent) + if previous then + local marker = next_marker(previous) + if marker then + bullet.marker = marker + end + end + + return bullet +end + +local function fallback_style_for_indent(indent) + return config.options.outline_levels[indent_depth(indent) + 1] +end + +local function change_line_level(lnum, direction) + local line = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, false)[1] + local bullet = resolve_bullet(parse_line(line), lnum) + if not bullet then + return false + end + + local style = style_for_bullet(bullet) + local index = style and outline_index(style) or nil + local next_style + local next_indent + + if direction == "demote" then + next_style = index and config.options.outline_levels[index + 1] + or fallback_style_for_indent(bullet.indent .. indent_unit()) + next_indent = bullet.indent .. indent_unit() + + if not next_style then + local last_style = config.options.outline_levels[#config.options.outline_levels] + if last_style and last_style:match("^std") and bullet.type == "std" then + next_style = style + else + return false + end + end + else + if bullet.indent == "" then + vim.api.nvim_buf_set_lines(0, lnum - 1, lnum, false, { bullet.text }) + vim.api.nvim_win_set_cursor(0, { lnum, 0 }) + return true + end + + next_indent = parent_indent(bullet.indent) + next_style = index and config.options.outline_levels[index - 1] or fallback_style_for_indent(next_indent) + if not next_style then + return false + end + end + + local next_bullet = bullet_for_level(next_style, next_indent, lnum) + if not next_bullet then + return false + end + + local prefix = current_prefix(next_bullet) + vim.api.nvim_buf_set_lines(0, lnum - 1, lnum, false, { prefix .. bullet.text }) + vim.api.nvim_win_set_cursor(0, { lnum, #prefix }) + return true +end + +local function change_current_line_level(direction) + local lnum = vim.api.nvim_win_get_cursor(0)[1] + local changed = change_line_level(lnum, direction) + if changed and config.options.renumber_on_change then + M.renumber_list() + end + return changed +end + +local function visual_range(first, last) + if first and last then + return first, last + end + + local start_pos = vim.fn.getpos("'<") + local end_pos = vim.fn.getpos("'>") + return math.min(start_pos[2], end_pos[2]), math.max(start_pos[2], end_pos[2]) +end + +local function change_visual_level(direction, first, last) + first, last = visual_range(first, last) + local changed = false + for lnum = first, last do + changed = change_line_level(lnum, direction) or changed + end + if changed and config.options.renumber_on_change then + M.renumber_selection() + end + return changed +end + local function ends_with_colon(text) return text:sub(-1) == ":" or text:sub(-3) == ":" end @@ -441,13 +596,21 @@ function M.toggle_checkbox() end function M.recompute_checkboxes() end -function M.demote() end +function M.demote() + return change_current_line_level("demote") +end -function M.promote() end +function M.promote() + return change_current_line_level("promote") +end -function M.demote_visual() end +function M.demote_visual(first, last) + return change_visual_level("demote", first, last) +end -function M.promote_visual() end +function M.promote_visual(first, last) + return change_visual_level("promote", first, last) +end function M.select_checkbox() end diff --git a/lua/bullets/init.lua b/lua/bullets/init.lua index 9fdcdb9..1ca3fd0 100644 --- a/lua/bullets/init.lua +++ b/lua/bullets/init.lua @@ -42,11 +42,11 @@ local function add_commands() command("BulletPromote", function() actions().promote() end) - command("BulletDemoteVisual", function() - actions().demote_visual() + command("BulletDemoteVisual", function(opts) + actions().demote_visual(opts.line1, opts.line2) end, { range = true }) - command("BulletPromoteVisual", function() - actions().promote_visual() + command("BulletPromoteVisual", function(opts) + actions().promote_visual(opts.line1, opts.line2) end, { range = true }) command("SelectCheckbox", function() actions().select_checkbox() diff --git a/test/nested_bullets_spec.lua b/test/nested_bullets_spec.lua index 2e8dfc5..7c6c4da 100644 --- a/test/nested_bullets_spec.lua +++ b/test/nested_bullets_spec.lua @@ -1,5 +1,5 @@ local helpers = require("test.helpers") -local it = pending +local pending_it = pending describe("Bullets.vim", function() describe("nested bullets", function() @@ -17,40 +17,12 @@ describe("Bullets.vim", function() "# 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", }) - -- Go to line 3 (gg + 2j), enter insert, demote with - helpers.feedkeys("gg2ji") - -- Back to normal mode, go down 1 line, demote 3 times with >> - helpers.feedkeys("j>>>>>>") - -- Continue demoting subsequent lines - helpers.feedkeys("j>>>>>>>>") - helpers.feedkeys("j>>>>>>>>>>") - helpers.feedkeys("j>>>>>>>>") - helpers.feedkeys(">>>>") - helpers.feedkeys("j>>>>>>>>") - helpers.feedkeys(">>>>>>") - helpers.feedkeys("j>>>>>>>>") - helpers.feedkeys(">>>>>>>>") - helpers.feedkeys("j>>>>>>>>") - helpers.feedkeys(">>>>>>>>>>") + helpers.feedkeys("gg2j>>") assert.are.same({ "# 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\t+ ninth bullet", }, helpers.get_lines()) end) @@ -59,38 +31,14 @@ describe("Bullets.vim", function() "# 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", + "\tB. third bullet", }) - -- Go to line 3 (gg + 2j), promote with << - helpers.feedkeys("gg2j<<") - -- Go to line 4, enter insert, demote twice with - helpers.feedkeys("ji") - -- Continue promoting subsequent lines - helpers.feedkeys("j<<<<<<") - helpers.feedkeys("j<<<<<<") - helpers.feedkeys("<<<<") - helpers.feedkeys("j<<<<<<") - helpers.feedkeys("<<<<<<") - helpers.feedkeys("j<<<<<<") - helpers.feedkeys("<<<<<<<<") - helpers.feedkeys("j<<<<<<") - helpers.feedkeys("<<<<<<") - helpers.feedkeys("<<<<") + helpers.feedkeys("gg3j<<") assert.are.same({ "# 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", + "\tA. second bullet", + "II. third bullet", }, helpers.get_lines()) end) @@ -124,7 +72,7 @@ describe("Bullets.vim", function() }, helpers.get_lines()) end) - it("restarts numbering with multiple outlines", function() + pending_it("restarts numbering with multiple outlines", function() helpers.new_buffer({ "# Hello there", "I. this is the first bullet", @@ -154,45 +102,30 @@ describe("Bullets.vim", function() end) it("works with custom outline level definitions", function() - vim.g.bullets_outline_levels = { "num", "ABC", "std*" } + require("bullets").setup({ outline_levels = { "num", "ABC", "std*" } }) helpers.new_buffer({ "# Hello there", + "1. first bullet", + "\tA. second bullet", + "\t\t* third bullet", + "2. fourth bullet", }) - -- Enter insert at end, CR (non-bullet line header, deferred CR) - helpers.feedkeys("GA") - -- Now in normal mode on new empty line - type the first bullet - helpers.feedkeys("i1. first bullet") - -- CR on bullet line - stays in insert after plugin fires - helpers.feedkeys("Asecond bullet") - -- CR on bullet line, then demote, then type - helpers.feedkeys("Athird bullet") - helpers.feedkeys("Afourth bullet") - helpers.feedkeys("Afifth bullet") - helpers.feedkeys("Asixth bullet") - helpers.feedkeys("Aseventh bullet") - helpers.feedkeys("Aeighth bullet") - -- demote twice, then type - helpers.feedkeys("Aninth bullet") - -- demote once, then type - helpers.feedkeys("Atenth bullet") - helpers.feedkeys("Aeleventh bullet") + vim.api.nvim_win_set_cursor(0, { 5, 0 }) + vim.cmd("BulletDemote") + vim.api.nvim_win_set_cursor(0, { 3, 0 }) + vim.cmd("BulletPromote") + vim.api.nvim_win_set_cursor(0, { 4, 0 }) + vim.cmd("BulletDemote") assert.are.same({ "# Hello there", "1. first bullet", "2. second bullet", - "\tA. third bullet", + "\t\t\t* third bullet", "\tB. fourth bullet", - "\t\t* fifth bullet", - "\t\t* sixth bullet", - "\t\t\t* seventh bullet", - "\t\t\t* eighth bullet", - "\tC. ninth bullet", - "3. tenth bullet", - "4. eleventh bullet", }, helpers.get_lines()) end) - it("promotes and demotes from different starting levels", function() + pending_it("promotes and demotes from different starting levels", function() helpers.new_buffer({ "# Hello there", "1. this is the first bullet", @@ -283,7 +216,7 @@ describe("Bullets.vim", function() }, helpers.get_lines()) end) - it("handle standard bullets when they are not in outline list", function() + pending_it("handle standard bullets when they are not in outline list", function() vim.g.bullets_outline_levels = { "num", "ABC" } helpers.new_buffer({ "# Hello there", @@ -306,7 +239,7 @@ describe("Bullets.vim", function() }, helpers.get_lines()) end) - it("adds new nested bullets with correct alpha/roman numerals", function() + pending_it("adds new nested bullets with correct alpha/roman numerals", function() helpers.new_buffer({ "# Hello there", "I. this is the first bullet", @@ -338,60 +271,24 @@ describe("Bullets.vim", function() end) it("changes levels in visual mode", function() - vim.g.bullets_outline_levels = { "num", "abc", "std*" } + require("bullets").setup({ outline_levels = { "num", "abc", "std*" } }) helpers.new_buffer({ "# Hello there", "1. first bullet", "\ta. second bullet", "\tb. third bullet", - "\t\t* fourth bullet", - "\t\t* fifth bullet", - "\t\t\tsixth bullet", - "\t\t* seventh bullet", - "2. eighth bullet", - "\t\ta. ninth bullet", - "\ta. tenth bullet", - "\tb. eleventh bullet", - "3. twelfth bullet", - "\t thirteenth bullet", - "\ta. fourteenth bullet", - "\t\t* fifteenth bullet", - "4. sixteenth bullet", }) - -- After each visual < or > operation, the plugin re-enters visual mode (via s:set_selection). - -- Exit visual mode before starting each fresh visual selection. - helpers.feedkeys("gg3jv<") - helpers.feedkeys("jv2j<") - helpers.feedkeys("jvj>") - helpers.feedkeys("jvj<") - -- The plugin leaves us in visual mode with the same selection. - helpers.feedkeys("<") - helpers.feedkeys("jv>") - helpers.feedkeys("3jv2j>") - -- Repeat the operation on the same visual selection. - helpers.feedkeys(">") + vim.cmd("3,4BulletPromoteVisual") + vim.cmd("3,4BulletDemoteVisual") assert.are.same({ "# Hello there", "1. first bullet", "\ta. second bullet", - "2. third bullet", - "\ta. fourth bullet", - "\tb. fifth bullet", - "\t\tsixth bullet", - "\t\t\t* seventh bullet", - "\tc. eighth bullet", - "3. ninth bullet", - "tenth bullet", - "\t\ta. eleventh bullet", - "4. twelfth bullet", - "\t thirteenth bullet", - "\t\t\ta. fourteenth bullet", - "\t\t\t\t* fifteenth bullet", - "\t\ta. sixteenth bullet", + "\tb. third bullet", }, helpers.get_lines()) end) - it("add and change bullets with multiple line spacing and wrapped lines", function() + pending_it("add and change bullets with multiple line spacing and wrapped lines", function() vim.g.bullets_line_spacing = 2 helpers.new_buffer({ "# Hello there", @@ -421,7 +318,7 @@ describe("Bullets.vim", function() }, helpers.get_lines()) end) - it("indents after a line ending in a colon", function() + pending_it("indents after a line ending in a colon", function() vim.g.bullets_auto_indent_after_colon = 1 helpers.new_buffer({ "# Hello there", From 5fda7e12756462cc5e232149853168b3c7b9cf91 Mon Sep 17 00:00:00 2001 From: Dorian Karter Date: Wed, 3 Jun 2026 12:06:10 -0500 Subject: [PATCH 2/2] test: preserve nested outline coverage --- test/nested_bullets_spec.lua | 311 ++++++++++++++++++++++++++++++++--- 1 file changed, 287 insertions(+), 24 deletions(-) diff --git a/test/nested_bullets_spec.lua b/test/nested_bullets_spec.lua index 7c6c4da..1e775be 100644 --- a/test/nested_bullets_spec.lua +++ b/test/nested_bullets_spec.lua @@ -1,4 +1,5 @@ local helpers = require("test.helpers") +local active_it = it local pending_it = pending describe("Bullets.vim", function() @@ -12,13 +13,15 @@ describe("Bullets.vim", function() vim.opt.tabstop = 4 end) - it("demotes an existing bullet", function() + active_it("demotes a bullet one outline level", function() helpers.new_buffer({ "# Hello there", "I. this is the first bullet", "II. second bullet", }) + helpers.feedkeys("gg2j>>") + assert.are.same({ "# Hello there", "I. this is the first bullet", @@ -26,14 +29,48 @@ describe("Bullets.vim", function() }, helpers.get_lines()) end) - it("promotes an existing bullet", function() + active_it("promotes a bullet one outline level", function() helpers.new_buffer({ "# Hello there", "I. this is the first bullet", "\tA. second bullet", "\tB. third bullet", }) + helpers.feedkeys("gg3j<<") + + assert.are.same({ + "# Hello there", + "I. this is the first bullet", + "\tA. second bullet", + "II. third bullet", + }, helpers.get_lines()) + end) + + active_it("demotes an empty bullet", function() + helpers.new_buffer({ + "# Hello there", + "I. this is the first bullet", + }) + + helpers.feedkeys("GAsecond bullet") + + assert.are.same({ + "# Hello there", + "I. this is the first bullet", + "\tA. second bullet", + }, helpers.get_lines()) + end) + + active_it("promotes an empty bullet", function() + helpers.new_buffer({ + "# Hello there", + "I. this is the first bullet", + "\tA. second bullet", + }) + + helpers.feedkeys("GAthird bullet") + assert.are.same({ "# Hello there", "I. this is the first bullet", @@ -42,7 +79,182 @@ describe("Bullets.vim", function() }, helpers.get_lines()) end) - it("demotes an empty bullet", function() + active_it("uses configured outline levels", function() + require("bullets").setup({ outline_levels = { "num", "ABC", "std*" } }) + helpers.new_buffer({ + "# Hello there", + "1. first bullet", + "\tA. second bullet", + "\t\t* third bullet", + "2. fourth bullet", + }) + + vim.api.nvim_win_set_cursor(0, { 5, 0 }) + vim.cmd("BulletDemote") + vim.api.nvim_win_set_cursor(0, { 3, 0 }) + vim.cmd("BulletPromote") + vim.api.nvim_win_set_cursor(0, { 4, 0 }) + vim.cmd("BulletDemote") + + assert.are.same({ + "# Hello there", + "1. first bullet", + "2. second bullet", + "\t\t\t* third bullet", + "\tB. fourth bullet", + }, helpers.get_lines()) + end) + + active_it("preserves the last standard outline level when demoting beyond configured levels", function() + helpers.new_buffer({ + "# Hello there", + "\t\t\t\t\t\t\t+ ninth bullet", + }) + + vim.api.nvim_win_set_cursor(0, { 2, 0 }) + vim.cmd("BulletDemote") + + assert.are.same({ + "# Hello there", + "\t\t\t\t\t\t\t\t+ ninth bullet", + }, helpers.get_lines()) + end) + + active_it("removes a bullet when promoting at the top outline level", function() + helpers.new_buffer({ + "# Hello there", + "I. first bullet", + }) + + helpers.feedkeys("ggj<<") + + assert.are.same({ + "# Hello there", + "first bullet", + }, helpers.get_lines()) + end) + + active_it("promotes bullets in a visual range", function() + require("bullets").setup({ outline_levels = { "num", "abc", "std*" } }) + helpers.new_buffer({ + "# Hello there", + "1. first bullet", + "\ta. second bullet", + "\tb. third bullet", + }) + + vim.cmd("3,4BulletPromoteVisual") + + assert.are.same({ + "# Hello there", + "1. first bullet", + "2. second bullet", + "3. third bullet", + }, helpers.get_lines()) + end) + + active_it("demotes bullets in a visual range", function() + require("bullets").setup({ outline_levels = { "num", "abc", "std*" } }) + helpers.new_buffer({ + "# Hello there", + "1. first bullet", + "2. second bullet", + "3. third bullet", + }) + + vim.cmd("3,4BulletDemoteVisual") + + assert.are.same({ + "# Hello there", + "1. first bullet", + "\ta. second bullet", + "\tb. third bullet", + }, helpers.get_lines()) + end) + + pending_it("demotes an existing bullet", function() + helpers.new_buffer({ + "# 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", + }) + -- Go to line 3 (gg + 2j), enter insert, demote with + helpers.feedkeys("gg2ji") + -- Back to normal mode, go down 1 line, demote 3 times with >> + helpers.feedkeys("j>>>>>>") + -- Continue demoting subsequent lines + helpers.feedkeys("j>>>>>>>>") + helpers.feedkeys("j>>>>>>>>>>") + helpers.feedkeys("j>>>>>>>>") + helpers.feedkeys(">>>>") + helpers.feedkeys("j>>>>>>>>") + helpers.feedkeys(">>>>>>") + helpers.feedkeys("j>>>>>>>>") + helpers.feedkeys(">>>>>>>>") + helpers.feedkeys("j>>>>>>>>") + helpers.feedkeys(">>>>>>>>>>") + assert.are.same({ + "# 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\t+ ninth bullet", + }, helpers.get_lines()) + end) + + pending_it("promotes an existing bullet", function() + helpers.new_buffer({ + "# 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", + }) + -- Go to line 3 (gg + 2j), promote with << + helpers.feedkeys("gg2j<<") + -- Go to line 4, enter insert, demote twice with + helpers.feedkeys("ji") + -- Continue promoting subsequent lines + helpers.feedkeys("j<<<<<<") + helpers.feedkeys("j<<<<<<") + helpers.feedkeys("<<<<") + helpers.feedkeys("j<<<<<<") + helpers.feedkeys("<<<<<<") + helpers.feedkeys("j<<<<<<") + helpers.feedkeys("<<<<<<<<") + helpers.feedkeys("j<<<<<<") + helpers.feedkeys("<<<<<<") + helpers.feedkeys("<<<<") + assert.are.same({ + "# 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", + }, helpers.get_lines()) + end) + + pending_it("demotes an empty bullet", function() helpers.new_buffer({ "# Hello there", "I. this is the first bullet", @@ -56,7 +268,7 @@ describe("Bullets.vim", function() }, helpers.get_lines()) end) - it("promotes an empty bullet", function() + pending_it("promotes an empty bullet", function() helpers.new_buffer({ "# Hello there", "I. this is the first bullet", @@ -101,27 +313,42 @@ describe("Bullets.vim", function() }, helpers.get_lines()) end) - it("works with custom outline level definitions", function() - require("bullets").setup({ outline_levels = { "num", "ABC", "std*" } }) + pending_it("works with custom outline level definitions", function() + vim.g.bullets_outline_levels = { "num", "ABC", "std*" } helpers.new_buffer({ "# Hello there", - "1. first bullet", - "\tA. second bullet", - "\t\t* third bullet", - "2. fourth bullet", }) - vim.api.nvim_win_set_cursor(0, { 5, 0 }) - vim.cmd("BulletDemote") - vim.api.nvim_win_set_cursor(0, { 3, 0 }) - vim.cmd("BulletPromote") - vim.api.nvim_win_set_cursor(0, { 4, 0 }) - vim.cmd("BulletDemote") + -- Enter insert at end, CR (non-bullet line header, deferred CR) + helpers.feedkeys("GA") + -- Now in normal mode on new empty line - type the first bullet + helpers.feedkeys("i1. first bullet") + -- CR on bullet line - stays in insert after plugin fires + helpers.feedkeys("Asecond bullet") + -- CR on bullet line, then demote, then type + helpers.feedkeys("Athird bullet") + helpers.feedkeys("Afourth bullet") + helpers.feedkeys("Afifth bullet") + helpers.feedkeys("Asixth bullet") + helpers.feedkeys("Aseventh bullet") + helpers.feedkeys("Aeighth bullet") + -- demote twice, then type + helpers.feedkeys("Aninth bullet") + -- demote once, then type + helpers.feedkeys("Atenth bullet") + helpers.feedkeys("Aeleventh bullet") assert.are.same({ "# Hello there", "1. first bullet", "2. second bullet", - "\t\t\t* third bullet", + "\tA. third bullet", "\tB. fourth bullet", + "\t\t* fifth bullet", + "\t\t* sixth bullet", + "\t\t\t* seventh bullet", + "\t\t\t* eighth bullet", + "\tC. ninth bullet", + "3. tenth bullet", + "4. eleventh bullet", }, helpers.get_lines()) end) @@ -163,7 +390,7 @@ describe("Bullets.vim", function() }, helpers.get_lines()) end) - it("does not nest beyond defined levels", function() + pending_it("does not nest beyond defined levels", function() helpers.new_buffer({ "# Hello there", "I. this is the first bullet", @@ -195,7 +422,7 @@ describe("Bullets.vim", function() }, helpers.get_lines()) end) - it("removes bullet when promoting top level bullet", function() + pending_it("removes bullet when promoting top level bullet", function() helpers.new_buffer({ "# Hello there", "A. this is the first bullet", @@ -270,21 +497,57 @@ describe("Bullets.vim", function() }, helpers.get_lines()) end) - it("changes levels in visual mode", function() - require("bullets").setup({ outline_levels = { "num", "abc", "std*" } }) + pending_it("changes levels in visual mode", function() + vim.g.bullets_outline_levels = { "num", "abc", "std*" } helpers.new_buffer({ "# Hello there", "1. first bullet", "\ta. second bullet", "\tb. third bullet", + "\t\t* fourth bullet", + "\t\t* fifth bullet", + "\t\t\tsixth bullet", + "\t\t* seventh bullet", + "2. eighth bullet", + "\t\ta. ninth bullet", + "\ta. tenth bullet", + "\tb. eleventh bullet", + "3. twelfth bullet", + "\t thirteenth bullet", + "\ta. fourteenth bullet", + "\t\t* fifteenth bullet", + "4. sixteenth bullet", }) - vim.cmd("3,4BulletPromoteVisual") - vim.cmd("3,4BulletDemoteVisual") + -- After each visual < or > operation, the plugin re-enters visual mode (via s:set_selection). + -- Exit visual mode before starting each fresh visual selection. + helpers.feedkeys("gg3jv<") + helpers.feedkeys("jv2j<") + helpers.feedkeys("jvj>") + helpers.feedkeys("jvj<") + -- The plugin leaves us in visual mode with the same selection. + helpers.feedkeys("<") + helpers.feedkeys("jv>") + helpers.feedkeys("3jv2j>") + -- Repeat the operation on the same visual selection. + helpers.feedkeys(">") assert.are.same({ "# Hello there", "1. first bullet", "\ta. second bullet", - "\tb. third bullet", + "2. third bullet", + "\ta. fourth bullet", + "\tb. fifth bullet", + "\t\tsixth bullet", + "\t\t\t* seventh bullet", + "\tc. eighth bullet", + "3. ninth bullet", + "tenth bullet", + "\t\ta. eleventh bullet", + "4. twelfth bullet", + "\t thirteenth bullet", + "\t\t\ta. fourteenth bullet", + "\t\t\t\t* fifteenth bullet", + "\t\ta. sixteenth bullet", }, helpers.get_lines()) end)