diff --git a/doc/calltree_jump_changes.md b/doc/calltree_jump_changes.md new file mode 100644 index 0000000..238b2f9 --- /dev/null +++ b/doc/calltree_jump_changes.md @@ -0,0 +1,17 @@ +# calltree 自动跳转改动差异 + +## 上一版(edc1394) +- 通过 `safe_buf_add_highlight` 与 `safe_win_set_cursor` 防止越界,但只在调用树调用窗口中寻找现有窗口。 +- 当引用所在文件未在任何窗口打开时,不会主动载入或挂接该缓冲区,导致 `j/k` 循环无法找到可跳转窗口。 +- 未显式触发 `BufRead`/`BufEnter` 等自动命令,因此后台加载的缓冲区缺失主题与 LSP 附着。 + +## 当前版本(简化后) +- 用 `prepare_buf` 同时负责后台加载后的 `filetype` 恢复与 `BufRead` 自动命令,减少分散的辅助函数。 +- 通过 `ensure_window_has_buf` 与 `target_wins` 在需要时复用调用源窗口,同时触发 `BufWinEnter`/`BufEnter`/`WinEnter`,确保主题与 LSP 立即生效。 +- `target_buf_for_node` 统一处理“调用”与“被调用”两种方向的目标缓冲区选择逻辑,并复用 `jump_to_reference` 在高亮和循环跳转之间共享跳转实现。 +- 继续维护 `M.last_highlight_buf` 与 `M.last_jumped_reference`,但通过更少的状态字段即可在未打开文件之间顺畅地循环跳转。 + +## 关键区别 +1. **窗口处理**:当前版本仍会在需要时把目标缓冲区挂到原始窗口,并触发窗口相关自动命令,但实现浓缩在 `target_wins`/`ensure_window_has_buf` 中,更易追踪。 +2. **文件类型与主题**:`prepare_buf` 将 `BufRead`、`BufReadPost` 和 `filetype` 检测集中处理,不再分散到多个函数中,减少重复逻辑。 +3. **跳转状态**:跳转与高亮共用 `jump_to_reference`,既保持 `j/k` 连续性,又避免重复的窗口遍历代码。 diff --git a/lua/litee/calltree/autocmds.lua b/lua/litee/calltree/autocmds.lua index 3a55f2c..6fc3535 100644 --- a/lua/litee/calltree/autocmds.lua +++ b/lua/litee/calltree/autocmds.lua @@ -2,10 +2,254 @@ local lib_state = require("litee.lib.state") local lib_tree = require("litee.lib.tree") local lib_autohi = require('litee.lib.highlights.auto') local lib_hi = require('litee.lib.highlights') -local lib_path = require('litee.lib.util.path') +local lib_path = require('litee.lib.util.path') local M = {} +-- prepare_buf ensures that the buffer backing a calltree node is ready to be +-- displayed inside a normal window. When a file is first touched via +-- `bufadd()/bufload()` it will exist as an unloaded "hidden" buffer that never +-- triggered BufRead autocommands nor detected its filetype. Without the +-- correct filetype Neovim will skip syntax highlighting and LSP attachments, +-- which caused the "主题/LSP 不生效" symptoms reported by users. This helper +-- therefore: +-- 1. Checks the buffer validity before doing anything expensive. +-- 2. Tries to read the `filetype` option; failing to read options is a hint +-- that the buffer is going away, so we abort early. +-- 3. If the buffer has no filetype yet, we manually fire the BufRead/ +-- BufReadPost events and rerun filetype detection so that syntax groups +-- and language servers have a chance to attach. +-- The helper returns `true` only when the buffer is safe to keep using. +local function prepare_buf(buf, path) + if not vim.api.nvim_buf_is_valid(buf) then + return false + end + + local ok, ft = pcall(vim.api.nvim_buf_get_option, buf, "filetype") + if not ok then + return false + end + + if ft == "" then + if vim.api.nvim_exec_autocmds ~= nil then + pcall(vim.api.nvim_exec_autocmds, "BufRead", { buffer = buf, modeline = false }) + pcall(vim.api.nvim_exec_autocmds, "BufReadPost", { buffer = buf, modeline = false }) + end + + if vim.filetype ~= nil and vim.filetype.match ~= nil then + local match_ok, detected = pcall(vim.filetype.match, { buf = buf, filename = path }) + if match_ok and detected ~= nil and detected ~= "" then + pcall(vim.api.nvim_buf_set_option, buf, "filetype", detected) + end + end + end + + return true +end + +-- fire_win_autocmds simulates the standard window-entering sequence for the +-- target buffer. When we push an unseen buffer into the invoking window via +-- `nvim_win_set_buf`, Neovim will not automatically emit BufWinEnter/BufEnter/ +-- WinEnter for that window. Many statusline, colorscheme and LSP plugins hook +-- into those autocmds, so skipping them would leave the window in an +-- uninitialised state. Wrapping the autocmd calls in `pcall` prevents noisy +-- errors from user-defined handlers. +local function fire_win_autocmds(win, buf) + if vim.api.nvim_exec_autocmds == nil then + return + end + + vim.api.nvim_win_call(win, function() + pcall(vim.api.nvim_exec_autocmds, "BufWinEnter", { buffer = buf, modeline = false }) + pcall(vim.api.nvim_exec_autocmds, "BufEnter", { buffer = buf, modeline = false }) + pcall(vim.api.nvim_exec_autocmds, "WinEnter", { buffer = buf }) + end) +end + +-- ensure_window_has_buf guarantees that the provided window actually displays +-- the target buffer. If the window already shows the buffer we simply keep it +-- as-is; otherwise we swap the buffer in and immediately replay the window +-- autocmd sequence to mimic a genuine user-initiated switch. Returning `true` +-- lets callers know they can continue working with this window list. +local function ensure_window_has_buf(win, buf) + if not vim.api.nvim_win_is_valid(win) then + return false + end + if not vim.api.nvim_buf_is_valid(buf) then + return false + end + + if vim.api.nvim_win_get_buf(win) ~= buf then + vim.api.nvim_win_set_buf(win, buf) + fire_win_autocmds(win, buf) + end + return true +end + +-- ensure_node_buf converts an LSP calltree node into a valid buffer handle. It +-- accepts both incoming and outgoing nodes, extracts their `uri`, resolves the +-- on-disk path and then ensures the buffer is loaded via `bufadd`/`bufload`. +-- Afterwards we delegate to `prepare_buf` so the buffer behaves like an openly +-- edited file. Any failure (missing uri, load error, invalid buffer, etc.) +-- results in a `nil` return which upstream callers treat as "skip this node". +local function ensure_node_buf(node) + if node == nil or node.location == nil or node.location.uri == nil then + return nil + end + + local path = lib_path.strip_file_prefix(node.location.uri) + if path == nil or path == "" then + return nil + end + + local buf = vim.fn.bufadd(path) + if vim.fn.bufloaded(buf) == 0 then + local ok = pcall(vim.fn.bufload, buf) + if not ok then + return nil + end + end + + if not prepare_buf(buf, path) then + return nil + end + + if not vim.api.nvim_buf_is_valid(buf) then + return nil + end + + return buf +end + +-- normalize_lsp_line converts the 0-indexed line numbers returned by LSP into a +-- safe value for Neovim API calls. LSP servers can report stale or off-by-one +-- ranges when files change on disk; this helper clamps the line to the final +-- valid row (line_count - 1) so highlighting and cursor movement stay within +-- bounds. Returning `nil` indicates the input was hopelessly invalid (negative +-- or empty buffer). +local function normalize_lsp_line(buf, line) + if not vim.api.nvim_buf_is_valid(buf) then + return nil + end + + local line_count = vim.api.nvim_buf_line_count(buf) + if line_count == 0 then + return nil + end + + if line < 0 then + return nil + end + + if line < line_count then + return line + end + + if line == line_count then + return line_count - 1 + end + + return nil +end + +-- safe_buf_add_highlight wraps `nvim_buf_add_highlight` with the normalisation +-- guard above. When the line falls outside the buffer we skip adding the +-- highlight instead of throwing an exception, keeping calltree navigation +-- responsive even when references go stale. +local function safe_buf_add_highlight(buf, ns, hl_group, line, start_col, end_col) + local normalized = normalize_lsp_line(buf, line) + if normalized == nil then + return false + end + + vim.api.nvim_buf_add_highlight(buf, ns, hl_group, normalized, start_col, end_col) + return true +end + +-- safe_win_set_cursor is the cursor-moving counterpart to the highlight guard: +-- it validates the target window and buffer, runs the line number through +-- `normalize_lsp_line`, then attempts to move the cursor. Callers treat the +-- boolean return as a success flag when searching for a window that can accept +-- the jump. +local function safe_win_set_cursor(win, pos) + if not vim.api.nvim_win_is_valid(win) then + return false + end + + local buf = vim.api.nvim_win_get_buf(win) + if not vim.api.nvim_buf_is_valid(buf) then + return false + end + + local normalized = normalize_lsp_line(buf, pos[1] - 1) + if normalized == nil then + return false + end + + vim.api.nvim_win_set_cursor(win, { normalized + 1, pos[2] }) + return true +end + +-- target_buf_for_node selects the buffer we should highlight or jump within. +-- For incoming calltrees (`direction == "to"`) or the root node we always +-- highlight inside the original invoking buffer; outgoing calls use the +-- callee's file resolved via `ensure_node_buf`. Returning `nil` signals that we +-- could not prepare an actionable target for this node. +local function target_buf_for_node(ctx, node) + local calltree = ctx.state and ctx.state["calltree"] + if calltree == nil then + return nil + end + + if calltree.direction == "to" or node.depth == 0 then + return calltree.invoking_buf + end + return ensure_node_buf(node) +end + +-- target_wins determines which windows should receive the highlight/jump. +-- Priority is given to any existing windows already showing the buffer; when +-- none are found we fall back to the calltree invocation window and swap the +-- buffer in using `ensure_window_has_buf`. The function may return an empty +-- list, allowing callers to gracefully skip cursor moves when no suitable +-- window exists (for example in headless sessions). +local function target_wins(ctx, buf) + local calltree = ctx.state and ctx.state["calltree"] + if buf == nil or not vim.api.nvim_buf_is_valid(buf) or calltree == nil then + return {} + end + + local wins = vim.fn.win_findbuf(buf) + if #wins > 0 then + return wins + end + + local invoking_win = calltree.invoking_win + if invoking_win == nil then + return {} + end + + if ensure_window_has_buf(invoking_win, buf) then + wins = vim.fn.win_findbuf(buf) + end + + return wins +end + +-- jump_to_reference iterates all candidate windows and tries to place the +-- cursor at the reference's starting position. It returns `true` as soon as at +-- least one window succeeded, which downstream logic uses to decide whether the +-- jump state (`M.last_jumped_reference`) should advance to this reference. +local function jump_to_reference(wins, ref) + local moved = false + for _, win in ipairs(wins) do + if safe_win_set_cursor(win, { ref["start"].line + 1, 0 }) then + moved = true + end + end + return moved +end + -- ui_req_ctx creates a context table summarizing the -- environment when a calltree request is being -- made. @@ -77,91 +321,101 @@ end M.highlight_ns = vim.api.nvim_create_namespace("calltree-node-hls") M.last_jumped_reference = nil +M.last_highlight_buf = nil function M.jumpto_next_reference() + -- `M.last_jumped_reference` stores the node and index we successfully + -- jumped to when highlighting the tree entry. Reusing that state allows the + -- user to press `j/k` (or other mapped keys) and cycle through the remaining + -- references without recomputing expensive context. If the stored state no + -- longer matches the current node we silently abort to avoid confusing + -- cursor jumps. if M.last_jumped_reference == nil then return end + local ctx = ui_req_ctx() if ctx.node == nil then return end - if - ctx.node.key ~= M.last_jumped_reference.node_key - then + + if ctx.node.key ~= M.last_jumped_reference.node_key then return end - local wins = {} - if ctx.state["calltree"].direction == "to" then - for _, win in ipairs(vim.api.nvim_list_wins()) do - if vim.api.nvim_win_get_buf(win) == ctx.state["calltree"].invoking_buf then - table.insert(wins, win) - end - end - else - local node_path = lib_path.strip_file_prefix(M.last_jumped_reference.node.location.uri) - for _, win in ipairs(vim.api.nvim_list_wins()) do - local buf = vim.api.nvim_win_get_buf(win) - local name = vim.api.nvim_buf_get_name(buf) - if node_path == name then - table.insert(wins, win) - end - end + + if ctx.node.references == nil or #ctx.node.references == 0 then + return end - local i = M.last_jumped_reference.ref_idx - i = i + 1 - if i > #ctx.node.references then - i = 1 + local target_buf = target_buf_for_node(ctx, M.last_jumped_reference.node) + if target_buf == nil then + return end - local ref = ctx.node.references[i] - for _, win in ipairs(wins) do - vim.api.nvim_win_set_cursor(win, {ref["start"].line+1, 0}) + + local wins = target_wins(ctx, target_buf) + if #wins == 0 then + return + end + + local ref_idx = M.last_jumped_reference.ref_idx + for _ = 1, #ctx.node.references do + ref_idx = ref_idx + 1 + if ref_idx > #ctx.node.references then + ref_idx = 1 + end + local ref = ctx.node.references[ref_idx] + if jump_to_reference(wins, ref) then + M.last_jumped_reference = { + node_key = ctx.node.key, + ref_idx = ref_idx, + node = M.last_jumped_reference.node, + } + return + end end - M.last_jumped_reference = { - node_key = ctx.node.key, - ref_idx = i, - node = M.last_jumped_reference.node - } end function M.highlight(set) + -- Highlight is invoked whenever the calltree selection changes. It + -- prepares the target buffer and windows using the helpers above and then + -- either clears highlights (`set == false`) or paints/jumps to the first + -- valid reference. The logic mirrors Neovim's native LSP handlers so that + -- calltree navigation feels identical to built-in references requests. local ctx = ui_req_ctx() - if ctx.node == nil then + if ctx.node == nil or ctx.state["calltree"] == nil then return end - if ctx.state["calltree"].invoking_buf == nil or - not vim.api.nvim_buf_is_valid(ctx.state["calltree"].invoking_buf) then + local target_buf = target_buf_for_node(ctx, ctx.node) + if target_buf == nil or not vim.api.nvim_buf_is_valid(target_buf) then + M.last_jumped_reference = nil return end - local wins = {} - for _, win in ipairs(vim.api.nvim_list_wins()) do - if vim.api.nvim_win_get_buf(win) == ctx.state["calltree"].invoking_buf then - table.insert(wins, win) - end + if M.last_highlight_buf ~= nil and + vim.api.nvim_buf_is_valid(M.last_highlight_buf) and + M.last_highlight_buf ~= target_buf then + vim.api.nvim_buf_clear_namespace(M.last_highlight_buf, M.highlight_ns, 0, -1) end - vim.api.nvim_buf_clear_namespace( - ctx.state["calltree"].invoking_buf, - M.highlight_ns, - 0, - -1 - ) + vim.api.nvim_buf_clear_namespace(target_buf, M.highlight_ns, 0, -1) + if not set then + M.last_highlight_buf = target_buf return end - -- highlight root node + local wins = target_wins(ctx, target_buf) + if ctx.node.depth == 0 then local location = ctx.node.location - if location == nil then + if location == nil or location.range == nil then + M.last_highlight_buf = target_buf return end local range = location.range - vim.api.nvim_buf_add_highlight( - ctx.state["calltree"].invoking_buf, + safe_buf_add_highlight( + target_buf, M.highlight_ns, lib_hi.hls.SymbolJumpHL, range["start"].line, @@ -169,69 +423,50 @@ function M.highlight(set) range["end"].character ) for _, win in ipairs(wins) do - vim.api.nvim_win_set_cursor(win, {range["start"].line+1, 0}) + safe_win_set_cursor(win, { range["start"].line + 1, 0 }) end + M.last_highlight_buf = target_buf return end - -- highlight references - if ctx.state["calltree"].direction == "to" then - if ctx.node.references ~= nil then - for i, ref in ipairs(ctx.node.references) do - vim.api.nvim_buf_add_highlight( - ctx.state["calltree"].invoking_buf, - M.highlight_ns, - lib_hi.hls.SymbolJumpHL, - ref["start"].line, - ref["start"].character, - ref["end"].character - ) - if i == 1 then - for _, win in ipairs(wins) do - vim.api.nvim_win_set_cursor(win, {ref["start"].line+1, 0}) - end - M.last_jumped_reference = { - node_key = ctx.node.key, - ref_idx = 1, - node = ctx.node - } - end + if ctx.node.references ~= nil then + local first_valid = nil + for i, ref in ipairs(ctx.node.references) do + local highlighted = safe_buf_add_highlight( + target_buf, + M.highlight_ns, + lib_hi.hls.SymbolJumpHL, + ref["start"].line, + ref["start"].character, + ref["end"].character + ) + local jumped = false + if first_valid == nil then + jumped = jump_to_reference(wins, ref) end - end - else - local wins = {} - -- do a buffer search for node's location - local node_path = lib_path.strip_file_prefix(ctx.node.location.uri) - for _, win in ipairs(vim.api.nvim_list_wins()) do - local buf = vim.api.nvim_win_get_buf(win) - local name = vim.api.nvim_buf_get_name(buf) - if node_path == name then - table.insert(wins, win) + if first_valid == nil and (highlighted or jumped) then + first_valid = i end end - if #wins > 0 then - for i, ref in ipairs(ctx.node.references) do - vim.api.nvim_buf_add_highlight( - ctx.state["calltree"].invoking_buf, - M.highlight_ns, - lib_hi.hls.SymbolJumpHL, - ref["start"].line, - ref["start"].character, - ref["end"].character - ) - if i == 1 then - for _, win in ipairs(wins) do - vim.api.nvim_win_set_cursor(win, {ref["start"].line+1, 0}) - end - M.last_jumped_reference = { - node_key = ctx.node.key, - ref_idx = 1, - node = ctx.node - } - end - end + + if first_valid ~= nil then + M.last_jumped_reference = { + node_key = ctx.node.key, + ref_idx = first_valid, + node = ctx.node, + } + else + M.last_jumped_reference = nil end + else + M.last_jumped_reference = nil end + + if ctx.node.references == nil or #ctx.node.references == 0 then + M.last_jumped_reference = nil + end + + M.last_highlight_buf = target_buf end return M diff --git a/lua/litee/calltree/handlers.lua b/lua/litee/calltree/handlers.lua index adf0d96..8523bc1 100644 --- a/lua/litee/calltree/handlers.lua +++ b/lua/litee/calltree/handlers.lua @@ -35,6 +35,22 @@ local update_autocmd_id = nil -- -- this handler serves as the single entry point for creating -- a calltree. +local function resolve_offset_encoding(ctx) + if ctx == nil then + return "utf-16" + end + if ctx.offset_encoding ~= nil then + return ctx.offset_encoding + end + if ctx.client_id ~= nil and vim.lsp.get_client_by_id ~= nil then + local client = vim.lsp.get_client_by_id(ctx.client_id) + if client ~= nil and client.offset_encoding ~= nil then + return client.offset_encoding + end + end + return "utf-16" +end + M.ch_lsp_handler = function(direction) return function(err, result, ctx, _) if err ~= nil then @@ -83,6 +99,8 @@ M.ch_lsp_handler = function(direction) -- create the root of our call tree, the request which -- signaled this response is in ctx.params + local offset_encoding = resolve_offset_encoding(ctx) + local root = lib_tree_node.new_node(ctx.params.item.name, keyify(ctx.params.item), 0) root.call_hierarchy_item = ctx.params.item root.location = { @@ -90,6 +108,7 @@ M.ch_lsp_handler = function(direction) range = root.call_hierarchy_item.range } root.references = ctx.params.item.fromRanges + root.offset_encoding = offset_encoding -- create the root's children nodes via the response array. local children = {} @@ -104,6 +123,7 @@ M.ch_lsp_handler = function(direction) range = child.call_hierarchy_item.range } child.references = call_hierarchy_call["fromRanges"] + child.offset_encoding = offset_encoding table.insert(children, child) end @@ -205,7 +225,7 @@ end -- ui_state : table - a ui_state table which provides the ui state -- of the current tab. defined in ui.lua function M.calltree_expand_handler(node, linenr, direction, state) - return function(err, result, _, _) + return function(err, result, ctx, _) if err ~= nil then vim.api.nvim_err_writeln(vim.inspect(err)) return @@ -222,6 +242,8 @@ function M.calltree_expand_handler(node, linenr, direction, state) return end + local offset_encoding = resolve_offset_encoding(ctx) + local children = {} for _, call_hierarchy_call in pairs(result) do local child = lib_tree_node.new_node( @@ -234,6 +256,7 @@ function M.calltree_expand_handler(node, linenr, direction, state) range = child.call_hierarchy_item.range } child.references = call_hierarchy_call["fromRanges"] + child.offset_encoding = offset_encoding table.insert(children, child) end @@ -274,6 +297,7 @@ function M.calltree_switch_handler(direction, state) if err ~= nil or result == nil then return end + local offset_encoding = resolve_offset_encoding(ctx) -- create the root of our call tree, the request which -- signaled this response is in ctx.params local root = lib_tree_node.new_node(ctx.params.item.name, keyify(ctx.params.item), 0) @@ -282,6 +306,7 @@ function M.calltree_switch_handler(direction, state) uri = root.call_hierarchy_item.uri, range = root.call_hierarchy_item.range } + root.offset_encoding = offset_encoding -- try to resolve the workspace symbol for root root.symbol = lib_lsp.symbol_from_node(state["calltree"].active_lsp_clients, root, state["calltree"].buf) @@ -299,6 +324,7 @@ function M.calltree_switch_handler(direction, state) range = child.call_hierarchy_item.range } child.references = call_hierarchy_call["fromRanges"] + child.offset_encoding = offset_encoding table.insert(children, child) end @@ -328,19 +354,110 @@ end local ns_id = vim.api.nvim_create_namespace("calltree-extmarks") +local function clamp_line(buf, line) + if line == nil then + return 0 + end + local line_count = 0 + if buf ~= nil then + local ok, count = pcall(vim.api.nvim_buf_line_count, buf) + if ok and type(count) == "number" then + line_count = count + end + end + if line_count <= 0 then + return math.max(line, 0) + end + if line < 0 then + return 0 + end + if line >= line_count then + return line_count - 1 + end + return line +end + +local function clamp_col(buf, line, col) + if col == nil then + return 0 + end + if col < 0 then + return 0 + end + if buf ~= nil and line ~= nil then + local ok, lines = pcall(vim.api.nvim_buf_get_lines, buf, line, line + 1, true) + if ok and type(lines) == "table" and lines[1] ~= nil then + local max_col = #lines[1] + if col > max_col then + return max_col + end + end + end + return col +end + +local function get_line_byte_from_position(buf, position, offset_encoding) + if position == nil then + return 0 + end + local resolved_encoding = offset_encoding or "utf-16" + if resolved_encoding == "utf-8" or position._litee_converted then + return position.character + end + local ok, col = pcall(vim.lsp.util._get_line_byte_from_position, buf, position, resolved_encoding) + if ok and col ~= nil then + return col + end + return position.character +end + +local function ensure_range_utf8(buf, range, offset_encoding) + if range == nil then + return 0, 0, 0, 0 + end + local start_line = clamp_line(buf, range["start"].line) + local end_line = clamp_line(buf, range["end"].line) + if range._litee_converted then + range["start"].line = start_line + range["end"].line = end_line + range["start"].character = clamp_col(buf, start_line, range["start"].character) + range["end"].character = clamp_col(buf, end_line, range["end"].character) + return start_line, range["start"].character, end_line, range["end"].character + end + local resolved_encoding = offset_encoding or "utf-16" + if resolved_encoding ~= "utf-8" then + range["start"].line = start_line + range["end"].line = end_line + local start_col = get_line_byte_from_position(buf, range["start"], resolved_encoding) + local end_col = get_line_byte_from_position(buf, range["end"], resolved_encoding) + range["start"].character = clamp_col(buf, start_line, start_col) + range["end"].character = clamp_col(buf, end_line, end_col) + else + range["start"].line = start_line + range["end"].line = end_line + range["start"].character = clamp_col(buf, start_line, range["start"].character) + range["end"].character = clamp_col(buf, end_line, range["end"].character) + end + range._litee_converted = true + range["start"]._litee_converted = true + range["end"]._litee_converted = true + return start_line, range["start"].character, end_line, range["end"].character +end + local function _update_calltree_extmarks(node, buf) if node.extmark == nil then -- extmark is nil, and buffer is open, create a extmark + local start_line, start_col, end_line, end_col = ensure_range_utf8(buf, node.location.range, node.offset_encoding) node.extmark = { buf = buf, id = vim.api.nvim_buf_set_extmark( buf, ns_id, - node.location.range["start"].line, - node.location.range["start"].character, + start_line, + start_col, { - end_row = node.location.range["end"].line, - end_col = node.location.range["end"].character, + end_row = end_line, + end_col = end_col, } ) } @@ -361,6 +478,9 @@ local function _update_calltree_extmarks(node, buf) node.location.range["start"].character = extmark_linenr[2] node.location.range["end"].line = extmark_linenr[1] + relative_line_count node.location.range["end"].character = extmark_linenr[2] + relative_char_count + node.location.range._litee_converted = true + node.location.range["start"]._litee_converted = true + node.location.range["end"]._litee_converted = true end end if node.ref_extmarks == nil and node.references ~= nil then @@ -368,16 +488,17 @@ local function _update_calltree_extmarks(node, buf) local ref_extmarks = {} for _, reference in ipairs(node.references) do -- extmark is nil, and buffer is open, create a extmark + local ref_start_line, ref_start_col, ref_end_line, ref_end_col = ensure_range_utf8(buf, reference, node.offset_encoding) local extmark = { buf = buf, id = vim.api.nvim_buf_set_extmark( buf, ns_id, - reference["start"].line, - reference["start"].character, + ref_start_line, + ref_start_col, { - end_row = reference["end"].line, - end_col = reference["end"].character, + end_row = ref_end_line, + end_col = ref_end_col, } ) } @@ -398,12 +519,15 @@ local function _update_calltree_extmarks(node, buf) local relative_line_count = reference["end"].line - reference["start"].line local relative_char_count = reference["end"].character - - reference["start"].line + reference["start"].character reference["start"].line = extmark_linenr[1] reference["start"].character = extmark_linenr[2] reference["end"].line = extmark_linenr[1] + relative_line_count reference["end"].character = extmark_linenr[2] + relative_char_count + reference._litee_converted = true + reference["start"]._litee_converted = true + reference["end"]._litee_converted = true end end end