From 151707a592c7cadd975698d6ee7fae061263cf0d Mon Sep 17 00:00:00 2001 From: Dorian Karter Date: Wed, 3 Jun 2026 12:16:25 -0500 Subject: [PATCH 1/2] feat: renumber bullet lists --- lua/bullets/actions.lua | 199 ++++++++++++++++++++++++++++++++- lua/bullets/init.lua | 4 +- test/helpers.lua | 1 + test/nested_bullets_spec.lua | 2 +- test/renumber_bullets_spec.lua | 1 - 5 files changed, 200 insertions(+), 7 deletions(-) diff --git a/lua/bullets/actions.lua b/lua/bullets/actions.lua index d078e8e..2339e3f 100644 --- a/lua/bullets/actions.lua +++ b/lua/bullets/actions.lua @@ -6,6 +6,10 @@ local function feed(keys) vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(keys, true, false, true), "n", false) end +local function line_spacing() + return vim.g.bullets_line_spacing or config.options.line_spacing +end + local function style_enabled(marker) for _, style in ipairs(config.options.list_item_styles) do if style == marker then @@ -539,6 +543,185 @@ local function wrapped_owner(lnum, line) return nil end +local function is_ordered(bullet) + return bullet.type == "num" or bullet.type == "abc" or bullet.type == "rom" +end + +local function marker_number(bullet) + if bullet.type == "num" then + return tonumber(bullet.marker) + end + if bullet.type == "abc" then + return ordinal.abc_to_number(bullet.marker) + end + if bullet.type == "rom" then + return ordinal.roman_to_number(bullet.marker) + end + + return nil +end + +local function number_marker(type, value, lower) + if type == "num" then + return tostring(value) + end + if type == "abc" then + return ordinal.number_to_abc(value, lower) + end + if type == "rom" then + return ordinal.number_to_roman(value, lower) + end + + return nil +end + +local function bullet_at(lnum) + local line = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, false)[1] + if not line then + return nil, nil + end + + return resolve_bullet(parse_line(line), lnum), line +end + +local function boundary_bullet_at(lnum) + local bullet, line = bullet_at(lnum) + if bullet then + return bullet, line + end + + return wrapped_owner(lnum, line or ""), line +end + +local function line_indent(lnum) + local line = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, false)[1] or "" + return #(line:match("^%s*") or "") +end + +local function first_bullet_line(lnum, min_indent) + min_indent = min_indent or 0 + if min_indent < 0 then + return -1 + end + + local first = lnum + local row = lnum - 1 + local blank_lines = 0 + while row >= 1 do + local bullet, line = boundary_bullet_at(row) + if bullet and #bullet.indent >= min_indent then + first = row + blank_lines = 0 + elseif line and line:match("^%s*$") then + blank_lines = blank_lines + 1 + if blank_lines >= line_spacing() then + break + end + elseif blank_lines == 0 then + break + end + + row = row - 1 + end + + return first +end + +local function last_bullet_line(lnum, min_indent) + min_indent = min_indent or 0 + if min_indent < 0 then + return -1 + end + + local last = -1 + local blank_lines = 0 + local row = lnum + local line_count = vim.api.nvim_buf_line_count(0) + + while row <= line_count and line_indent(row) >= min_indent do + local bullet, line = boundary_bullet_at(row) + if bullet then + last = row + blank_lines = 0 + elseif line:match("^%s*$") then + blank_lines = blank_lines + 1 + if blank_lines >= line_spacing() then + break + end + end + + row = row + 1 + end + + return last +end + +local function ordered_state(bullet) + local value = marker_number(bullet) + if not value then + return nil + end + + return { + index = 1, + type = bullet.type, + lower = bullet.marker == bullet.marker:lower(), + closure = bullet.closure, + spacing = bullet.spacing, + } +end + +local function reset_child_states(states, indent) + for key, _ in pairs(states) do + if key > indent then + states[key] = nil + end + end +end + +local function renumber_lines(first, last) + local previous_indent = -1 + local states = {} + + for lnum = first, last do + local bullet = bullet_at(lnum) + if bullet then + local indent = #bullet.indent + if indent < previous_indent then + reset_child_states(states, indent) + end + + if is_ordered(bullet) then + local state = states[indent] + if not state or indent > previous_indent then + state = ordered_state(bullet) + states[indent] = state + else + state.index = state.index + 1 + end + + if state then + local marker = number_marker(state.type, state.index, state.lower) + if marker then + local marker_prefix = marker .. state.closure .. state.spacing + if state.pad_width and #marker_prefix < state.pad_width then + marker_prefix = pad_right(marker_prefix, state.pad_width) + elseif state.pad_width and #marker_prefix > state.pad_width then + state.pad_width = #marker_prefix + elseif not state.pad_width then + state.pad_width = #marker_prefix + end + local prefix = bullet.indent .. marker_prefix + vim.api.nvim_buf_set_lines(0, lnum - 1, lnum, false, { prefix .. bullet.text }) + end + end + end + + previous_indent = indent + end + end +end + function M.insert_new_bullet() local mode = vim.fn.mode() local lnum = vim.api.nvim_win_get_cursor(0)[1] @@ -588,9 +771,21 @@ function M.insert_new_bullet() return "" end -function M.renumber_list() end +function M.renumber_list() + local lnum = vim.api.nvim_win_get_cursor(0)[1] + local first = first_bullet_line(lnum) + local last = last_bullet_line(lnum) + if first > 0 and last > 0 then + renumber_lines(first, last) + end +end -function M.renumber_selection() end +function M.renumber_selection(first, last) + first, last = visual_range(first, last) + if first and last then + renumber_lines(first, last) + end +end function M.toggle_checkbox() end diff --git a/lua/bullets/init.lua b/lua/bullets/init.lua index 1ca3fd0..aa5a5b0 100644 --- a/lua/bullets/init.lua +++ b/lua/bullets/init.lua @@ -72,9 +72,7 @@ local function add_plug_mappings() map("n", "(bullets-renumber)", function() actions().renumber_list() end) - map("x", "(bullets-renumber)", function() - actions().renumber_selection() - end) + map("x", "(bullets-renumber)", ":RenumberSelection") map("n", "(bullets-toggle-checkbox)", function() actions().toggle_checkbox() end) diff --git a/test/helpers.lua b/test/helpers.lua index 6b5ee8b..d215146 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -32,6 +32,7 @@ end -- Resets bullets.nvim to the plugin defaults used by the specs. -- Call in before_each for any describe block that mutates config. function M.reset_config() + vim.g.bullets_line_spacing = nil require("bullets").setup({ enabled_file_types = { "markdown", "text", "gitcommit", "scratch" }, enable_in_empty_buffers = true, diff --git a/test/nested_bullets_spec.lua b/test/nested_bullets_spec.lua index 1e775be..6aca7be 100644 --- a/test/nested_bullets_spec.lua +++ b/test/nested_bullets_spec.lua @@ -101,7 +101,7 @@ describe("Bullets.vim", function() "1. first bullet", "2. second bullet", "\t\t\t* third bullet", - "\tB. fourth bullet", + "\tA. fourth bullet", }, helpers.get_lines()) end) diff --git a/test/renumber_bullets_spec.lua b/test/renumber_bullets_spec.lua index 9b009c7..0f25c5f 100644 --- a/test/renumber_bullets_spec.lua +++ b/test/renumber_bullets_spec.lua @@ -1,5 +1,4 @@ local helpers = require("test.helpers") -local it = pending describe("re-numbering", function() before_each(function() From 198dd3e6810b640e815463110088ff31737747e8 Mon Sep 17 00:00:00 2001 From: Dorian Karter Date: Wed, 3 Jun 2026 12:23:19 -0500 Subject: [PATCH 2/2] fix: use lua config for renumber spacing --- lua/bullets/actions.lua | 8 ++------ test/helpers.lua | 1 - test/renumber_bullets_spec.lua | 4 ++-- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/lua/bullets/actions.lua b/lua/bullets/actions.lua index 2339e3f..b7cb63d 100644 --- a/lua/bullets/actions.lua +++ b/lua/bullets/actions.lua @@ -6,10 +6,6 @@ local function feed(keys) vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(keys, true, false, true), "n", false) end -local function line_spacing() - return vim.g.bullets_line_spacing or config.options.line_spacing -end - local function style_enabled(marker) for _, style in ipairs(config.options.list_item_styles) do if style == marker then @@ -614,7 +610,7 @@ local function first_bullet_line(lnum, min_indent) blank_lines = 0 elseif line and line:match("^%s*$") then blank_lines = blank_lines + 1 - if blank_lines >= line_spacing() then + if blank_lines >= config.options.line_spacing then break end elseif blank_lines == 0 then @@ -645,7 +641,7 @@ local function last_bullet_line(lnum, min_indent) blank_lines = 0 elseif line:match("^%s*$") then blank_lines = blank_lines + 1 - if blank_lines >= line_spacing() then + if blank_lines >= config.options.line_spacing then break end end diff --git a/test/helpers.lua b/test/helpers.lua index d215146..6b5ee8b 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -32,7 +32,6 @@ end -- Resets bullets.nvim to the plugin defaults used by the specs. -- Call in before_each for any describe block that mutates config. function M.reset_config() - vim.g.bullets_line_spacing = nil require("bullets").setup({ enabled_file_types = { "markdown", "text", "gitcommit", "scratch" }, enable_in_empty_buffers = true, diff --git a/test/renumber_bullets_spec.lua b/test/renumber_bullets_spec.lua index 0f25c5f..ead0f40 100644 --- a/test/renumber_bullets_spec.lua +++ b/test/renumber_bullets_spec.lua @@ -56,7 +56,7 @@ describe("re-numbering", function() end) it("renumbers a nested list", function() - vim.g.bullets_line_spacing = 2 + require("bullets").setup({ line_spacing = 2 }) helpers.new_buffer({ "# Hello there", "0. zero bullet", @@ -180,7 +180,7 @@ describe("re-numbering", function() end) it("visually renumbers a nested list", function() - vim.g.bullets_line_spacing = 2 + require("bullets").setup({ line_spacing = 2 }) helpers.new_buffer({ "# Hello there", "0. zero bullet",