mirror of
https://github.com/neovim/neovim.git
synced 2026-05-23 21:30:11 +00:00
Problem: - To share logic, creating a `vim.Range` currently creates two `vim.Pos` values as intermediates, which causes unnecessary table allocations. - `pos.lua` and `range.lua` contain some overlapping logic. Solution: Add `vim.pos._util`, a module for handling positions represented directly by `row` and `col`.
176 lines
4.0 KiB
Lua
176 lines
4.0 KiB
Lua
local api = vim.api
|
|
local util = require('vim.pos._util')
|
|
|
|
local M = {}
|
|
|
|
---@class Range2
|
|
---@inlinedoc
|
|
---@field [1] integer start row
|
|
---@field [2] integer end row
|
|
|
|
---@class Range4
|
|
---@inlinedoc
|
|
---@field [1] integer start row
|
|
---@field [2] integer start column
|
|
---@field [3] integer end row
|
|
---@field [4] integer end column
|
|
|
|
---@class Range6
|
|
---@inlinedoc
|
|
---@field [1] integer start row
|
|
---@field [2] integer start column
|
|
---@field [3] integer start bytes
|
|
---@field [4] integer end row
|
|
---@field [5] integer end column
|
|
---@field [6] integer end bytes
|
|
|
|
---@alias Range Range2|Range4|Range6
|
|
|
|
-- TODO(ofseed): directly use `cmp_pos` from `util` and replace all exported usages.
|
|
M.cmp_pos = util.cmp_pos
|
|
|
|
---Check if a variable is a valid range object
|
|
---@param r any
|
|
---@return boolean
|
|
function M.validate(r)
|
|
if type(r) ~= 'table' or #r ~= 6 and #r ~= 4 then
|
|
return false
|
|
end
|
|
|
|
for _, e in
|
|
ipairs(r --[[@as any[] ]])
|
|
do
|
|
if type(e) ~= 'number' then
|
|
return false
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
---@param r1 Range
|
|
---@param r2 Range
|
|
---@return boolean
|
|
function M.intercepts(r1, r2)
|
|
local srow_1, scol_1, erow_1, ecol_1 = M.unpack4(r1)
|
|
local srow_2, scol_2, erow_2, ecol_2 = M.unpack4(r2)
|
|
|
|
-- r1 is above r2
|
|
if M.cmp_pos.le(erow_1, ecol_1, srow_2, scol_2) then
|
|
return false
|
|
end
|
|
|
|
-- r1 is below r2
|
|
if M.cmp_pos.ge(srow_1, scol_1, erow_2, ecol_2) then
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
---@param r1 Range6
|
|
---@param r2 Range6
|
|
---@return Range6?
|
|
---@overload fun(r1:Range4,r2:Range4):Range4?
|
|
function M.intersection(r1, r2)
|
|
if not M.intercepts(r1, r2) then
|
|
return nil
|
|
end
|
|
|
|
if #r1 == 4 or #r2 == 4 then
|
|
local rs = M.cmp_pos.le(r1[1], r1[2], r2[1], r2[2]) and r2 or r1
|
|
local re = M.cmp_pos.ge(r1[3], r1[4], r2[3], r2[4]) and r2 or r1
|
|
return { rs[1], rs[2], re[3], re[4] }
|
|
end
|
|
|
|
local rs = M.cmp_pos.le(r1[1], r1[2], r2[1], r2[2]) and r2 or r1
|
|
local re = M.cmp_pos.ge(r1[4], r1[5], r2[4], r2[5]) and r2 or r1
|
|
return { rs[1], rs[2], rs[3], re[4], re[5], re[6] }
|
|
end
|
|
|
|
---@param r Range
|
|
---@return integer, integer, integer, integer
|
|
function M.unpack4(r)
|
|
if #r == 2 then
|
|
return r[1], 0, r[2], 0
|
|
end
|
|
local off_1 = #r == 6 and 1 or 0
|
|
return r[1], r[2], r[3 + off_1], r[4 + off_1]
|
|
end
|
|
|
|
---@param r Range6
|
|
---@return integer, integer, integer, integer, integer, integer
|
|
function M.unpack6(r)
|
|
return r[1], r[2], r[3], r[4], r[5], r[6]
|
|
end
|
|
|
|
---@param r1 Range
|
|
---@param r2 Range
|
|
---@return boolean whether r1 contains r2
|
|
function M.contains(r1, r2)
|
|
local srow_1, scol_1, erow_1, ecol_1 = M.unpack4(r1)
|
|
local srow_2, scol_2, erow_2, ecol_2 = M.unpack4(r2)
|
|
|
|
-- start doesn't fit
|
|
if M.cmp_pos.gt(srow_1, scol_1, srow_2, scol_2) then
|
|
return false
|
|
end
|
|
|
|
-- end doesn't fit
|
|
if M.cmp_pos.lt(erow_1, ecol_1, erow_2, ecol_2) then
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
--- @param r1 Range4
|
|
--- @param r2 Range4
|
|
--- @return boolean
|
|
function M.equal(r1, r2)
|
|
local srow_1, scol_1, erow_1, ecol_1 = M.unpack4(r1)
|
|
local srow_2, scol_2, erow_2, ecol_2 = M.unpack4(r2)
|
|
return srow_1 == srow_2 and scol_1 == scol_2 and erow_1 == erow_2 and ecol_1 == ecol_2
|
|
end
|
|
|
|
--- @param source integer|string
|
|
--- @param index integer
|
|
--- @return integer
|
|
local function get_offset(source, index)
|
|
if index == 0 then
|
|
return 0
|
|
end
|
|
|
|
if type(source) == 'number' then
|
|
return api.nvim_buf_get_offset(source, index)
|
|
end
|
|
|
|
local byte = 0
|
|
local next_offset = source:gmatch('()\n')
|
|
local line = 1
|
|
while line <= index do
|
|
byte = next_offset() --[[@as integer]]
|
|
line = line + 1
|
|
end
|
|
|
|
return byte
|
|
end
|
|
|
|
---@param source integer|string
|
|
---@param range Range
|
|
---@return Range6
|
|
function M.add_bytes(source, range)
|
|
if type(range) == 'table' and #range == 6 then
|
|
return range --[[@as Range6]]
|
|
end
|
|
|
|
local start_row, start_col, end_row, end_col = M.unpack4(range)
|
|
-- TODO(vigoux): proper byte computation here, and account for EOL ?
|
|
local start_byte = get_offset(source, start_row) + start_col
|
|
local end_byte = get_offset(source, end_row) + end_col
|
|
|
|
return { start_row, start_col, start_byte, end_row, end_col, end_byte }
|
|
end
|
|
|
|
return M
|