add Range:difference
All checks were successful
NeoVim tests / plenary-tests (push) Successful in 9s

This commit is contained in:
Jonathan Apodaca 2025-04-13 14:44:28 -06:00
parent dacb186324
commit 9f5fdb4f2b
4 changed files with 101 additions and 50 deletions

View File

@ -1,11 +1,12 @@
local vim_repeat = require 'u.repeat'
local Pos = require 'u.pos'
local Range = require 'u.range'
local Buffer = require 'u.buffer'
local CodeWriter = require 'u.codewriter'
local M = {}
local ESC = vim.api.nvim_replace_termcodes('<Esc>', true, false, true)
local surrounds = {
[')'] = { left = '(', right = ')' },
['('] = { left = '( ', right = ' )' },
@ -126,7 +127,7 @@ function M.setup()
do_surround(range, bounds)
-- this is a visual mapping: end in normal mode:
vim.cmd { cmd = 'normal', args = { '' }, bang = true }
vim.cmd.normal(ESC)
end, { noremap = true, silent = true })
-- Change

View File

@ -12,8 +12,17 @@ end
--- @field col number 1-based column index
--- @field off number
local Pos = {}
Pos.__index = Pos
Pos.MAX_COL = MAX_COL
function Pos.__tostring(self)
if self.off ~= 0 then
return string.format('Pos(%d:%d){bufnr=%d, off=%d}', self.lnum, self.col, self.bufnr, self.off)
else
return string.format('Pos(%d:%d){bufnr=%d}', self.lnum, self.col, self.bufnr)
end
end
--- @param bufnr? number
--- @param lnum number 1-based
--- @param col number 1-based
@ -28,31 +37,13 @@ function Pos.new(bufnr, lnum, col, off)
col = col,
off = off,
}
local function str()
if pos.off ~= 0 then
return string.format('Pos(%d:%d){bufnr=%d, off=%d}', pos.lnum, pos.col, pos.bufnr, pos.off)
else
return string.format('Pos(%d:%d){bufnr=%d}', pos.lnum, pos.col, pos.bufnr)
end
end
setmetatable(pos, {
__index = Pos,
__tostring = str,
__lt = Pos.__lt,
__le = Pos.__le,
__eq = Pos.__eq,
})
setmetatable(pos, Pos)
return pos
end
function Pos.invalid() return Pos.new(0, 0, 0, 0) end
function Pos.is(x)
if not type(x) == 'table' then return false end
local mt = getmetatable(x)
return mt and mt.__index == Pos
end
function Pos.is(x) return getmetatable(x) == Pos end
function Pos.__lt(a, b) return a.lnum < b.lnum or (a.lnum == b.lnum and a.col < b.col) end
function Pos.__le(a, b) return a < b or a == b end

View File

@ -12,6 +12,29 @@ end
--- @field stop u.Pos|nil
--- @field mode 'v'|'V'
local Range = {}
Range.__index = Range
function Range.__tostring(self)
--- @param p u.Pos
local function posstr(p)
if p == nil then
return 'nil'
elseif p.off ~= 0 then
return string.format('Pos(%d:%d){off=%d}', p.lnum, p.col, p.off)
else
return string.format('Pos(%d:%d)', p.lnum, p.col)
end
end
local _1 = posstr(self.start)
local _2 = posstr(self.stop)
return string.format(
'Range{bufnr=%d, mode=%s, start=%s, stop=%s}',
self.start.bufnr,
self.mode,
_1,
_2
)
end
--- @param start u.Pos
--- @param stop u.Pos|nil
@ -23,36 +46,12 @@ function Range.new(start, stop, mode)
end
local r = { start = start, stop = stop, mode = mode or 'v' }
local function str()
--- @param p u.Pos
local function posstr(p)
if p == nil then
return 'nil'
elseif p.off ~= 0 then
return string.format('Pos(%d:%d){off=%d}', p.lnum, p.col, p.off)
else
return string.format('Pos(%d:%d)', p.lnum, p.col)
end
end
local _1 = posstr(r.start)
local _2 = posstr(r.stop)
return string.format(
'Range{bufnr=%d, mode=%s, start=%s, stop=%s}',
r.start.bufnr,
r.mode,
_1,
_2
)
end
setmetatable(r, { __index = Range, __tostring = str })
setmetatable(r, Range)
return r
end
function Range.is(x)
local mt = getmetatable(x)
return mt and mt.__index == Range
end
function Range.is(x) return getmetatable(x) == Range end
--- @param lpos string
--- @param rpos string
@ -299,8 +298,44 @@ function Range:trim_stop()
return r
end
--- @param p u.Pos
function Range:contains(p) return not self:is_empty() and p >= self.start and p <= self.stop end
--- @param x u.Pos | u.Range
function Range:contains(x)
if getmetatable(x) == Pos then
return not self:is_empty() and x >= self.start and x <= self.stop
elseif getmetatable(x) == Range then
return self:contains(x.start) and self:contains(x.stop)
end
return false
end
--- TODO: test
--- @param other u.Range
--- @return u.Range|nil, u.Range|nil
function Range:difference(other)
local outer, inner = self, other
if not outer:contains(inner) then
outer, inner = inner, outer
end
if not outer:contains(inner) then return nil, nil end
local left
if outer.start ~= inner.start then
local stop = inner.start:clone() - 1
left = Range.new(outer.start, stop)
else
left = Range.new(outer.start) -- empty range
end
local right
if inner.stop ~= outer.stop then
local start = inner.stop:clone() + 1
right = Range.new(start, outer.stop)
else
right = Range.new(inner.stop) -- empty range
end
return left, right
end
--- @return string[]
function Range:lines()

View File

@ -422,6 +422,30 @@ describe('Range', function()
end)
end)
it('difference', function()
withbuf({ 'line one', 'and line two' }, function()
local range_outer = Range.new(Pos.new(nil, 2, 1), Pos.new(nil, 2, 12), 'v')
local range_inner = Range.new(Pos.new(nil, 2, 5), Pos.new(nil, 2, 8), 'v')
assert.are.same(range_outer:text(), 'and line two')
assert.are.same(range_inner:text(), 'line')
local left, right = range_outer:difference(range_inner)
assert.are.same(left:text(), 'and ')
assert.are.same(right:text(), ' two')
left, right = range_inner:difference(range_outer)
assert.are.same(left:text(), 'and ')
assert.are.same(right:text(), ' two')
left, right = range_outer:difference(range_outer)
assert.are.same(left:is_empty(), true)
assert.are.same(left:text(), '')
assert.are.same(right:is_empty(), true)
assert.are.same(right:text(), '')
end)
end)
it('shrink', function()
withbuf({ 'line one', 'and line two' }, function()
local range = Range.new(Pos.new(nil, 2, 3), Pos.new(nil, 3, 5), 'v')