aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/range.lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim/range.lua')
-rw-r--r--runtime/lua/vim/range.lua141
1 files changed, 141 insertions, 0 deletions
diff --git a/runtime/lua/vim/range.lua b/runtime/lua/vim/range.lua
new file mode 100644
index 0000000000..8302c74337
--- /dev/null
+++ b/runtime/lua/vim/range.lua
@@ -0,0 +1,141 @@
+---@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
+
+-- 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