mirror of
https://github.com/neovim/neovim.git
synced 2026-09-13 17:41:10 +00:00
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
216 lines
5.3 KiB
Lua
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
|