From 10ae1c2427b13b01bfb5e133a0550b40de3dd646 Mon Sep 17 00:00:00 2001 From: Starslayerx Date: Fri, 10 Oct 2025 16:22:41 +0800 Subject: [PATCH 1/4] Migrate from deprecated nvim-treesitter.ts_utils to built-in APIs - Remove dependency on nvim-treesitter.ts_utils module - Replace ts_utils.get_node_at_cursor() with vim.treesitter.get_node() - Replace ts_utils.get_vim_range() with vim.treesitter.get_node_range() - Replace ts_utils.get_node_text() with vim.treesitter.get_node_text() - Add proper coordinate conversion from 0-based to 1-based indexing - Ensure compatibility with modern nvim-treesitter versions --- lua/wildfire/init.lua | 7 ++++--- lua/wildfire/utils.lua | 24 ++++++++++++++++-------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/lua/wildfire/init.lua b/lua/wildfire/init.lua index ec0628f..a377126 100644 --- a/lua/wildfire/init.lua +++ b/lua/wildfire/init.lua @@ -1,7 +1,6 @@ local api = vim.api local keymap = vim.keymap -local ts_utils = require("nvim-treesitter.ts_utils") local parsers = require("nvim-treesitter.parsers") local utils = require("wildfire.utils") @@ -102,7 +101,7 @@ local function init_by_node(node) end function M.init_selection() count = vim.v.count1 - local node = ts_utils.get_node_at_cursor() + local node = vim.treesitter.get_node() if not node then return end @@ -155,7 +154,9 @@ local function select_incremental(get_parent) end end node = parent - local nsrow, nscol, nerow, necol = ts_utils.get_vim_range({ node:range() }) + local nsrow, nscol, nerow, necol = vim.treesitter.get_node_range(node) + -- Convert 0-based to 1-based indexing to match vim coordinates + nsrow, nscol, nerow, necol = nsrow + 1, nscol + 1, nerow + 1, necol + 1 local larger_range = utils.range_larger({ nsrow, nscol, nerow, necol }, { csrow, cscol, cerow, cecol }) diff --git a/lua/wildfire/utils.lua b/lua/wildfire/utils.lua index 5915356..d787796 100644 --- a/lua/wildfire/utils.lua +++ b/lua/wildfire/utils.lua @@ -1,6 +1,5 @@ local api = vim.api -local ts_utils = require("nvim-treesitter.ts_utils") local ts = vim.treesitter local M = {} @@ -9,8 +8,12 @@ function M.get_range(node_or_range) if type(node_or_range) == "table" then start_row, start_col, end_row, end_col = unpack(node_or_range) else - local buf = api.nvim_get_current_buf() - start_row, start_col, end_row, end_col = ts_utils.get_vim_range({ ts.get_node_range(node_or_range) }, buf) + start_row, start_col, end_row, end_col = ts.get_node_range(node_or_range) + -- Convert 0-based to 1-based indexing to match vim coordinates + start_row = start_row + 1 + start_col = start_col + 1 + end_row = end_row + 1 + end_col = end_col + 1 end return start_row, start_col, end_row, end_col ---@type integer, integer, integer, integer end @@ -63,15 +66,15 @@ end function M.print_selection(node_or_range) local bufnr = api.nvim_get_current_buf() - local lines + local node_text if type(node_or_range) == "table" then local srow, scol, erow, ecol srow, scol, erow, ecol = unpack(node_or_range) - lines = vim.api.nvim_buf_get_text(bufnr, srow - 1, scol - 1, erow - 1, ecol, {}) + local lines = vim.api.nvim_buf_get_text(bufnr, srow - 1, scol - 1, erow - 1, ecol, {}) + node_text = table.concat(lines, "\n") else - lines = ts_utils.get_node_text(node_or_range, bufnr) + node_text = vim.treesitter.get_node_text(node_or_range, bufnr) end - local node_text = table.concat(lines, "\n") print(node_text) end @@ -81,7 +84,12 @@ function M.update_selection(buf, node_or_range, selection_mode) if type(node_or_range) == "table" then start_row, start_col, end_row, end_col = unpack(node_or_range) else - start_row, start_col, end_row, end_col = ts_utils.get_vim_range({ ts.get_node_range(node_or_range) }, buf) + start_row, start_col, end_row, end_col = ts.get_node_range(node_or_range) + -- Convert 0-based to 1-based indexing to match vim coordinates + start_row = start_row + 1 + start_col = start_col + 1 + end_row = end_row + 1 + end_col = end_col + 1 end local v_table = { charwise = "v", linewise = "V", blockwise = "" } From 8097db8dfd9c03c6e711cb3b59648470d3505e84 Mon Sep 17 00:00:00 2001 From: Starslayerx <1049353754@qq.com> Date: Sat, 11 Oct 2025 10:27:00 +0800 Subject: [PATCH 2/4] fix: correct end_col coordinate conversion from treesitter to vim The previous migration incorrectly added +1 to end_col when converting from treesitter's 0-based exclusive indexing to vim's 1-based inclusive indexing. This caused selections to include an extra character on the right side. The correct conversion is: - start_row, start_col, end_row: 0-based -> 1-based (add +1) - end_col: 0-based exclusive == 1-based inclusive (no change needed) This fixes the bug where selecting text inside brackets/quotes would incorrectly include the closing bracket/quote character. --- lua/wildfire/init.lua | 4 +++- lua/wildfire/utils.lua | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lua/wildfire/init.lua b/lua/wildfire/init.lua index a377126..5de9310 100644 --- a/lua/wildfire/init.lua +++ b/lua/wildfire/init.lua @@ -156,7 +156,9 @@ local function select_incremental(get_parent) node = parent local nsrow, nscol, nerow, necol = vim.treesitter.get_node_range(node) -- Convert 0-based to 1-based indexing to match vim coordinates - nsrow, nscol, nerow, necol = nsrow + 1, nscol + 1, nerow + 1, necol + 1 + -- Note: treesitter end_col is exclusive, but vim coordinates are inclusive + nsrow, nscol, nerow, necol = nsrow + 1, nscol + 1, nerow + 1, necol + -- necol: 0-based exclusive == 1-based inclusive, so no +1 needed local larger_range = utils.range_larger({ nsrow, nscol, nerow, necol }, { csrow, cscol, cerow, cecol }) diff --git a/lua/wildfire/utils.lua b/lua/wildfire/utils.lua index d787796..8aaf3c6 100644 --- a/lua/wildfire/utils.lua +++ b/lua/wildfire/utils.lua @@ -10,10 +10,12 @@ function M.get_range(node_or_range) else start_row, start_col, end_row, end_col = ts.get_node_range(node_or_range) -- Convert 0-based to 1-based indexing to match vim coordinates + -- Note: treesitter end_col is exclusive, but vim coordinates are inclusive start_row = start_row + 1 start_col = start_col + 1 end_row = end_row + 1 - end_col = end_col + 1 + -- end_col is already exclusive in treesitter (0-based), converting to 1-based inclusive means no change needed + -- because: 0-based exclusive position == 1-based inclusive position end return start_row, start_col, end_row, end_col ---@type integer, integer, integer, integer end @@ -86,10 +88,12 @@ function M.update_selection(buf, node_or_range, selection_mode) else start_row, start_col, end_row, end_col = ts.get_node_range(node_or_range) -- Convert 0-based to 1-based indexing to match vim coordinates + -- Note: treesitter end_col is exclusive, but vim coordinates are inclusive start_row = start_row + 1 start_col = start_col + 1 end_row = end_row + 1 - end_col = end_col + 1 + -- end_col is already exclusive in treesitter (0-based), converting to 1-based inclusive means no change needed + -- because: 0-based exclusive position == 1-based inclusive position end local v_table = { charwise = "v", linewise = "V", blockwise = "" } From cb27671546be2a911020970a4eeb022da2d92501 Mon Sep 17 00:00:00 2001 From: Starslayerx <1049353754@qq.com> Date: Tue, 14 Oct 2025 15:28:08 +0800 Subject: [PATCH 3/4] fix: add bounds checking and migrate to new treesitter API - Replace deprecated parsers.get_parser() with vim.treesitter.get_parser(buf) - Add buffer/column bounds validation in unsurround_coordinates() - Add cursor position clamping in update_selection() - Use pcall for safe API calls and graceful error handling - Fix E5108 crashes on blank lines and unsupported filetypes --- lua/wildfire/init.lua | 57 ++++++++++++++++++++++++++++++++++++++---- lua/wildfire/utils.lua | 27 ++++++++++++++++++-- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/lua/wildfire/init.lua b/lua/wildfire/init.lua index 5de9310..55032f1 100644 --- a/lua/wildfire/init.lua +++ b/lua/wildfire/init.lua @@ -1,7 +1,6 @@ local api = vim.api local keymap = vim.keymap -local parsers = require("nvim-treesitter.parsers") local utils = require("wildfire.utils") local M = {} @@ -29,7 +28,24 @@ local count = 1 function M.unsurround_coordinates(node_or_range, buf) -- local lines = vim.split(s, "\n") local srow, scol, erow, ecol = utils.get_range(node_or_range) - local lines = vim.api.nvim_buf_get_text(buf, srow - 1, scol - 1, erow - 1, ecol, {}) + + -- Validate buffer bounds to prevent index out of bounds error + local line_count = vim.api.nvim_buf_line_count(buf) + if srow < 1 or erow > line_count or srow > erow then + return false, { srow, scol, erow, ecol } + end + + -- Validate column bounds for the specific lines + if scol < 1 or ecol < 0 then + return false, { srow, scol, erow, ecol } + end + + -- Use pcall to safely get text, handle any edge cases + local ok, lines = pcall(vim.api.nvim_buf_get_text, buf, srow - 1, scol - 1, erow - 1, ecol, {}) + if not ok or not lines or #lines == 0 then + return false, { srow, scol, erow, ecol } + end + local node_text = table.concat(lines, "\n") local match_brackets = nil for _, pair in ipairs(M.options.surrounds) do @@ -103,6 +119,15 @@ function M.init_selection() count = vim.v.count1 local node = vim.treesitter.get_node() if not node then + -- No treesitter node available, try to handle gracefully + -- Check if treesitter is available for this filetype + local buf = api.nvim_get_current_buf() + local ok, parser = pcall(vim.treesitter.get_parser, buf) + if not ok or not parser then + -- No parser available for this filetype + vim.notify("Wildfire: No treesitter parser available for this filetype", vim.log.levels.WARN) + return + end return end init_by_node(node) @@ -133,9 +158,21 @@ local function select_incremental(get_parent) -- Initialize incremental selection with current selection if not nodes or #nodes == 0 then - local root = parsers.get_parser():parse()[1]:root() + -- Use native vim.treesitter API to get the parser + local ok, parser = pcall(vim.treesitter.get_parser, buf) + if not ok or not parser then + -- No parser available for this filetype, fallback to visual selection + return + end + local tree = parser:parse()[1] + if not tree then + return + end + local root = tree:root() local node = root:named_descendant_for_range(csrow - 1, cscol - 1, cerow - 1, cecol) - update_selection_by_node(node) + if node then + update_selection_by_node(node) + end return end @@ -146,7 +183,17 @@ local function select_incremental(get_parent) if not parent or parent == node then -- Keep searching in the main tree -- TODO: we should search on the parent tree of the current node. - local root = parsers.get_parser():parse()[1]:root() + local ok, parser = pcall(vim.treesitter.get_parser, buf) + if not ok or not parser then + utils.update_selection(buf, node) + return + end + local tree = parser:parse()[1] + if not tree then + utils.update_selection(buf, node) + return + end + local root = tree:root() parent = root:named_descendant_for_range(csrow - 1, cscol - 1, cerow - 1, cecol) if not parent or root == node or parent == node then utils.update_selection(buf, node) diff --git a/lua/wildfire/utils.lua b/lua/wildfire/utils.lua index 8aaf3c6..02c6980 100644 --- a/lua/wildfire/utils.lua +++ b/lua/wildfire/utils.lua @@ -96,6 +96,29 @@ function M.update_selection(buf, node_or_range, selection_mode) -- because: 0-based exclusive position == 1-based inclusive position end + -- Validate buffer bounds to prevent cursor position errors + local line_count = api.nvim_buf_line_count(buf) + if start_row < 1 or end_row < 1 or start_row > line_count or end_row > line_count then + -- Invalid row range, cannot update selection + return + end + + -- Validate column bounds + if start_col < 1 or end_col < 0 then + -- Invalid column range, cannot update selection + return + end + + -- Get the actual line lengths to validate column positions + local start_line_text = api.nvim_buf_get_lines(buf, start_row - 1, start_row, false)[1] or "" + local end_line_text = api.nvim_buf_get_lines(buf, end_row - 1, end_row, false)[1] or "" + local start_line_len = #start_line_text + local end_line_len = #end_line_text + + -- Clamp column positions to valid ranges (0-indexed for nvim_win_set_cursor) + start_col = math.max(0, math.min(start_col - 1, start_line_len)) + end_col = math.max(0, math.min(end_col - 1, end_line_len)) + local v_table = { charwise = "v", linewise = "V", blockwise = "" } selection_mode = selection_mode or "charwise" @@ -115,8 +138,8 @@ function M.update_selection(buf, node_or_range, selection_mode) api.nvim_cmd({ cmd = "normal", bang = true, args = { selection_mode } }, {}) end - api.nvim_win_set_cursor(0, { start_row, start_col - 1 }) + api.nvim_win_set_cursor(0, { start_row, start_col }) vim.cmd("normal! o") - api.nvim_win_set_cursor(0, { end_row, end_col - 1 }) + api.nvim_win_set_cursor(0, { end_row, end_col }) end return M From e12ba5457b6809ed3f3acbb200e11c0764f506a1 Mon Sep 17 00:00:00 2001 From: Starslayerx <1049353754@qq.com> Date: Wed, 15 Oct 2025 09:11:43 +0800 Subject: [PATCH 4/4] docs: update installation requirements for treesitter API migration - Add Neovim >= 0.9.0 requirement - Specify nvim-treesitter main branch dependency - Reflects migration from deprecated nvim-treesitter.ts_utils to vim.treesitter APIs --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2bfe2b2..66dbede 100644 --- a/README.md +++ b/README.md @@ -115,11 +115,15 @@ Beyond the basic actions such as yank(`CR`), delete(`d`), and change ## Installation +Requires Neovim >= 0.9.0 + ``` lua { "sustech-data/wildfire.nvim", event = "VeryLazy", - dependencies = { "nvim-treesitter/nvim-treesitter" }, + dependencies = { + { "nvim-treesitter/nvim-treesitter", branch = "main" } + }, config = function() require("wildfire").setup() end,