Merge #39822 from ofseed/pos-mark

feat(pos,range): pos:to_mark(), pos.mark(), range:to_mark(), range.mark()
This commit is contained in:
Justin M. Keyes
2026-05-18 10:45:21 -04:00
committed by GitHub
9 changed files with 181 additions and 43 deletions

View File

@@ -1856,32 +1856,11 @@ function M.make_given_range_params(start_pos, end_pos, bufnr, position_encoding)
validate('position_encoding', position_encoding, 'string')
bufnr = vim._resolve_bufnr(bufnr)
--- @type [integer, integer]
local A = { unpack(start_pos or api.nvim_buf_get_mark(bufnr, '<')) }
--- @type [integer, integer]
local B = { unpack(end_pos or api.nvim_buf_get_mark(bufnr, '>')) }
-- convert to 0-index
A[1] = A[1] - 1
B[1] = B[1] - 1
-- account for position_encoding.
if A[2] > 0 then
A[2] = M.character_offset(bufnr, A[1], A[2], position_encoding)
end
if B[2] > 0 then
B[2] = M.character_offset(bufnr, B[1], B[2], position_encoding)
end
-- we need to offset the end character position otherwise we loose the last
-- character of the selection, as LSP end position is exclusive
-- see https://microsoft.github.io/language-server-protocol/specification#range
if vim.o.selection ~= 'exclusive' then
B[2] = B[2] + 1
end
local start_row, start_col = unpack(start_pos or api.nvim_buf_get_mark(bufnr, '<'))
local end_row, end_col = unpack(end_pos or api.nvim_buf_get_mark(bufnr, '>'))
return {
textDocument = M.make_text_document_params(bufnr),
range = {
start = { line = A[1], character = A[2] },
['end'] = { line = B[1], character = B[2] },
},
range = vim.range.mark(bufnr, start_row, start_col, end_row, end_col):to_lsp(position_encoding),
}
end
@@ -1933,12 +1912,14 @@ end
--- Returns the UTF-32 and UTF-16 offsets for a position in a certain buffer.
---
---@deprecated
---@param buf integer buffer number (0 for current)
---@param row integer 0-indexed line
---@param col integer 0-indexed byte offset in line
---@param position_encoding 'utf-8'|'utf-16'|'utf-32'
---@return integer `position_encoding` index of the character in line {row} column {col} in buffer {buf}
function M.character_offset(buf, row, col, position_encoding)
vim.deprecate('vim.lsp.util.character_offset', 'vim.str_utfindex', '0.14')
vim.validate('position_encoding', position_encoding, 'string')
local line = get_line(buf, row)

View File

@@ -291,6 +291,25 @@ function M.cursor(buf, pos)
return M.new(buf, pos[1] - 1, pos[2])
end
--- Converts |vim.Pos| to mark position (see |api-indexing|).
---@param pos vim.Pos
---@return integer, integer
function M.to_mark(pos)
return pos[1] + 1, pos[2]
end
--- Creates a new |vim.Pos| from mark position (see |api-indexing|).
---@param buf integer
---@param row integer
---@param col integer
function M.mark(buf, row, col)
if buf == 0 then
buf = api.nvim_get_current_buf()
end
return M.new(buf, row - 1, col)
end
--- Converts |vim.Pos| to extmark position (see |api-indexing|).
---@param pos vim.Pos
---@return integer, integer

View File

@@ -150,11 +150,28 @@ end
---@param buf integer
---@return integer, integer
local function to_inclusive_pos(buf, row, col)
local line = get_line(buf, row)
if col > 0 then
col = col - 1
col = col + vim.str_utf_start(line, col) - 1
elseif col == 0 and row > 0 then
row = row - 1
col = #get_line(buf, row)
col = #line > 0 and #line + vim.str_utf_start(line, #line) - 1 or 0
end
return row, col
end
---@param row integer
---@param col integer
---@param buf integer
---@return integer, integer
local function to_exclusive_pos(buf, row, col)
local line = get_line(buf, row)
if col >= #line then
row = row + 1
col = 0
else
col = col + vim.str_utf_end(line, col + 1) + 1
end
return row, col
@@ -164,7 +181,7 @@ end
---@param r1 vim.Range
---@param r2 vim.Range
function M.__lt(r1, r2)
if r1:is_empty() then
if r1:is_empty() or r2:is_empty() then
return cmp_pos(r1[3], r1[4], r2[1], r2[2]) ~= 1
end
@@ -176,7 +193,7 @@ end
---@param r1 vim.Range
---@param r2 vim.Range
function M.__le(r1, r2)
if r1:is_empty() then
if r1:is_empty() or r2:is_empty() then
return cmp_pos(r1[3], r1[4], r2[1], r2[2]) ~= 1
end
@@ -314,6 +331,67 @@ function M.lsp(buf, range, position_encoding)
return M.new(start, end_)
end
--- Converts |vim.Range| to extmark range (see |api-indexing|).
---
--- Example:
--- ```lua
--- local range = vim.range(0, 3, 5, 4, 0)
---
--- -- Convert to mark range, you can call it in a method style.
--- local start_row, start_col, end_row, end_col = range:to_mark()
--- ```
---@param range vim.Range
---@return integer, integer, integer, integer
function M.to_mark(range)
validate('range', range, 'table')
local buf = range.buf
local start_row, start_col, end_row, end_col = range[1], range[2], range[3], range[4]
if vim.o.selection ~= 'exclusive' then
end_row, end_col = to_inclusive_pos(buf, end_row, end_col)
end
start_row, start_col = vim.pos(buf, start_row, start_col):to_mark()
end_row, end_col = vim.pos(buf, end_row, end_col):to_mark()
return start_row, start_col, end_row, end_col
end
--- Creates a new |vim.Range| from "mark-indexed" range (see |api-indexing|).
---
--- Example:
--- ```lua
--- -- A range represented by marks may be end-inclusive (decided by 'selection' option).
--- local start_row, start_col = unpack(api.nvim_buf_get_mark(bufnr, '<'))
--- local end_row, end_col = unpack(api.nvim_buf_get_mark(bufnr, '>'))
---
--- -- Create an end-exclusive range.
--- local range = vim.range.mark(0, start_row, start_col, end_row, end_col)
--- ```
---@param buf integer
---@param start_row integer
---@param start_col integer
---@param end_row integer
---@param end_col integer
function M.mark(buf, start_row, start_col, end_row, end_col)
validate('buf', buf, 'number')
validate('start_row', start_row, 'number')
validate('start_col', start_col, 'number')
validate('end_row', end_row, 'number')
validate('end_col', end_col, 'number')
if buf == 0 then
buf = api.nvim_get_current_buf()
end
local start = vim.pos.mark(buf, start_row, start_col)
local end_ = vim.pos.mark(buf, end_row, end_col)
if vim.o.selection ~= 'exclusive' then
end_[1], end_[2] = to_exclusive_pos(buf, end_[1], end_[2])
end
return M.new(start, end_)
end
--- Converts |vim.Range| to extmark range (see |api-indexing|).
---
--- Example: