mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 03:18:16 +00:00
Merge #35109 vim.pos, vim.range
This commit is contained in:
@@ -3949,6 +3949,205 @@ vim.net.request({url}, {opts}, {on_response}) *vim.net.request()*
|
|||||||
success.
|
success.
|
||||||
|
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
Lua module: vim.pos *vim.pos*
|
||||||
|
|
||||||
|
WARNING: This module is under experimental support. Its semantics are not yet
|
||||||
|
finalized, and the stability of this API is not guaranteed. Avoid using it
|
||||||
|
outside of Nvim. You may subscribe to or participate in the tracking issue
|
||||||
|
https://github.com/neovim/neovim/issues/25509 to stay updated or contribute to
|
||||||
|
its development.
|
||||||
|
|
||||||
|
Built on |vim.Pos| objects, this module offers operations that support
|
||||||
|
comparisons and conversions between various types of positions.
|
||||||
|
|
||||||
|
|
||||||
|
*vim.Pos*
|
||||||
|
Represents a well-defined position.
|
||||||
|
|
||||||
|
A |vim.Pos| object contains the {row} and {col} coordinates of a position.
|
||||||
|
To create a new |vim.Pos| object, call `vim.pos()`.
|
||||||
|
|
||||||
|
Example: >lua
|
||||||
|
local pos1 = vim.pos(3, 5)
|
||||||
|
local pos2 = vim.pos(4, 0)
|
||||||
|
|
||||||
|
-- Operators are overloaded for comparing two `vim.Pos` objects.
|
||||||
|
if pos1 < pos2 then
|
||||||
|
print("pos1 comes before pos2")
|
||||||
|
end
|
||||||
|
|
||||||
|
if pos1 ~= pos2 then
|
||||||
|
print("pos1 and pos2 are different positions")
|
||||||
|
end
|
||||||
|
<
|
||||||
|
|
||||||
|
It may include optional fields that enable additional capabilities, such
|
||||||
|
as format conversions.
|
||||||
|
|
||||||
|
Fields: ~
|
||||||
|
• {row} (`integer`) 0-based byte index.
|
||||||
|
• {col} (`integer`) 0-based byte index.
|
||||||
|
• {buf}? (`integer`) Optional buffer handle.
|
||||||
|
|
||||||
|
When specified, it indicates that this position belongs to a
|
||||||
|
specific buffer. This field is required when performing
|
||||||
|
position conversions.
|
||||||
|
• {to_lsp} (`fun(pos: vim.Pos, position_encoding: lsp.PositionEncodingKind)`)
|
||||||
|
See |Pos:to_lsp()|.
|
||||||
|
• {lsp} (`fun(buf: integer, pos: lsp.Position, position_encoding: lsp.PositionEncodingKind)`)
|
||||||
|
See |Pos:lsp()|.
|
||||||
|
|
||||||
|
|
||||||
|
Pos:lsp({buf}, {pos}, {position_encoding}) *Pos:lsp()*
|
||||||
|
Creates a new |vim.Pos| from `lsp.Position`.
|
||||||
|
|
||||||
|
Example: >lua
|
||||||
|
local buf = vim.api.nvim_get_current_buf()
|
||||||
|
local lsp_pos = {
|
||||||
|
line = 3,
|
||||||
|
character = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
-- `buf` is mandatory, as LSP positions are always associated with a buffer.
|
||||||
|
local pos = vim.pos.lsp(buf, lsp_pos, 'utf-16')
|
||||||
|
<
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
• {buf} (`integer`)
|
||||||
|
• {pos} (`lsp.Position`)
|
||||||
|
• {position_encoding} (`lsp.PositionEncodingKind`)
|
||||||
|
|
||||||
|
Pos:to_lsp({pos}, {position_encoding}) *Pos:to_lsp()*
|
||||||
|
Converts |vim.Pos| to `lsp.Position`.
|
||||||
|
|
||||||
|
Example: >lua
|
||||||
|
-- `buf` is required for conversion to LSP position.
|
||||||
|
local buf = vim.api.nvim_get_current_buf()
|
||||||
|
local pos = vim.pos(3, 5, { buf = buf })
|
||||||
|
|
||||||
|
-- Convert to LSP position, you can call it in a method style.
|
||||||
|
local lsp_pos = pos:lsp('utf-16')
|
||||||
|
<
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
• {pos} (`vim.Pos`) See |vim.Pos|.
|
||||||
|
• {position_encoding} (`lsp.PositionEncodingKind`)
|
||||||
|
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
Lua module: vim.range *vim.range*
|
||||||
|
|
||||||
|
WARNING: This module is under experimental support. Its semantics are not yet
|
||||||
|
finalized, and the stability of this API is not guaranteed. Avoid using it
|
||||||
|
outside of Nvim. You may subscribe to or participate in the tracking issue
|
||||||
|
https://github.com/neovim/neovim/issues/25509 to stay updated or contribute to
|
||||||
|
its development.
|
||||||
|
|
||||||
|
Built on |vim.Range| objects, this module offers operations that support
|
||||||
|
comparisons as well as containment checks (for positions and for other
|
||||||
|
ranges). conversions between various types of ranges is also provided.
|
||||||
|
|
||||||
|
|
||||||
|
*vim.Range*
|
||||||
|
Represents a well-defined range.
|
||||||
|
|
||||||
|
A |vim.Range| object contains a {start} and a {end_} position(see
|
||||||
|
|vim.Pos|). Note that the {end_} position is exclusive. To create a new
|
||||||
|
|vim.Range| object, call `vim.range()`.
|
||||||
|
|
||||||
|
Example: >lua
|
||||||
|
local pos1 = vim.pos(3, 5)
|
||||||
|
local pos2 = vim.pos(4, 0)
|
||||||
|
|
||||||
|
-- Create a range from two positions.
|
||||||
|
local range1 = vim.range(pos1, pos2)
|
||||||
|
-- Or createa range from four integers representing start and end positions.
|
||||||
|
local range2 = vim.range(3, 5, 4, 0)
|
||||||
|
|
||||||
|
-- Because `vim.Range` is end exclusive, `range1` and `range2` both represent
|
||||||
|
-- a range starting at the row 3, column 5 and ending at where the row 3 ends.
|
||||||
|
|
||||||
|
-- Operators are overloaded for comparing two `vim.Pos` objects.
|
||||||
|
if range1 == range2 then
|
||||||
|
print("range1 and range2 are the same range")
|
||||||
|
end
|
||||||
|
<
|
||||||
|
|
||||||
|
It may include optional fields that enable additional capabilities, such
|
||||||
|
as format conversions. Note that the {start} and {end_} positions need to
|
||||||
|
have the same optional fields.
|
||||||
|
|
||||||
|
Fields: ~
|
||||||
|
• {start} (`vim.Pos`) Start position.
|
||||||
|
• {end_} (`vim.Pos`) End position, exclusive.
|
||||||
|
• {has} (`fun(outer: vim.Range, inner: vim.Range): boolean`) See
|
||||||
|
|Range:has()|.
|
||||||
|
• {intersect} (`fun(r1: vim.Range, r2: vim.Range): vim.Range?`) See
|
||||||
|
|Range:intersect()|.
|
||||||
|
• {to_lsp} (`fun(range: vim.Range, position_encoding: lsp.PositionEncodingKind)`)
|
||||||
|
See |Range:to_lsp()|.
|
||||||
|
• {lsp} (`fun(buf: integer, range: lsp.Range, position_encoding: lsp.PositionEncodingKind)`)
|
||||||
|
See |Range:lsp()|.
|
||||||
|
|
||||||
|
|
||||||
|
Range:has({outer}, {inner}) *Range:has()*
|
||||||
|
Checks whether {outer} range contains {inner} range.
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
• {outer} (`vim.Range`) See |vim.Range|.
|
||||||
|
• {inner} (`vim.Range`) See |vim.Range|.
|
||||||
|
|
||||||
|
Return: ~
|
||||||
|
(`boolean`) `true` if {outer} range fully contains {inner} range.
|
||||||
|
|
||||||
|
Range:intersect({r1}, {r2}) *Range:intersect()*
|
||||||
|
Computes the common range shared by the given ranges.
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
• {r1} (`vim.Range`) First range to intersect. See |vim.Range|.
|
||||||
|
• {r2} (`vim.Range`) Second range to intersect. See |vim.Range|.
|
||||||
|
|
||||||
|
Return: ~
|
||||||
|
(`vim.Range?`) range that is present inside both `r1` and `r2`. `nil`
|
||||||
|
if such range does not exist. See |vim.Range|.
|
||||||
|
|
||||||
|
Range:lsp({buf}, {range}, {position_encoding}) *Range:lsp()*
|
||||||
|
Creates a new |vim.Range| from `lsp.Range`.
|
||||||
|
|
||||||
|
Example: >lua
|
||||||
|
local buf = vim.api.nvim_get_current_buf()
|
||||||
|
local lsp_range = {
|
||||||
|
['start'] = { line = 3, character = 5 },
|
||||||
|
['end'] = { line = 4, character = 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
-- `buf` is mandatory, as LSP ranges are always associated with a buffer.
|
||||||
|
local range = vim.range.lsp(buf, lsp_range, 'utf-16')
|
||||||
|
<
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
• {buf} (`integer`)
|
||||||
|
• {range} (`lsp.Range`)
|
||||||
|
• {position_encoding} (`lsp.PositionEncodingKind`)
|
||||||
|
|
||||||
|
Range:to_lsp({range}, {position_encoding}) *Range:to_lsp()*
|
||||||
|
Converts |vim.Range| to `lsp.Range`.
|
||||||
|
|
||||||
|
Example: >lua
|
||||||
|
-- `buf` is required for conversion to LSP range.
|
||||||
|
local buf = vim.api.nvim_get_current_buf()
|
||||||
|
local range = vim.range(3, 5, 4, 0, { buf = buf })
|
||||||
|
|
||||||
|
-- Convert to LSP range, you can call it in a method style.
|
||||||
|
local lsp_range = range:to_lsp('utf-16')
|
||||||
|
<
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
• {range} (`vim.Range`) See |vim.Range|.
|
||||||
|
• {position_encoding} (`lsp.PositionEncodingKind`)
|
||||||
|
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
Lua module: vim.re *vim.re*
|
Lua module: vim.re *vim.re*
|
||||||
|
|
||||||
|
@@ -241,6 +241,7 @@ LUA
|
|||||||
• Built-in plugin manager |vim.pack|
|
• Built-in plugin manager |vim.pack|
|
||||||
• |vim.list.unique()| to deduplicate lists.
|
• |vim.list.unique()| to deduplicate lists.
|
||||||
• |vim.list.bisect()| for binary search.
|
• |vim.list.bisect()| for binary search.
|
||||||
|
• Experimental `vim.pos` and `vim.range` for Position/Range abstraction.
|
||||||
|
|
||||||
OPTIONS
|
OPTIONS
|
||||||
|
|
||||||
|
@@ -42,6 +42,8 @@ for k, v in pairs({
|
|||||||
pack = true,
|
pack = true,
|
||||||
_watch = true,
|
_watch = true,
|
||||||
net = true,
|
net = true,
|
||||||
|
pos = true,
|
||||||
|
range = true,
|
||||||
}) do
|
}) do
|
||||||
vim._submodules[k] = v
|
vim._submodules[k] = v
|
||||||
end
|
end
|
||||||
|
@@ -21,6 +21,8 @@ vim.keymap = require('vim.keymap')
|
|||||||
vim.loader = require('vim.loader')
|
vim.loader = require('vim.loader')
|
||||||
vim.lsp = require('vim.lsp')
|
vim.lsp = require('vim.lsp')
|
||||||
vim.pack = require('vim.pack')
|
vim.pack = require('vim.pack')
|
||||||
|
vim.pos = require('vim.pos')
|
||||||
|
vim.range = require('vim.range')
|
||||||
vim.re = require('vim.re')
|
vim.re = require('vim.re')
|
||||||
vim.secure = require('vim.secure')
|
vim.secure = require('vim.secure')
|
||||||
vim.snippet = require('vim.snippet')
|
vim.snippet = require('vim.snippet')
|
||||||
|
189
runtime/lua/vim/pos.lua
Normal file
189
runtime/lua/vim/pos.lua
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
---@brief
|
||||||
|
---
|
||||||
|
--- WARNING: This module is under experimental support.
|
||||||
|
--- Its semantics are not yet finalized,
|
||||||
|
--- and the stability of this API is not guaranteed.
|
||||||
|
--- Avoid using it outside of Nvim.
|
||||||
|
--- You may subscribe to or participate in the tracking issue
|
||||||
|
--- https://github.com/neovim/neovim/issues/25509
|
||||||
|
--- to stay updated or contribute to its development.
|
||||||
|
---
|
||||||
|
--- Built on |vim.Pos| objects, this module offers operations
|
||||||
|
--- that support comparisons and conversions between various types of positions.
|
||||||
|
|
||||||
|
local api = vim.api
|
||||||
|
local validate = vim.validate
|
||||||
|
|
||||||
|
--- Represents a well-defined position.
|
||||||
|
---
|
||||||
|
--- A |vim.Pos| object contains the {row} and {col} coordinates of a position.
|
||||||
|
--- To create a new |vim.Pos| object, call `vim.pos()`.
|
||||||
|
---
|
||||||
|
--- Example:
|
||||||
|
--- ```lua
|
||||||
|
--- local pos1 = vim.pos(3, 5)
|
||||||
|
--- local pos2 = vim.pos(4, 0)
|
||||||
|
---
|
||||||
|
--- -- Operators are overloaded for comparing two `vim.Pos` objects.
|
||||||
|
--- if pos1 < pos2 then
|
||||||
|
--- print("pos1 comes before pos2")
|
||||||
|
--- end
|
||||||
|
---
|
||||||
|
--- if pos1 ~= pos2 then
|
||||||
|
--- print("pos1 and pos2 are different positions")
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
---
|
||||||
|
--- It may include optional fields that enable additional capabilities,
|
||||||
|
--- such as format conversions.
|
||||||
|
---
|
||||||
|
---@class vim.Pos
|
||||||
|
---@field row integer 0-based byte index.
|
||||||
|
---@field col integer 0-based byte index.
|
||||||
|
---
|
||||||
|
--- Optional buffer handle.
|
||||||
|
---
|
||||||
|
--- When specified, it indicates that this position belongs to a specific buffer.
|
||||||
|
--- This field is required when performing position conversions.
|
||||||
|
---@field buf? integer
|
||||||
|
local Pos = {}
|
||||||
|
Pos.__index = Pos
|
||||||
|
|
||||||
|
---@class vim.Pos.Optional
|
||||||
|
---@inlinedoc
|
||||||
|
---@field buf? integer
|
||||||
|
|
||||||
|
---@package
|
||||||
|
---@param row integer
|
||||||
|
---@param col integer
|
||||||
|
---@param opts vim.Pos.Optional
|
||||||
|
function Pos.new(row, col, opts)
|
||||||
|
validate('row', row, 'number')
|
||||||
|
validate('col', col, 'number')
|
||||||
|
validate('opts', opts, 'table', true)
|
||||||
|
|
||||||
|
opts = opts or {}
|
||||||
|
|
||||||
|
---@type vim.Pos
|
||||||
|
local self = setmetatable({
|
||||||
|
row = row,
|
||||||
|
col = col,
|
||||||
|
buf = opts.buf,
|
||||||
|
}, Pos)
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param p1 vim.Pos First position to compare.
|
||||||
|
---@param p2 vim.Pos Second position to compare.
|
||||||
|
---@return integer
|
||||||
|
--- 1: a > b
|
||||||
|
--- 0: a == b
|
||||||
|
--- -1: a < b
|
||||||
|
local function cmp_pos(p1, p2)
|
||||||
|
if p1.row == p2.row then
|
||||||
|
if p1.col > p2.col then
|
||||||
|
return 1
|
||||||
|
elseif p1.col < p2.col then
|
||||||
|
return -1
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
elseif p1.row > p2.row then
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return -1
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
function Pos.__lt(...)
|
||||||
|
return cmp_pos(...) == -1
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
function Pos.__le(...)
|
||||||
|
return cmp_pos(...) ~= 1
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
function Pos.__eq(...)
|
||||||
|
return cmp_pos(...) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
--- TODO(ofseed): Make it work for unloaded buffers. Check get_line() in vim.lsp.util.
|
||||||
|
---@param buf integer
|
||||||
|
---@param row integer
|
||||||
|
local function get_line(buf, row)
|
||||||
|
return api.nvim_buf_get_lines(buf, row, row + 1, true)[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Converts |vim.Pos| to `lsp.Position`.
|
||||||
|
---
|
||||||
|
--- Example:
|
||||||
|
--- ```lua
|
||||||
|
--- -- `buf` is required for conversion to LSP position.
|
||||||
|
--- local buf = vim.api.nvim_get_current_buf()
|
||||||
|
--- local pos = vim.pos(3, 5, { buf = buf })
|
||||||
|
---
|
||||||
|
--- -- Convert to LSP position, you can call it in a method style.
|
||||||
|
--- local lsp_pos = pos:lsp('utf-16')
|
||||||
|
--- ```
|
||||||
|
---@param pos vim.Pos
|
||||||
|
---@param position_encoding lsp.PositionEncodingKind
|
||||||
|
function Pos.to_lsp(pos, position_encoding)
|
||||||
|
validate('pos', pos, 'table')
|
||||||
|
validate('position_encoding', position_encoding, 'string')
|
||||||
|
|
||||||
|
local buf = assert(pos.buf, 'position is not a buffer position')
|
||||||
|
local row, col = pos.row, pos.col
|
||||||
|
-- When on the first character,
|
||||||
|
-- we can ignore the difference between byte and character.
|
||||||
|
if col > 0 then
|
||||||
|
col = vim.str_utfindex(get_line(buf, row), position_encoding, col, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type lsp.Position
|
||||||
|
return { line = row, character = col }
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Creates a new |vim.Pos| from `lsp.Position`.
|
||||||
|
---
|
||||||
|
--- Example:
|
||||||
|
--- ```lua
|
||||||
|
--- local buf = vim.api.nvim_get_current_buf()
|
||||||
|
--- local lsp_pos = {
|
||||||
|
--- line = 3,
|
||||||
|
--- character = 5
|
||||||
|
--- }
|
||||||
|
---
|
||||||
|
--- -- `buf` is mandatory, as LSP positions are always associated with a buffer.
|
||||||
|
--- local pos = vim.pos.lsp(buf, lsp_pos, 'utf-16')
|
||||||
|
--- ```
|
||||||
|
---@param buf integer
|
||||||
|
---@param pos lsp.Position
|
||||||
|
---@param position_encoding lsp.PositionEncodingKind
|
||||||
|
function Pos.lsp(buf, pos, position_encoding)
|
||||||
|
validate('buf', buf, 'number')
|
||||||
|
validate('pos', pos, 'table')
|
||||||
|
validate('position_encoding', position_encoding, 'string')
|
||||||
|
|
||||||
|
local row, col = pos.line, pos.character
|
||||||
|
-- When on the first character,
|
||||||
|
-- we can ignore the difference between byte and character.
|
||||||
|
if col > 0 then
|
||||||
|
col = vim.str_byteindex(get_line(buf, row), position_encoding, col)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Pos.new(row, col, { buf = buf })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Overload `Range.new` to allow calling this module as a function.
|
||||||
|
setmetatable(Pos, {
|
||||||
|
__call = function(_, ...)
|
||||||
|
return Pos.new(...)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
---@cast Pos +fun(row: integer, col: integer, opts: vim.Pos.Optional?): vim.Pos
|
||||||
|
|
||||||
|
return Pos
|
194
runtime/lua/vim/range.lua
Normal file
194
runtime/lua/vim/range.lua
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
---@brief
|
||||||
|
---
|
||||||
|
--- WARNING: This module is under experimental support.
|
||||||
|
--- Its semantics are not yet finalized,
|
||||||
|
--- and the stability of this API is not guaranteed.
|
||||||
|
--- Avoid using it outside of Nvim.
|
||||||
|
--- You may subscribe to or participate in the tracking issue
|
||||||
|
--- https://github.com/neovim/neovim/issues/25509
|
||||||
|
--- to stay updated or contribute to its development.
|
||||||
|
---
|
||||||
|
--- Built on |vim.Range| objects, this module offers operations
|
||||||
|
--- that support comparisons as well as containment checks
|
||||||
|
--- (for positions and for other ranges).
|
||||||
|
--- conversions between various types of ranges is also provided.
|
||||||
|
|
||||||
|
local validate = vim.validate
|
||||||
|
|
||||||
|
--- Represents a well-defined range.
|
||||||
|
---
|
||||||
|
--- A |vim.Range| object contains a {start} and a {end_} position(see |vim.Pos|).
|
||||||
|
--- Note that the {end_} position is exclusive.
|
||||||
|
--- To create a new |vim.Range| object, call `vim.range()`.
|
||||||
|
---
|
||||||
|
--- Example:
|
||||||
|
--- ```lua
|
||||||
|
--- local pos1 = vim.pos(3, 5)
|
||||||
|
--- local pos2 = vim.pos(4, 0)
|
||||||
|
---
|
||||||
|
--- -- Create a range from two positions.
|
||||||
|
--- local range1 = vim.range(pos1, pos2)
|
||||||
|
--- -- Or createa range from four integers representing start and end positions.
|
||||||
|
--- local range2 = vim.range(3, 5, 4, 0)
|
||||||
|
---
|
||||||
|
--- -- Because `vim.Range` is end exclusive, `range1` and `range2` both represent
|
||||||
|
--- -- a range starting at the row 3, column 5 and ending at where the row 3 ends.
|
||||||
|
---
|
||||||
|
--- -- Operators are overloaded for comparing two `vim.Pos` objects.
|
||||||
|
--- if range1 == range2 then
|
||||||
|
--- print("range1 and range2 are the same range")
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
---
|
||||||
|
--- It may include optional fields that enable additional capabilities,
|
||||||
|
--- such as format conversions. Note that the {start} and {end_} positions
|
||||||
|
--- need to have the same optional fields.
|
||||||
|
---
|
||||||
|
---@class vim.Range
|
||||||
|
---@field start vim.Pos Start position.
|
||||||
|
---@field end_ vim.Pos End position, exclusive.
|
||||||
|
local Range = {}
|
||||||
|
Range.__index = Range
|
||||||
|
|
||||||
|
---@package
|
||||||
|
---@overload fun(self: vim.Range, start: vim.Pos, end_: vim.Pos): vim.Range
|
||||||
|
---@overload fun(self: vim.Range, start_row: integer, start_col: integer, end_row: integer, end_col: integer, opts?: vim.Pos.Optional): vim.Range
|
||||||
|
function Range.new(...)
|
||||||
|
---@type vim.Pos, vim.Pos, vim.Pos.Optional
|
||||||
|
local start, end_
|
||||||
|
|
||||||
|
local nargs = select('#', ...)
|
||||||
|
if nargs == 2 then
|
||||||
|
---@type vim.Pos, vim.Pos
|
||||||
|
start, end_ = ...
|
||||||
|
validate('start', start, 'table')
|
||||||
|
validate('end_', end_, 'table')
|
||||||
|
|
||||||
|
if start.buf ~= end_.buf then
|
||||||
|
error('start and end positions must belong to the same buffer')
|
||||||
|
end
|
||||||
|
elseif nargs == 4 or nargs == 5 then
|
||||||
|
---@type integer, integer, integer, integer, vim.Pos.Optional
|
||||||
|
local start_row, start_col, end_row, end_col, opts = ...
|
||||||
|
start, end_ = vim.pos(start_row, start_col, opts), vim.pos(end_row, end_col, opts)
|
||||||
|
else
|
||||||
|
error('invalid parameters')
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type vim.Range
|
||||||
|
local self = setmetatable({
|
||||||
|
start = start,
|
||||||
|
end_ = end_,
|
||||||
|
}, Range)
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param r1 vim.Range
|
||||||
|
---@param r2 vim.Range
|
||||||
|
function Range.__lt(r1, r2)
|
||||||
|
return r1.end_ < r2.start
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param r1 vim.Range
|
||||||
|
---@param r2 vim.Range
|
||||||
|
function Range.__le(r1, r2)
|
||||||
|
return r1.end_ <= r2.start
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param r1 vim.Range
|
||||||
|
---@param r2 vim.Range
|
||||||
|
function Range.__eq(r1, r2)
|
||||||
|
return r1.start == r2.start and r1.end_ == r2.end_
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Checks whether {outer} range contains {inner} range.
|
||||||
|
---
|
||||||
|
---@param outer vim.Range
|
||||||
|
---@param inner vim.Range
|
||||||
|
---@return boolean `true` if {outer} range fully contains {inner} range.
|
||||||
|
function Range.has(outer, inner)
|
||||||
|
return outer.start <= inner.start and outer.end_ >= inner.end_
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Computes the common range shared by the given ranges.
|
||||||
|
---
|
||||||
|
---@param r1 vim.Range First range to intersect.
|
||||||
|
---@param r2 vim.Range Second range to intersect
|
||||||
|
---@return vim.Range? range that is present inside both `r1` and `r2`.
|
||||||
|
--- `nil` if such range does not exist.
|
||||||
|
function Range.intersect(r1, r2)
|
||||||
|
if r1.end_ <= r2.start or r1.start >= r2.end_ then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local rs = r1.start <= r2.start and r2 or r1
|
||||||
|
local re = r1.end_ >= r2.end_ and r2 or r1
|
||||||
|
return Range.new(rs.start, re.end_)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Converts |vim.Range| to `lsp.Range`.
|
||||||
|
---
|
||||||
|
--- Example:
|
||||||
|
--- ```lua
|
||||||
|
--- -- `buf` is required for conversion to LSP range.
|
||||||
|
--- local buf = vim.api.nvim_get_current_buf()
|
||||||
|
--- local range = vim.range(3, 5, 4, 0, { buf = buf })
|
||||||
|
---
|
||||||
|
--- -- Convert to LSP range, you can call it in a method style.
|
||||||
|
--- local lsp_range = range:to_lsp('utf-16')
|
||||||
|
--- ```
|
||||||
|
---@param range vim.Range
|
||||||
|
---@param position_encoding lsp.PositionEncodingKind
|
||||||
|
function Range.to_lsp(range, position_encoding)
|
||||||
|
validate('range', range, 'table')
|
||||||
|
validate('position_encoding', position_encoding, 'string', true)
|
||||||
|
|
||||||
|
---@type lsp.Range
|
||||||
|
return {
|
||||||
|
['start'] = range.start:to_lsp(position_encoding),
|
||||||
|
['end'] = range.end_:to_lsp(position_encoding),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Creates a new |vim.Range| from `lsp.Range`.
|
||||||
|
---
|
||||||
|
--- Example:
|
||||||
|
--- ```lua
|
||||||
|
--- local buf = vim.api.nvim_get_current_buf()
|
||||||
|
--- local lsp_range = {
|
||||||
|
--- ['start'] = { line = 3, character = 5 },
|
||||||
|
--- ['end'] = { line = 4, character = 0 }
|
||||||
|
--- }
|
||||||
|
---
|
||||||
|
--- -- `buf` is mandatory, as LSP ranges are always associated with a buffer.
|
||||||
|
--- local range = vim.range.lsp(buf, lsp_range, 'utf-16')
|
||||||
|
--- ```
|
||||||
|
---@param buf integer
|
||||||
|
---@param range lsp.Range
|
||||||
|
---@param position_encoding lsp.PositionEncodingKind
|
||||||
|
function Range.lsp(buf, range, position_encoding)
|
||||||
|
validate('buf', buf, 'number')
|
||||||
|
validate('range', range, 'table')
|
||||||
|
validate('position_encoding', position_encoding, 'string')
|
||||||
|
|
||||||
|
-- TODO(ofseed): avoid using `Pos:lsp()` here,
|
||||||
|
-- as they need reading files separately if buffer is unloaded.
|
||||||
|
local start = vim.pos.lsp(buf, range['start'], position_encoding)
|
||||||
|
local end_ = vim.pos.lsp(buf, range['end'], position_encoding)
|
||||||
|
|
||||||
|
return Range.new(start, end_)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Overload `Range.new` to allow calling this module as a function.
|
||||||
|
setmetatable(Range, {
|
||||||
|
__call = function(_, ...)
|
||||||
|
return Range.new(...)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
---@cast Range +fun(start: vim.Pos, end_: vim.Pos): vim.Range
|
||||||
|
---@cast Range +fun(start_row: integer, start_col: integer, end_row: integer, end_col: integer, opts?: vim.Pos.Optional): vim.Range
|
||||||
|
|
||||||
|
return Range
|
@@ -155,6 +155,8 @@ local config = {
|
|||||||
'lpeg.lua',
|
'lpeg.lua',
|
||||||
'mpack.lua',
|
'mpack.lua',
|
||||||
'net.lua',
|
'net.lua',
|
||||||
|
'pos.lua',
|
||||||
|
'range.lua',
|
||||||
're.lua',
|
're.lua',
|
||||||
'regex.lua',
|
'regex.lua',
|
||||||
'secure.lua',
|
'secure.lua',
|
||||||
@@ -193,6 +195,8 @@ local config = {
|
|||||||
'runtime/lua/vim/keymap.lua',
|
'runtime/lua/vim/keymap.lua',
|
||||||
'runtime/lua/vim/loader.lua',
|
'runtime/lua/vim/loader.lua',
|
||||||
'runtime/lua/vim/net.lua',
|
'runtime/lua/vim/net.lua',
|
||||||
|
'runtime/lua/vim/pos.lua',
|
||||||
|
'runtime/lua/vim/range.lua',
|
||||||
'runtime/lua/vim/secure.lua',
|
'runtime/lua/vim/secure.lua',
|
||||||
'runtime/lua/vim/shared.lua',
|
'runtime/lua/vim/shared.lua',
|
||||||
'runtime/lua/vim/snippet.lua',
|
'runtime/lua/vim/snippet.lua',
|
||||||
|
91
test/functional/lua/pos_spec.lua
Normal file
91
test/functional/lua/pos_spec.lua
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
-- Test suite for vim.pos
|
||||||
|
local t = require('test.testutil')
|
||||||
|
local n = require('test.functional.testnvim')()
|
||||||
|
local eq = t.eq
|
||||||
|
|
||||||
|
local clear = n.clear
|
||||||
|
local exec_lua = n.exec_lua
|
||||||
|
local insert = n.insert
|
||||||
|
|
||||||
|
describe('vim.pos', function()
|
||||||
|
before_each(clear)
|
||||||
|
after_each(clear)
|
||||||
|
|
||||||
|
it('creates a position with or without optional fields', function()
|
||||||
|
local pos = exec_lua(function()
|
||||||
|
return vim.pos(3, 5)
|
||||||
|
end)
|
||||||
|
eq(3, pos.row)
|
||||||
|
eq(5, pos.col)
|
||||||
|
eq(nil, pos.buf)
|
||||||
|
|
||||||
|
local buf = exec_lua(function()
|
||||||
|
return vim.api.nvim_create_buf(false, true)
|
||||||
|
end)
|
||||||
|
pos = exec_lua(function()
|
||||||
|
return vim.pos(3, 5, { buf = buf })
|
||||||
|
end)
|
||||||
|
eq(3, pos.row)
|
||||||
|
eq(5, pos.col)
|
||||||
|
eq(buf, pos.buf)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('supports comparisons by overloaded mathmatical operators', function()
|
||||||
|
eq(
|
||||||
|
true,
|
||||||
|
exec_lua(function()
|
||||||
|
return vim.pos(3, 5) < vim.pos(4, 5)
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
eq(
|
||||||
|
true,
|
||||||
|
exec_lua(function()
|
||||||
|
return vim.pos(3, 5) <= vim.pos(3, 6)
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
eq(
|
||||||
|
true,
|
||||||
|
exec_lua(function()
|
||||||
|
return vim.pos(3, 5) > vim.pos(2, 5)
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
eq(
|
||||||
|
true,
|
||||||
|
exec_lua(function()
|
||||||
|
return vim.pos(3, 5) >= vim.pos(3, 5)
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
eq(
|
||||||
|
true,
|
||||||
|
exec_lua(function()
|
||||||
|
return vim.pos(3, 5) == vim.pos(3, 5)
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
eq(
|
||||||
|
true,
|
||||||
|
exec_lua(function()
|
||||||
|
return vim.pos(3, 5) ~= vim.pos(3, 6)
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('supports conversion between vim.Pos and lsp.Position', function()
|
||||||
|
local buf = exec_lua(function()
|
||||||
|
return vim.api.nvim_get_current_buf()
|
||||||
|
end)
|
||||||
|
insert('Neovim 是 Vim 的分支,专注于扩展性和可用性。')
|
||||||
|
local lsp_pos = exec_lua(function()
|
||||||
|
local pos = vim.pos(0, 36, { buf = buf })
|
||||||
|
return pos:to_lsp('utf-16')
|
||||||
|
end)
|
||||||
|
eq({ line = 0, character = 20 }, lsp_pos)
|
||||||
|
local pos = exec_lua(function()
|
||||||
|
return vim.pos.lsp(buf, lsp_pos, 'utf-16')
|
||||||
|
end)
|
||||||
|
eq({
|
||||||
|
buf = buf,
|
||||||
|
row = 0,
|
||||||
|
col = 36,
|
||||||
|
}, pos)
|
||||||
|
end)
|
||||||
|
end)
|
86
test/functional/lua/range_spec.lua
Normal file
86
test/functional/lua/range_spec.lua
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
-- Test suite for vim.range
|
||||||
|
local t = require('test.testutil')
|
||||||
|
local n = require('test.functional.testnvim')()
|
||||||
|
local eq = t.eq
|
||||||
|
|
||||||
|
local clear = n.clear
|
||||||
|
local exec_lua = n.exec_lua
|
||||||
|
local insert = n.insert
|
||||||
|
|
||||||
|
describe('vim.range', function()
|
||||||
|
before_each(clear)
|
||||||
|
after_each(clear)
|
||||||
|
|
||||||
|
it('creates a range with or without optional fields', function()
|
||||||
|
local range = exec_lua(function()
|
||||||
|
return vim.range(3, 5, 4, 6)
|
||||||
|
end)
|
||||||
|
eq(3, range.start.row)
|
||||||
|
eq(5, range.start.col)
|
||||||
|
eq(4, range.end_.row)
|
||||||
|
eq(6, range.end_.col)
|
||||||
|
eq(nil, range.start.buf)
|
||||||
|
eq(nil, range.end_.buf)
|
||||||
|
local buf = exec_lua(function()
|
||||||
|
return vim.api.nvim_create_buf(false, true)
|
||||||
|
end)
|
||||||
|
range = exec_lua(function()
|
||||||
|
return vim.range(3, 5, 4, 6, { buf = buf })
|
||||||
|
end)
|
||||||
|
eq(buf, range.start.buf)
|
||||||
|
eq(buf, range.end_.buf)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('create a range from two positions when optional fields are not matched', function()
|
||||||
|
local range = exec_lua(function()
|
||||||
|
return vim.range(vim.pos(3, 5), vim.pos(4, 6))
|
||||||
|
end)
|
||||||
|
eq(3, range.start.row)
|
||||||
|
eq(5, range.start.col)
|
||||||
|
eq(4, range.end_.row)
|
||||||
|
eq(6, range.end_.col)
|
||||||
|
eq(nil, range.start.buf)
|
||||||
|
eq(nil, range.end_.buf)
|
||||||
|
|
||||||
|
local buf1 = exec_lua(function()
|
||||||
|
return vim.api.nvim_create_buf(false, true)
|
||||||
|
end)
|
||||||
|
range = exec_lua(function()
|
||||||
|
return vim.range(vim.pos(3, 5, { buf = buf1 }), vim.pos(4, 6, { buf = buf1 }))
|
||||||
|
end)
|
||||||
|
eq(buf1, range.start.buf)
|
||||||
|
eq(buf1, range.end_.buf)
|
||||||
|
|
||||||
|
local buf2 = exec_lua(function()
|
||||||
|
return vim.api.nvim_create_buf(false, true)
|
||||||
|
end)
|
||||||
|
local success = exec_lua(function()
|
||||||
|
return pcall(function()
|
||||||
|
return vim.range(vim.pos(3, 5, { buf = buf1 }), vim.pos(4, 6, { buf = buf2 }))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
eq(success, false)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('supports conversion between vim.Range and lsp.Range', function()
|
||||||
|
local buf = exec_lua(function()
|
||||||
|
return vim.api.nvim_get_current_buf()
|
||||||
|
end)
|
||||||
|
insert('Neovim 是 Vim 的分支,专注于扩展性和可用性。')
|
||||||
|
local lsp_range = exec_lua(function()
|
||||||
|
local range = vim.range(0, 10, 0, 36, { buf = buf })
|
||||||
|
return range:to_lsp('utf-16')
|
||||||
|
end)
|
||||||
|
eq({
|
||||||
|
['start'] = { line = 0, character = 8 },
|
||||||
|
['end'] = { line = 0, character = 20 },
|
||||||
|
}, lsp_range)
|
||||||
|
local range = exec_lua(function()
|
||||||
|
return vim.range.lsp(buf, lsp_range, 'utf-16')
|
||||||
|
end)
|
||||||
|
eq({
|
||||||
|
start = { row = 0, col = 10, buf = buf },
|
||||||
|
end_ = { row = 0, col = 36, buf = buf },
|
||||||
|
}, range)
|
||||||
|
end)
|
||||||
|
end)
|
Reference in New Issue
Block a user