Files
neovim/runtime/lua/vim/treesitter/_range.lua
Lewis Russell d039f19af5 build: enable more EmmyLua checks
Enable checks for unused code, deprecated calls, return types, and
annotations.

Fix the warnings and replace the deprecated highlight function. Keep
exceptions where old APIs are still needed or the checker gets it wrong.

AI-assisted
2026-09-09 22:31:58 +01:00

216 lines
5.3 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
-- This branch implements the Range4 overload.
---@diagnostic disable-next-line: return-type-mismatch
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
-- EmmyLua does not narrow tuple fields from the range length.
---@diagnostic disable-next-line: return-type-mismatch
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
---@param range Range
function M.visual_select(range)
local start_row, start_col, end_row, end_col = M.unpack4(range)
-- If the selection ends at column 0, adjust the position to the end of the previous line.
if end_col == 0 then
end_row = end_row - 1
end_col = #vim.fn.getline(end_row + 1) + 1
end
if vim.fn.visualmode() ~= 'v' then
-- Reset visualmode() to 'v'
vim.cmd.normal({ 'v\27', bang = true })
end
local cursor_other_end_of_selection = false
local visual_col, visual_row = vim.fn.col('v'), vim.fn.line('v')
local cursor_col, cursor_row = vim.fn.col('.'), vim.fn.line('.')
if M.cmp_pos.gt(visual_row, visual_col, cursor_row, cursor_col) then
cursor_other_end_of_selection = true
end
if vim.o.selection == 'exclusive' then
end_col = end_col + 1
end
vim.fn.setpos("'<", { 0, start_row + 1, start_col + 1, 0 })
vim.fn.setpos("'>", { 0, end_row + 1, end_col, 0 })
if cursor_other_end_of_selection then
vim.cmd.normal({ 'gvo', bang = true })
else
vim.cmd.normal({ 'gv', bang = true })
end
end
return M