From 1c687c76b0abadcbed1dbf3669cba7aad8f23b7e Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Fri, 15 May 2026 19:16:10 +0800 Subject: [PATCH] refactor(pos): move `get_lines` from `lsp.util` to `pos` --- runtime/lua/vim/lsp/util.lua | 93 +------------------------------- runtime/lua/vim/pos.lua | 102 +++++++++++++++++++++++++++++++++-- 2 files changed, 99 insertions(+), 96 deletions(-) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 077e1831b8..5d3538db6b 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -133,98 +133,9 @@ local function sort_by_key(fn) end end ---- Gets the zero-indexed lines from the given buffer. ---- Works on unloaded buffers by reading the file using libuv to bypass buf reading events. ---- Falls back to loading the buffer and nvim_buf_get_lines for buffers with non-file URI. ---- ----@param bufnr integer bufnr to get the lines from ----@param rows integer[] zero-indexed line numbers ----@return table # a table mapping rows to lines -local function get_lines(bufnr, rows) - --- @type integer[] - rows = type(rows) == 'table' and rows or { rows } +local get_lines = vim.pos._get_lines - -- This is needed for bufload and bufloaded - bufnr = vim._resolve_bufnr(bufnr) - - local function buf_lines() - local lines = {} --- @type table - for _, row in ipairs(rows) do - lines[row] = (api.nvim_buf_get_lines(bufnr, row, row + 1, false) or { '' })[1] - end - return lines - end - - -- use loaded buffers if available - if vim.fn.bufloaded(bufnr) == 1 then - return buf_lines() - end - - local uri = vim.uri_from_bufnr(bufnr) - - -- load the buffer if this is not a file uri - -- Custom language server protocol extensions can result in servers sending URIs with custom schemes. Plugins are able to load these via `BufReadCmd` autocmds. - if uri:sub(1, 4) ~= 'file' then - vim.fn.bufload(bufnr) - return buf_lines() - end - - local filename = api.nvim_buf_get_name(bufnr) - if vim.fn.isdirectory(filename) ~= 0 then - return {} - end - - -- get the data from the file - local fd = uv.fs_open(filename, 'r', 438) - if not fd then - return {} - end - local stat = assert(uv.fs_fstat(fd)) - local data = assert(uv.fs_read(fd, stat.size, 0)) - uv.fs_close(fd) - - local lines = {} --- @type table rows we need to retrieve - local need = 0 -- keep track of how many unique rows we need - for _, row in pairs(rows) do - if not lines[row] then - need = need + 1 - end - lines[row] = true - end - - local found = 0 - local lnum = 0 - - for line in string.gmatch(data, '([^\n]*)\n?') do - if lines[lnum] == true then - lines[lnum] = line - found = found + 1 - if found == need then - break - end - end - lnum = lnum + 1 - end - - -- change any lines we didn't find to the empty string - for i, line in pairs(lines) do - if line == true then - lines[i] = '' - end - end - return lines --[[@as table]] -end - ---- Gets the zero-indexed line from the given buffer. ---- Works on unloaded buffers by reading the file using libuv to bypass buf reading events. ---- Falls back to loading the buffer and nvim_buf_get_lines for buffers with non-file URI. ---- ----@param bufnr integer ----@param row integer zero-indexed line number ----@return string the line at row in filename -local function get_line(bufnr, row) - return get_lines(bufnr, { row })[row] -end +local get_line = vim.pos._get_line --- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position ---@param position lsp.Position diff --git a/runtime/lua/vim/pos.lua b/runtime/lua/vim/pos.lua index 14e3f5138c..e5d0d4c84e 100644 --- a/runtime/lua/vim/pos.lua +++ b/runtime/lua/vim/pos.lua @@ -7,6 +7,7 @@ --- objects. local api = vim.api +local uv = vim.uv local validate = vim.validate --- Represents a well-defined position. @@ -116,11 +117,97 @@ function M.__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] +--- Gets the zero-indexed lines from the given buffer. +--- Works on unloaded buffers by reading the file using libuv to bypass buf reading events. +--- Falls back to loading the buffer and nvim_buf_get_lines for buffers with non-file URI. +--- +---@param bufnr integer bufnr to get the lines from +---@param rows integer[] zero-indexed line numbers +---@return table # a table mapping rows to lines +local function get_lines(bufnr, rows) + --- @type integer[] + rows = type(rows) == 'table' and rows or { rows } + + -- This is needed for bufload and bufloaded + bufnr = vim._resolve_bufnr(bufnr) + + local function buf_lines() + local lines = {} --- @type table + for _, row in ipairs(rows) do + lines[row] = (api.nvim_buf_get_lines(bufnr, row, row + 1, false) or { '' })[1] + end + return lines + end + + -- use loaded buffers if available + if vim.fn.bufloaded(bufnr) == 1 then + return buf_lines() + end + + local uri = vim.uri_from_bufnr(bufnr) + + -- load the buffer if this is not a file uri + -- Custom language server protocol extensions can result in servers sending URIs with custom schemes. Plugins are able to load these via `BufReadCmd` autocmds. + if uri:sub(1, 4) ~= 'file' then + vim.fn.bufload(bufnr) + return buf_lines() + end + + local filename = api.nvim_buf_get_name(bufnr) + if vim.fn.isdirectory(filename) ~= 0 then + return {} + end + + -- get the data from the file + local fd = uv.fs_open(filename, 'r', 438) + if not fd then + return {} + end + local stat = assert(uv.fs_fstat(fd)) + local data = assert(uv.fs_read(fd, stat.size, 0)) + uv.fs_close(fd) + + local lines = {} --- @type table rows we need to retrieve + local need = 0 -- keep track of how many unique rows we need + for _, row in pairs(rows) do + if not lines[row] then + need = need + 1 + end + lines[row] = true + end + + local found = 0 + local lnum = 0 + + for line in string.gmatch(data, '([^\n]*)\n?') do + if lines[lnum] == true then + lines[lnum] = line + found = found + 1 + if found == need then + break + end + end + lnum = lnum + 1 + end + + -- change any lines we didn't find to the empty string + for i, line in pairs(lines) do + if line == true then + lines[i] = '' + end + end + return lines --[[@as table]] +end + +--- Gets the zero-indexed line from the given buffer. +--- Works on unloaded buffers by reading the file using libuv to bypass buf reading events. +--- Falls back to loading the buffer and nvim_buf_get_lines for buffers with non-file URI. +--- +---@param bufnr integer +---@param row integer zero-indexed line number +---@return string the line at row in filename +local function get_line(bufnr, row) + return get_lines(bufnr, { row })[row] end --- Converts |vim.Pos| to `lsp.Position`. @@ -269,6 +356,11 @@ function M.offset(buf, offset) return M.new(buf, row, col) end +-- TODO(ofseed): remove these exported functions by replacing their usages with `vim.pos`. +M._get_lines = get_lines + +M._get_line = get_line + -- Overload `Range.new` to allow calling this module as a function. setmetatable(M, { __call = function(_, ...)