From 6e8a728e3dad747d0c46dc47a530b76e8997bc08 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Sat, 25 May 2024 20:35:37 +0200 Subject: refactor: fix luals type warnings --- runtime/lua/vim/lsp/util.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 5a229a1169..0099e82f52 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -616,7 +616,7 @@ function M.rename(old_fname, new_fname, opts) buf_rename[b] = { from = old_bname, to = new_bname } end - local newdir = assert(vim.fs.dirname(new_fname)) + local newdir = vim.fs.dirname(new_fname) vim.fn.mkdir(newdir, 'p') local ok, err = os.rename(old_fname_full, new_fname) @@ -625,7 +625,7 @@ function M.rename(old_fname, new_fname, opts) local old_undofile = vim.fn.undofile(old_fname_full) if uv.fs_stat(old_undofile) ~= nil then local new_undofile = vim.fn.undofile(new_fname) - vim.fn.mkdir(assert(vim.fs.dirname(new_undofile)), 'p') + vim.fn.mkdir(vim.fs.dirname(new_undofile), 'p') os.rename(old_undofile, new_undofile) end -- cgit From 5c33815448e11b514678f39cecc74e68131d4628 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Thu, 30 May 2024 10:46:26 +0200 Subject: refactor(lsp): replace util.buf_versions with changedtick (#28943) `lsp.util.buf_versions` was already derived from changedtick (`on_lines` from `buf_attach` synced the version) As far as I can tell there is no need to keep track of the state in a separate table. --- runtime/lua/vim/lsp/util.lua | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 0099e82f52..d1f0e97065 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -509,8 +509,7 @@ function M.apply_text_document_edit(text_document_edit, index, offset_encoding) and ( text_document.version and text_document.version > 0 - and M.buf_versions[bufnr] - and M.buf_versions[bufnr] > text_document.version + and vim.b[bufnr].changedtick > text_document.version ) then print('Buffer ', text_document.uri, ' newer than edits.') @@ -2200,9 +2199,16 @@ function M._refresh(method, opts) end end -M._get_line_byte_from_position = get_line_byte_from_position - ---@nodoc -M.buf_versions = {} ---@type table +---@deprecated +---@type table +M.buf_versions = setmetatable({}, { + __index = function(_, bufnr) + vim.deprecate('vim.lsp.util.buf_versions', 'vim.b.changedtick', '0.13') + return vim.b[bufnr].changedtick + end, +}) + +M._get_line_byte_from_position = get_line_byte_from_position return M -- cgit From 6566a59b3a6c8dabfa40f8debd0de96d875825e9 Mon Sep 17 00:00:00 2001 From: Ilia Choly Date: Fri, 31 May 2024 08:41:10 -0400 Subject: refactor(lsp): use predefined types in util function signatures (#29095) --- runtime/lua/vim/lsp/util.lua | 60 +++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 29 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index d1f0e97065..b33f6ccf69 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -173,11 +173,11 @@ local _str_byteindex_enc = M._str_byteindex_enc --- CAUTION: Changes in-place! --- ---@deprecated ----@param lines (table) Original list of strings ----@param A (table) Start position; a 2-tuple of {line,col} numbers ----@param B (table) End position; a 2-tuple of {line,col} numbers ----@param new_lines (table) list of strings to replace the original ----@return table The modified {lines} object +---@param lines string[] Original list of strings +---@param A [integer, integer] Start position; a 2-tuple of {line,col} numbers +---@param B [integer, integer] End position; a 2-tuple {line,col} numbers +---@param new_lines string[] list of strings to replace the original +---@return string[] The modified {lines} object function M.set_lines(lines, A, B, new_lines) vim.deprecate('vim.lsp.util.set_lines()', 'nil', '0.12') -- 0-indexing to 1-indexing @@ -343,7 +343,7 @@ local function get_line_byte_from_position(bufnr, position, offset_encoding) end --- Applies a list of text edits to a buffer. ----@param text_edits table list of `TextEdit` objects +---@param text_edits lsp.TextEdit[] ---@param bufnr integer Buffer id ---@param offset_encoding string utf-8|utf-16|utf-32 ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit @@ -481,8 +481,8 @@ end --- Applies a `TextDocumentEdit`, which is a list of changes to a single --- document. --- ----@param text_document_edit table: a `TextDocumentEdit` object ----@param index integer: Optional index of the edit, if from a list of edits (or nil, if not from a list) +---@param text_document_edit lsp.TextDocumentEdit +---@param index? integer: Optional index of the edit, if from a list of edits (or nil, if not from a list) ---@param offset_encoding? string ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit function M.apply_text_document_edit(text_document_edit, index, offset_encoding) @@ -533,6 +533,7 @@ local function path_under_prefix(path, prefix) end --- Get list of buffers whose filename matches the given path prefix (normalized full path) +---@param prefix string ---@return integer[] local function get_bufs_with_prefix(prefix) prefix = path_components(prefix) @@ -677,7 +678,7 @@ end --- Applies a `WorkspaceEdit`. --- ----@param workspace_edit table `WorkspaceEdit` +---@param workspace_edit lsp.WorkspaceEdit ---@param offset_encoding string utf-8|utf-16|utf-32 (required) ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit function M.apply_workspace_edit(workspace_edit, offset_encoding) @@ -723,8 +724,8 @@ end --- Note that if the input is of type `MarkupContent` and its kind is `plaintext`, --- then the corresponding value is returned without further modifications. --- ----@param input (lsp.MarkedString | lsp.MarkedString[] | lsp.MarkupContent) ----@param contents (table|nil) List of strings to extend with converted lines. Defaults to {}. +---@param input lsp.MarkedString|lsp.MarkedString[]|lsp.MarkupContent +---@param contents string[]|nil List of strings to extend with converted lines. Defaults to {}. ---@return string[] extended with lines of converted markdown. ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover function M.convert_input_to_markdown_lines(input, contents) @@ -759,11 +760,11 @@ end --- Converts `textDocument/signatureHelp` response to markdown lines. --- ----@param signature_help table Response of `textDocument/SignatureHelp` +---@param signature_help lsp.SignatureHelp Response of `textDocument/SignatureHelp` ---@param ft string|nil filetype that will be use as the `lang` for the label markdown code block ---@param triggers table|nil list of trigger characters from the lsp server. used to better determine parameter offsets ----@return table|nil table list of lines of converted markdown. ----@return table|nil table of active hl +---@return string[]|nil table list of lines of converted markdown. +---@return number[]|nil table of active hl ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers) if not signature_help.signatures then @@ -960,7 +961,7 @@ end --- Shows document and optionally jumps to the location. --- ----@param location table (`Location`|`LocationLink`) +---@param location lsp.Location|lsp.LocationLink ---@param offset_encoding string|nil utf-8|utf-16|utf-32 ---@param opts table|nil options --- - reuse_win (boolean) Jump to existing window if buffer is already open. @@ -1017,7 +1018,7 @@ end --- Jumps to a location. --- ----@param location table (`Location`|`LocationLink`) +---@param location lsp.Location|lsp.LocationLink ---@param offset_encoding string|nil utf-8|utf-16|utf-32 ---@param reuse_win boolean|nil Jump to existing window if buffer is already open. ---@return boolean `true` if the jump succeeded @@ -1038,7 +1039,7 @@ end --- - for Location, range is shown (e.g., function definition) --- - for LocationLink, targetRange is shown (e.g., body of function definition) --- ----@param location table a single `Location` or `LocationLink` +---@param location lsp.Location|lsp.LocationLink ---@param opts table ---@return integer|nil buffer id of float window ---@return integer|nil window id of float window @@ -1154,7 +1155,7 @@ end --- If you want to open a popup with fancy markdown, use `open_floating_preview` instead --- ---@param bufnr integer ----@param contents table of lines to show in window +---@param contents string[] of lines to show in window ---@param opts table with optional fields --- - height of floating window --- - width of floating window @@ -1669,7 +1670,7 @@ do --[[ References ]] --- Shows a list of document highlights for a certain buffer. --- ---@param bufnr integer Buffer id - ---@param references table List of `DocumentHighlight` objects to highlight + ---@param references lsp.DocumentHighlight[] objects to highlight ---@param offset_encoding string One of "utf-8", "utf-16", "utf-32". ---@see https://microsoft.github.io/language-server-protocol/specification/#textDocumentContentChangeEvent function M.buf_highlight_references(bufnr, references, offset_encoding) @@ -1873,7 +1874,7 @@ end --- CAUTION: Modifies the input in-place! --- ---@deprecated ----@param lines table list of lines +---@param lines string[] list of lines ---@return string filetype or "markdown" if it was unchanged. function M.try_trim_markdown_code_blocks(lines) vim.deprecate('vim.lsp.util.try_trim_markdown_code_blocks()', 'nil', '0.12') @@ -1898,7 +1899,7 @@ function M.try_trim_markdown_code_blocks(lines) end ---@param window integer|nil: window handle or 0 for current, defaults to current ----@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window` +---@param offset_encoding? string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window` local function make_position_param(window, offset_encoding) window = window or 0 local buf = api.nvim_win_get_buf(window) @@ -1919,7 +1920,7 @@ end --- ---@param window integer|nil: window handle or 0 for current, defaults to current ---@param offset_encoding string|nil utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window` ----@return table `TextDocumentPositionParams` object +---@return lsp.TextDocumentPositionParams ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams function M.make_position_params(window, offset_encoding) window = window or 0 @@ -1932,7 +1933,7 @@ function M.make_position_params(window, offset_encoding) end --- Utility function for getting the encoding of the first LSP client on the given buffer. ----@param bufnr (integer) buffer handle or 0 for current, defaults to current +---@param bufnr integer buffer handle or 0 for current, defaults to current ---@return string encoding first client if there is one, nil otherwise function M._get_offset_encoding(bufnr) validate({ @@ -2033,15 +2034,16 @@ end --- Creates a `TextDocumentIdentifier` object for the current buffer. --- ---@param bufnr integer|nil: Buffer handle, defaults to current ----@return table `TextDocumentIdentifier` +---@return lsp.TextDocumentIdentifier ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier function M.make_text_document_params(bufnr) return { uri = vim.uri_from_bufnr(bufnr or 0) } end --- Create the workspace params ----@param added table ----@param removed table +---@param added lsp.WorkspaceFolder[] +---@param removed lsp.WorkspaceFolder[] +---@return lsp.WorkspaceFoldersChangeEvent function M.make_workspace_params(added, removed) return { event = { added = added, removed = removed } } end @@ -2049,8 +2051,8 @@ end --- Returns indentation size. --- ---@see 'shiftwidth' ----@param bufnr (integer|nil): Buffer handle, defaults to current ----@return (integer) indentation size +---@param bufnr integer|nil: Buffer handle, defaults to current +---@return integer indentation size function M.get_effective_tabstop(bufnr) validate({ bufnr = { bufnr, 'n', true } }) local bo = bufnr and vim.bo[bufnr] or vim.bo @@ -2060,7 +2062,7 @@ end --- Creates a `DocumentFormattingParams` object for the current buffer and cursor position. --- ----@param options table|nil with valid `FormattingOptions` entries +---@param options lsp.FormattingOptions|nil with valid `FormattingOptions` entries ---@return lsp.DocumentFormattingParams object ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting function M.make_formatting_params(options) -- cgit From 2e6d295f799c27372e5c0c44727fa613c81717fd Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Tue, 4 Jun 2024 17:21:37 +0200 Subject: fix(lsp): account for changedtick version gap on modified reset (#29170) Follow up to https://github.com/neovim/neovim/pull/28943 Fixes https://github.com/neovim/neovim/issues/29163 --- runtime/lua/vim/lsp/util.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index b33f6ccf69..3215b40d1d 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -502,6 +502,11 @@ function M.apply_text_document_edit(text_document_edit, index, offset_encoding) should_check_version = false end + -- changedtick increases on save but server only receives version updates + -- on line changes (via didChange) + -- This allows a gap of 1 to account for the servers outdated view + local version_offset = vim.b[bufnr].modified and 0 or 1 + -- `VersionedTextDocumentIdentifier`s version may be null -- https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier if @@ -509,7 +514,7 @@ function M.apply_text_document_edit(text_document_edit, index, offset_encoding) and ( text_document.version and text_document.version > 0 - and vim.b[bufnr].changedtick > text_document.version + and vim.b[bufnr].changedtick > (text_document.version + version_offset) ) then print('Buffer ', text_document.uri, ' newer than edits.') -- cgit From 6e45cd7f0026ee33b8c397b810dcfe5b4678bbd8 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Fri, 7 Jun 2024 11:36:46 +0200 Subject: fix(lsp): revert buf_versions deprecation/replacement (#29217) * Revert "fix(lsp): account for changedtick version gap on modified reset (#29170)" This reverts commit 2e6d295f799c27372e5c0c44727fa613c81717fd. * Revert "refactor(lsp): replace util.buf_versions with changedtick (#28943)" This reverts commit 5c33815448e11b514678f39cecc74e68131d4628. --- runtime/lua/vim/lsp/util.lua | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 3215b40d1d..fc027cfcc0 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -502,11 +502,6 @@ function M.apply_text_document_edit(text_document_edit, index, offset_encoding) should_check_version = false end - -- changedtick increases on save but server only receives version updates - -- on line changes (via didChange) - -- This allows a gap of 1 to account for the servers outdated view - local version_offset = vim.b[bufnr].modified and 0 or 1 - -- `VersionedTextDocumentIdentifier`s version may be null -- https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier if @@ -514,7 +509,8 @@ function M.apply_text_document_edit(text_document_edit, index, offset_encoding) and ( text_document.version and text_document.version > 0 - and vim.b[bufnr].changedtick > (text_document.version + version_offset) + and M.buf_versions[bufnr] + and M.buf_versions[bufnr] > text_document.version ) then print('Buffer ', text_document.uri, ' newer than edits.') @@ -2206,16 +2202,9 @@ function M._refresh(method, opts) end end ----@nodoc ----@deprecated ----@type table -M.buf_versions = setmetatable({}, { - __index = function(_, bufnr) - vim.deprecate('vim.lsp.util.buf_versions', 'vim.b.changedtick', '0.13') - return vim.b[bufnr].changedtick - end, -}) - M._get_line_byte_from_position = get_line_byte_from_position +---@nodoc +M.buf_versions = {} ---@type table + return M -- cgit From 2ce4a4d91e4abee0aab8b98c47eea9fbd4849ba6 Mon Sep 17 00:00:00 2001 From: Al Colmenar <57642956+alcolmenar@users.noreply.github.com> Date: Fri, 7 Jun 2024 02:54:43 -0700 Subject: fix(lsp): fix reverse sorting of same position text edits (#29212) Problem: Text edits with the same position (both line and character) were being reverse sorted prior to being applied which differs from the lsp spec Solution: Change the sort order for just the same position edits --- runtime/lua/vim/lsp/util.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index fc027cfcc0..3d61af8b15 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -391,7 +391,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) return a.range.start.character > b.range.start.character end if a._index ~= b._index then - return a._index > b._index + return a._index < b._index end end) -- cgit From 20f22f75ee629ae2db4cd99e730fa0af26553177 Mon Sep 17 00:00:00 2001 From: Tom Praschan <13141438+tom-anders@users.noreply.github.com> Date: Mon, 10 Jun 2024 03:14:55 +0200 Subject: feat(lsp): include end_col, end_lnum in vim.lsp.buf.locations_to_items #29164 --- runtime/lua/vim/lsp/util.lua | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 3d61af8b15..088d57b6d6 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1722,7 +1722,9 @@ end) ---@inlinedoc ---@field filename string ---@field lnum integer 1-indexed line number +---@field end_lnum integer 1-indexed end line number ---@field col integer 1-indexed column +---@field end_col integer 1-indexed end column ---@field text string ---@field user_data lsp.Location|lsp.LocationLink @@ -1749,7 +1751,7 @@ function M.locations_to_items(locations, offset_encoding) end local items = {} - ---@type table + ---@type table local grouped = setmetatable({}, { __index = function(t, k) local v = {} @@ -1761,7 +1763,7 @@ function M.locations_to_items(locations, offset_encoding) -- locations may be Location or LocationLink local uri = d.uri or d.targetUri local range = d.range or d.targetSelectionRange - table.insert(grouped[uri], { start = range.start, location = d }) + table.insert(grouped[uri], { start = range.start, ['end'] = range['end'], location = d }) end ---@type string[] @@ -1776,6 +1778,9 @@ function M.locations_to_items(locations, offset_encoding) local line_numbers = {} for _, temp in ipairs(rows) do table.insert(line_numbers, temp.start.line) + if temp.start.line ~= temp['end'].line then + table.insert(line_numbers, temp['end'].line) + end end -- get all the lines for this uri @@ -1783,13 +1788,18 @@ function M.locations_to_items(locations, offset_encoding) for _, temp in ipairs(rows) do local pos = temp.start + local end_pos = temp['end'] local row = pos.line + local end_row = end_pos.line local line = lines[row] or '' local col = M._str_byteindex_enc(line, pos.character, offset_encoding) + local end_col = M._str_byteindex_enc(lines[end_row] or '', end_pos.character, offset_encoding) table.insert(items, { filename = filename, lnum = row + 1, + end_lnum = end_row + 1, col = col + 1, + end_col = end_col + 1, text = line, user_data = temp.location, }) -- cgit From 5e49ef0af3cb8dba658e5d0dc6a807f8edebf590 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 11 Jun 2024 12:05:18 +0100 Subject: refactor(lua): improve type annotations --- runtime/lua/vim/lsp/util.lua | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 088d57b6d6..ae6de591b3 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -238,6 +238,7 @@ end ---@param rows integer[] zero-indexed line numbers ---@return table|string 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 @@ -246,7 +247,7 @@ local function get_lines(bufnr, rows) end local function buf_lines() - local 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 @@ -274,11 +275,11 @@ local function get_lines(bufnr, rows) if not fd then return '' end - local stat = uv.fs_fstat(fd) - local data = uv.fs_read(fd, stat.size, 0) + local stat = assert(uv.fs_fstat(fd)) + local data = assert(uv.fs_read(fd, stat.size, 0)) uv.fs_close(fd) - local lines = {} -- rows we need to retrieve + 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 @@ -307,7 +308,7 @@ local function get_lines(bufnr, rows) lines[i] = '' end end - return lines + return lines --[[@as table]] end --- Gets the zero-indexed line from the given buffer. @@ -322,7 +323,8 @@ local function get_line(bufnr, row) end --- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position ----@param offset_encoding string|nil utf-8|utf-16|utf-32 +---@param position lsp.Position +---@param offset_encoding? string utf-8|utf-16|utf-32 ---@return integer local function get_line_byte_from_position(bufnr, position, offset_encoding) -- LSP's line and characters are 0-indexed @@ -366,6 +368,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) -- Fix reversed range and indexing each text_edits local index = 0 + --- @param text_edit lsp.TextEdit text_edits = vim.tbl_map(function(text_edit) index = index + 1 text_edit._index = index @@ -383,6 +386,9 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) end, text_edits) -- Sort text_edits + ---@param a lsp.TextEdit | { _index: integer } + ---@param b lsp.TextEdit | { _index: integer } + ---@return boolean table.sort(text_edits, function(a, b) if a.range.start.line ~= b.range.start.line then return a.range.start.line > b.range.start.line @@ -393,10 +399,11 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) if a._index ~= b._index then return a._index < b._index end + return false end) -- save and restore local marks since they get deleted by nvim_buf_set_lines - local marks = {} + local marks = {} --- @type table for _, m in pairs(vim.fn.getmarklist(bufnr)) do if m.mark:match("^'[a-z]$") then marks[m.mark:sub(2, 2)] = { m.pos[2], m.pos[3] - 1 } -- api-indexed -- cgit From 0a9c81d70964f905112857900fbaa6aae590a96d Mon Sep 17 00:00:00 2001 From: Ilia Choly Date: Fri, 14 Jun 2024 05:03:58 -0400 Subject: refactor(lsp): use metatable for buf_versions (#29304) This reduces the number of nil checks around buf_versions usage Test changes were lifted from 5c33815 Co-authored-by: Mathias Fussenegger --- runtime/lua/vim/lsp/util.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index ae6de591b3..a5cf13ed0f 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -516,7 +516,6 @@ function M.apply_text_document_edit(text_document_edit, index, offset_encoding) and ( text_document.version and text_document.version > 0 - and M.buf_versions[bufnr] and M.buf_versions[bufnr] > text_document.version ) then @@ -2222,6 +2221,11 @@ end M._get_line_byte_from_position = get_line_byte_from_position ---@nodoc -M.buf_versions = {} ---@type table +---@type table +M.buf_versions = setmetatable({}, { + __index = function(t, bufnr) + return rawget(t, bufnr) or 0 + end, +}) return M -- cgit From aa6b9c677d83d76d448c3bb0973bf8d14bfdf922 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Sat, 8 Jun 2024 21:40:18 +0200 Subject: refactor: use `vim._with` where possible This mostly means replacing `nvim_buf_call` and `nvim_win_call` with `vim._with`. --- runtime/lua/vim/lsp/util.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index a5cf13ed0f..b0fd25af3a 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -640,7 +640,7 @@ function M.rename(old_fname, new_fname, opts) -- Rename with :saveas. This does two things: -- * Unset BF_WRITE_MASK, so that users don't get E13 when they do :write. -- * Send didClose and didOpen via textDocument/didSave handler. - api.nvim_buf_call(b, function() + vim._with({ buf = b }, function() vim.cmd('keepalt saveas! ' .. vim.fn.fnameescape(rename.to)) end) -- Delete the new buffer with the old name created by :saveas. nvim_buf_delete and @@ -1014,7 +1014,7 @@ function M.show_document(location, offset_encoding, opts) local row = range.start.line local col = get_line_byte_from_position(bufnr, range.start, offset_encoding) api.nvim_win_set_cursor(win, { row + 1, col }) - api.nvim_win_call(win, function() + vim._with({ win = win }, function() -- Open folds under the cursor vim.cmd('normal! zv') end) @@ -1334,7 +1334,7 @@ function M.stylize_markdown(bufnr, contents, opts) end -- needs to run in the buffer for the regions to work - api.nvim_buf_call(bufnr, function() + vim._with({ buf = bufnr }, function() -- we need to apply lsp_markdown regions speperately, since otherwise -- markdown regions can "bleed" through the other syntax regions -- and mess up the formatting -- cgit From bdff50dee56ebf6de58d58315920abf2f8e262f7 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Sat, 27 Jul 2024 22:30:14 +0200 Subject: fix(lsp): revert text edit application order change (#29877) Reverts https://github.com/neovim/neovim/pull/29212 and adds a few additional test cases From the spec > All text edits ranges refer to positions in the document they are > computed on. They therefore move a document from state S1 to S2 without > describing any intermediate state. Text edits ranges must never overlap, > that means no part of the original document must be manipulated by more > than one edit. However, it is possible that multiple edits have the same > start position: multiple inserts, or any number of inserts followed by a > single remove or replace edit. If multiple inserts have the same > position, the order in the array defines the order in which the inserted > strings appear in the resulting text. The previous fix seems wrong. The important part: > If multiple inserts have the same position, the order in the array > defines the order in which the inserted strings appear in the > resulting text. Emphasis on _appear in the resulting text_ Which means that in: local edits1 = { make_edit(0, 3, 0, 3, { 'World' }), make_edit(0, 3, 0, 3, { 'Hello' }), } `World` must appear before `Hello` in the final text. That means the old logic was correct, and the fix was wrong. --- runtime/lua/vim/lsp/util.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index b0fd25af3a..59ae3beae4 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -396,10 +396,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) if a.range.start.character ~= b.range.start.character then return a.range.start.character > b.range.start.character end - if a._index ~= b._index then - return a._index < b._index - end - return false + return a._index > b._index end) -- save and restore local marks since they get deleted by nvim_buf_set_lines -- cgit From 9b5ab66678f8efa1f5fe89205839fe053fe5efaf Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 11 Aug 2024 11:58:15 +0100 Subject: test(lsp): refactor and tidy - Merge all the top level 'LSP' describe blocks - Refactor text edit tests - Fix typing errors - Add linebreaks between tests --- runtime/lua/vim/lsp/util.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 59ae3beae4..cbbe3a66b7 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1442,7 +1442,7 @@ end --- Computes size of float needed to show contents (with optional wrapping) --- ---@param contents table of lines to show in window ----@param opts table with optional fields +---@param opts? table with optional fields --- - height of floating window --- - width of floating window --- - wrap_at character to wrap at for computing height @@ -1821,7 +1821,7 @@ end --- Converts symbols to quickfix list items. --- ---@param symbols table DocumentSymbol[] or SymbolInformation[] ----@param bufnr integer +---@param bufnr? integer function M.symbols_to_items(symbols, bufnr) local function _symbols_to_items(_symbols, _items, _bufnr) for _, symbol in ipairs(_symbols) do -- cgit From 882a450a2982caa05fcbbe90e94246a8c2b45b8b Mon Sep 17 00:00:00 2001 From: Tristan Knight Date: Thu, 5 Sep 2024 08:23:11 +0100 Subject: fix(lsp): handle locations exceeding line length #30253 Problem: LSP spec [states](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position) that "if the character value is greater than the line length it defaults back to the line length", but `locations_to_items` fails in that case. Solution: Adjust locations_to_items to follow the spec. closes #28281 --- runtime/lua/vim/lsp/util.lua | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index cbbe3a66b7..ec66449b54 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1795,8 +1795,18 @@ function M.locations_to_items(locations, offset_encoding) local row = pos.line local end_row = end_pos.line local line = lines[row] or '' - local col = M._str_byteindex_enc(line, pos.character, offset_encoding) - local end_col = M._str_byteindex_enc(lines[end_row] or '', end_pos.character, offset_encoding) + local line_len = vim.fn.strcharlen(line) + local end_line = lines[end_row] or '' + local end_line_len = vim.fn.strcharlen(end_line) + -- LSP spec: if character > line length, default to the line length. + -- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position + local col = pos.character <= line_len + and M._str_byteindex_enc(line, pos.character, offset_encoding) + or line_len + local end_col = end_pos.character <= end_line_len + and M._str_byteindex_enc(end_line, end_pos.character, offset_encoding) + or end_line_len + table.insert(items, { filename = filename, lnum = row + 1, -- cgit From 003b8a251dc1184e36c222f675bf79a50a40ab3a Mon Sep 17 00:00:00 2001 From: Tristan Knight Date: Sun, 8 Sep 2024 11:44:46 +0100 Subject: fix(lsp): handle out-of-bounds character positions #30288 Problem: str_byteindex_enc could return an error if the index was longer than the lline length. This was handled in each of the calls to it individually Solution: * Fix the call at the source level so that if the index is higher than the line length, line length is returned as per LSP specification * Remove pcalls on str_byteindex_enc calls. No longer needed now that str_byteindex_enc has a bounds check. --- runtime/lua/vim/lsp/util.lua | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index ec66449b54..5046459645 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -147,6 +147,12 @@ end ---@param encoding string utf-8|utf-16|utf-32| defaults to utf-16 ---@return integer byte (utf-8) index of `encoding` index `index` in `line` function M._str_byteindex_enc(line, index, encoding) + local len = vim.fn.strlen(line) + if index > len then + -- LSP spec: if character > line length, default to the line length. + -- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position + return len + end if not encoding then encoding = 'utf-16' end @@ -166,7 +172,6 @@ function M._str_byteindex_enc(line, index, encoding) end local _str_utfindex_enc = M._str_utfindex_enc -local _str_byteindex_enc = M._str_byteindex_enc --- Replaces text in a range with new text. --- @@ -334,12 +339,7 @@ local function get_line_byte_from_position(bufnr, position, offset_encoding) -- character if col > 0 then local line = get_line(bufnr, position.line) or '' - local ok, result - ok, result = pcall(_str_byteindex_enc, line, col, offset_encoding) - if ok then - return result - end - return math.min(#line, col) + return M._str_byteindex_enc(line, col, offset_encoding or 'utf-16') end return col end @@ -436,14 +436,15 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) e.end_col = last_line_len has_eol_text_edit = true else - -- If the replacement is over the end of a line (i.e. e.end_col is out of bounds and the + -- If the replacement is over the end of a line (i.e. e.end_col is equal to the line length and the -- replacement text ends with a newline We can likely assume that the replacement is assumed -- to be meant to replace the newline with another newline and we need to make sure this -- doesn't add an extra empty line. E.g. when the last line to be replaced contains a '\r' -- in the file some servers (clangd on windows) will include that character in the line -- while nvim_buf_set_text doesn't count it as part of the line. if - e.end_col > last_line_len + e.end_col >= last_line_len + and text_edit.range['end'].character > e.end_col and #text_edit.newText > 0 and string.sub(text_edit.newText, -1) == '\n' then @@ -1795,17 +1796,9 @@ function M.locations_to_items(locations, offset_encoding) local row = pos.line local end_row = end_pos.line local line = lines[row] or '' - local line_len = vim.fn.strcharlen(line) local end_line = lines[end_row] or '' - local end_line_len = vim.fn.strcharlen(end_line) - -- LSP spec: if character > line length, default to the line length. - -- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position - local col = pos.character <= line_len - and M._str_byteindex_enc(line, pos.character, offset_encoding) - or line_len - local end_col = end_pos.character <= end_line_len - and M._str_byteindex_enc(end_line, end_pos.character, offset_encoding) - or end_line_len + local col = M._str_byteindex_enc(line, pos.character, offset_encoding) + local end_col = M._str_byteindex_enc(end_line, end_pos.character, offset_encoding) table.insert(items, { filename = filename, -- cgit From f279d1ae33ee5650bccf7b52f9f29fd192ccea1d Mon Sep 17 00:00:00 2001 From: tris203 Date: Mon, 9 Sep 2024 22:39:02 +0100 Subject: fix(lsp): handle encoding bounds in str_utfindex_enc Problem: str_utfindex_enc could return an error if the index was longer than the line length. This was handled in each of the calls to it individually Solution: * Fix the call at the source level so that if the index is higher than the line length, utf length is returned --- runtime/lua/vim/lsp/util.lua | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 5046459645..09b97ac861 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -119,6 +119,7 @@ end ---@param encoding string|nil utf-8|utf-16|utf-32|nil defaults to utf-16 ---@return integer `encoding` index of `index` in `line` function M._str_utfindex_enc(line, index, encoding) + local len32, len16 = vim.str_utfindex(line) if not encoding then encoding = 'utf-16' end @@ -129,9 +130,15 @@ function M._str_utfindex_enc(line, index, encoding) return #line end elseif encoding == 'utf-16' then + if not index or index > len16 then + return len16 + end local _, col16 = vim.str_utfindex(line, index) return col16 elseif encoding == 'utf-32' then + if not index or index > len32 then + return len32 + end local col32, _ = vim.str_utfindex(line, index) return col32 else @@ -171,8 +178,6 @@ function M._str_byteindex_enc(line, index, encoding) end end -local _str_utfindex_enc = M._str_utfindex_enc - --- Replaces text in a range with new text. --- --- CAUTION: Changes in-place! @@ -1928,7 +1933,7 @@ local function make_position_param(window, offset_encoding) return { line = 0, character = 0 } end - col = _str_utfindex_enc(line, col, offset_encoding) + col = M._str_utfindex_enc(line, col, offset_encoding) return { line = row, character = col } end @@ -2110,11 +2115,7 @@ function M.character_offset(buf, row, col, offset_encoding) ) offset_encoding = vim.lsp.get_clients({ bufnr = buf })[1].offset_encoding end - -- If the col is past the EOL, use the line length. - if col > #line then - return _str_utfindex_enc(line, nil, offset_encoding) - end - return _str_utfindex_enc(line, col, offset_encoding) + return M._str_utfindex_enc(line, col, offset_encoding) end --- Helper function to return nested values in language server settings -- cgit From 8512f669f0e095df99e0456db11a0e0b5a2b0485 Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Fri, 13 Sep 2024 19:57:04 +0200 Subject: fix(lsp): handle nil bytes in strings Problem: The LSP omnifunc can insert nil bytes, which when read in other places (like semantic token) could cause an error: semantic_tokens.lua:304: Vim:E976: Using a Blob as a String Solution: Use `#line` instead of `vim.fn.strlen(line)`. Both return UTF-8 bytes but the latter can't handle nil bytes. Completion candidates can currently insert nil bytes, if other parts of Alternative fix to https://github.com/neovim/neovim/pull/30359 Note that https://github.com/neovim/neovim/pull/30315 will avoid the insertion of nil bytes from the LSP omnifunc, but the change of this PR can more easily be backported. --- runtime/lua/vim/lsp/util.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 09b97ac861..65f84e8f87 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -154,7 +154,7 @@ end ---@param encoding string utf-8|utf-16|utf-32| defaults to utf-16 ---@return integer byte (utf-8) index of `encoding` index `index` in `line` function M._str_byteindex_enc(line, index, encoding) - local len = vim.fn.strlen(line) + local len = #line if index > len then -- LSP spec: if character > line length, default to the line length. -- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position @@ -167,7 +167,7 @@ function M._str_byteindex_enc(line, index, encoding) if index then return index else - return #line + return len end elseif encoding == 'utf-16' then return vim.str_byteindex(line, index, true) -- cgit From 511b991e66892b4bb8176ce64c0e8fefb300f638 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sat, 21 Sep 2024 16:23:00 +0100 Subject: feat(fs.lua): add vim.fs.rm() Analogous to the shell `rm` command. --- runtime/lua/vim/lsp/util.lua | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 65f84e8f87..9ee7dc8df7 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -652,6 +652,7 @@ function M.rename(old_fname, new_fname, opts) end end +--- @param change lsp.CreateFile local function create_file(change) local opts = change.options or {} -- from spec: Overwrite wins over `ignoreIfExists` @@ -666,23 +667,15 @@ local function create_file(change) vim.fn.bufadd(fname) end +--- @param change lsp.DeleteFile local function delete_file(change) local opts = change.options or {} local fname = vim.uri_to_fname(change.uri) - local stat = uv.fs_stat(fname) - if opts.ignoreIfNotExists and not stat then - return - end - assert(stat, 'Cannot delete not existing file or folder ' .. fname) - local flags - if stat and stat.type == 'directory' then - flags = opts.recursive and 'rf' or 'd' - else - flags = '' - end local bufnr = vim.fn.bufadd(fname) - local result = tonumber(vim.fn.delete(fname, flags)) - assert(result == 0, 'Could not delete file: ' .. fname .. ', stat: ' .. vim.inspect(stat)) + vim.fs.rm(fname, { + force = opts.ignoreIfNotExists, + recursive = opts.recursive, + }) api.nvim_buf_delete(bufnr, { force = true }) end -- cgit From 27f3750817b188c9ababe94eade22c30d8819585 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 6 Oct 2024 12:20:40 -0700 Subject: feat(lsp): improve LSP doc hover rendering #30695 Problem: - Some servers like LuaLS add unwanted blank lines after multiline `@param` description. - List items do not wrap nicely. Solution: - When rendering the LSP doc hover, remove blank lines in each `@param` or `@return`. - But ensure exactly one empty line before each. - Set 'breakindent'. --- runtime/lua/vim/lsp/util.lua | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 9ee7dc8df7..a08825b735 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -99,9 +99,26 @@ local function get_border_size(opts) return { height = height, width = width } end -local function split_lines(value) - value = string.gsub(value, '\r\n?', '\n') - return split(value, '\n', { plain = true, trimempty = true }) +--- Splits string at newlines, optionally removing unwanted blank lines. +--- +--- @param s string Multiline string +--- @param no_blank boolean? Drop blank lines for each @param/@return (except one empty line +--- separating each). Workaround for https://github.com/LuaLS/lua-language-server/issues/2333 +local function split_lines(s, no_blank) + s = string.gsub(s, '\r\n?', '\n') + local lines = {} + local in_desc = true -- Main description block, before seeing any @foo. + for line in vim.gsplit(s, '\n', { plain = true, trimempty = true }) do + local start_annotation = not not line:find('^ ?%@.?[pr]') + in_desc = (not start_annotation) and in_desc or false + if start_annotation and no_blank and not (lines[#lines] or ''):find('^%s*$') then + table.insert(lines, '') -- Separate each @foo with a blank line. + end + if in_desc or not no_blank or not line:find('^%s*$') then + table.insert(lines, line) + end + end + return lines end local function create_window_without_focus() @@ -116,7 +133,7 @@ end --- Convenience wrapper around vim.str_utfindex ---@param line string line to be indexed ---@param index integer|nil byte index (utf-8), or `nil` for length ----@param encoding string|nil utf-8|utf-16|utf-32|nil defaults to utf-16 +---@param encoding 'utf-8'|'utf-16'|'utf-32'|nil defaults to utf-16 ---@return integer `encoding` index of `index` in `line` function M._str_utfindex_enc(line, index, encoding) local len32, len16 = vim.str_utfindex(line) @@ -735,13 +752,13 @@ function M.convert_input_to_markdown_lines(input, contents) contents = contents or {} -- MarkedString variation 1 if type(input) == 'string' then - list_extend(contents, split_lines(input)) + list_extend(contents, split_lines(input, true)) else assert(type(input) == 'table', 'Expected a table for LSP input') -- MarkupContent if input.kind then local value = input.value or '' - list_extend(contents, split_lines(value)) + list_extend(contents, split_lines(value, true)) -- MarkupString variation 2 elseif input.language then table.insert(contents, '```' .. input.language) @@ -1633,10 +1650,9 @@ function M.open_floating_preview(contents, syntax, opts) if do_stylize then vim.wo[floating_winnr].conceallevel = 2 end - -- disable folding - vim.wo[floating_winnr].foldenable = false - -- soft wrapping - vim.wo[floating_winnr].wrap = opts.wrap + vim.wo[floating_winnr].foldenable = false -- Disable folding. + vim.wo[floating_winnr].wrap = opts.wrap -- Soft wrapping. + vim.wo[floating_winnr].breakindent = true -- Slightly better list presentation. vim.bo[floating_bufnr].modifiable = false vim.bo[floating_bufnr].bufhidden = 'wipe' -- cgit From 50f006b61774311e67e6948cd863bd503e4bcdfb Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 7 Oct 2024 08:25:13 -0700 Subject: fix(lsp): tagfunc fails in unusual buffer #30700 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: tagfunc failed in a weird buffer (either a directory or some other non-file buffer, I don't remember): E987: Invalid return value from tagfunc E5108: Error executing lua …/runtime/lua/vim/lsp/util.lua:311: EISDIR: illegal operation on a directory stack traceback: at this line: local data = assert(uv.fs_read(fd, stat.size, 0)) Solution: Check for directory. --- runtime/lua/vim/lsp/util.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'runtime/lua/vim/lsp/util.lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index a08825b735..882ec22ca6 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -296,6 +296,9 @@ local function get_lines(bufnr, rows) 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) -- cgit