diff --git a/lua/u/range.lua b/lua/u/range.lua index b8d8c0f..11a409d 100644 --- a/lua/u/range.lua +++ b/lua/u/range.lua @@ -1,6 +1,13 @@ local Pos = require 'u.pos' local ESC = vim.api.nvim_replace_termcodes('', true, false, true) +local NS = vim.api.nvim_create_namespace 'u.range' + +---@class u.ExtmarkRange +---@field bufnr number +---@field id number +local ExtmarkRange = {} +ExtmarkRange.__index = ExtmarkRange --- @class u.Range --- @field start u.Pos @@ -83,6 +90,27 @@ function Range.from_marks(lpos, rpos) return Range.new(start, stop, mode) end +--- @param bufnr number +--- @param id number +function Range.from_extmark(bufnr, id) + ---@type integer, integer, vim.api.keyset.extmark_details | nil + local start_row0, start_col0, details = + unpack(vim.api.nvim_buf_get_extmark_by_id(bufnr, NS, id, { details = true })) + + local start = Pos.new(bufnr, start_row0 + 1, start_col0 + 1) + local stop = details and Pos.new(bufnr, details.end_row + 1, details.end_col) + + local max_line_num = vim.api.nvim_buf_line_count(bufnr) + if stop and stop.lnum > max_line_num then + stop.lnum = max_line_num + stop.col = Pos.MAX_COL + stop = stop:as_real() + end + if stop and stop.col == 0 then stop = stop:must_next(-1) end + + return Range.new(start, stop, 'v') +end + --- @param bufnr? number function Range.from_buf_text(bufnr) if bufnr == nil or bufnr == 0 then bufnr = vim.api.nvim_get_current_buf() end @@ -272,6 +300,13 @@ function Range:to_linewise() return r end +function Range:to_charwise() + local r = self:clone() + r.mode = 'v' + if r.stop:is_col_max() then r.stop = r.stop:as_real() end + return r +end + --- @param x u.Pos | u.Range function Range:contains(x) if getmetatable(x) == Pos then @@ -334,6 +369,15 @@ function Range:save_to_marks(left, right) end end +function Range:save_to_extmark() + local r = self:to_charwise() + local id = vim.api.nvim_buf_set_extmark(r.start.bufnr, NS, r.start.lnum - 1, r.start.col - 1, { + end_row = r.stop.lnum - 1, + end_col = r.stop.col, + }) + return ExtmarkRange.new(r.start.bufnr, id) +end + function Range:set_visual_selection() if self:is_empty() then return end if vim.api.nvim_get_current_buf() ~= self.start.bufnr then @@ -596,4 +640,10 @@ function Range:highlight(group, opts) } end +function ExtmarkRange.new(bufnr, id) return setmetatable({ bufnr = bufnr, id = id }, ExtmarkRange) end + +function ExtmarkRange:range() return Range.from_extmark(self.bufnr, self.id) end + +function ExtmarkRange:delete() vim.api.nvim_buf_del_extmark(self.bufnr, NS, self.id) end + return Range diff --git a/lua/u/utils.lua b/lua/u/utils.lua index d0d7d90..e60933b 100644 --- a/lua/u/utils.lua +++ b/lua/u/utils.lua @@ -7,6 +7,8 @@ local M = {} --- @alias QfItem { col: number, filename: string, kind: string, lnum: number, text: string } --- @alias KeyMaps table } -- luacheck: ignore +--- @alias RawCmdArgs { args: string; bang: boolean; count: number; fargs: string[]; line1: number; line2: number; mods: string; name: string; range: 0|1|2; reg: string; smods: any } +-- luacheck: ignore --- @alias CmdArgs { args: string; bang: boolean; count: number; fargs: string[]; line1: number; line2: number; mods: string; name: string; range: 0|1|2; reg: string; smods: any; info: u.Range|nil } --- @generic T @@ -51,6 +53,17 @@ function M.ucmd(name, cmd, opts) vim.api.nvim_create_user_command(name, cmd2, opts or {}) end +--- @param current_args RawCmdArgs +function M.create_forward_cmd_args(current_args) + local args = { args = current_args.fargs } + if current_args.range == 1 then + args.range = { current_args.line1 } + elseif current_args.range == 2 then + args.range = { current_args.line1, current_args.line2 } + end + return args +end + function M.get_editor_dimensions() return { width = vim.go.columns, height = vim.go.lines } end return M diff --git a/spec/range_spec.lua b/spec/range_spec.lua index f30dd40..063a890 100644 --- a/spec/range_spec.lua +++ b/spec/range_spec.lua @@ -617,4 +617,29 @@ describe('Range', function() }, vim.api.nvim_buf_get_lines(b, 0, -1, false)) end) end) + + it('can save to extmark', function() + withbuf({ + 'The quick brown', + 'fox', + 'jumps', + 'over', + 'the lazy dog', + }, function() + -- Construct a range over 'fox jumps' + local r = Range.new(Pos.new(nil, 2, 1), Pos.new(nil, 3, Pos.MAX_COL), 'V') + local extrange = r:save_to_extmark() + assert.are.same({ 'fox', 'jumps' }, extrange:range():lines()) + -- change 'jumps' to 'leaps': + vim.api.nvim_buf_set_text(extrange.bufnr, 2, 0, 2, 4, { 'leap' }) + assert.are.same({ + 'The quick brown', + 'fox', + 'leaps', + 'over', + 'the lazy dog', + }, vim.api.nvim_buf_get_lines(extrange.bufnr, 0, -1, false)) + assert.are.same({ 'fox', 'leaps' }, extrange:range():lines()) + end) + end) end)