-
Notifications
You must be signed in to change notification settings - Fork 14
Main #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Main #12
Changes from all commits
10ae1c2
8097db8
cb27671
e12ba54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,8 +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") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| local M = {} | ||||||||||||||||||||||
|
|
@@ -30,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 | ||||||||||||||||||||||
|
|
@@ -102,8 +117,17 @@ 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 | ||||||||||||||||||||||
| -- 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 | ||||||||||||||||||||||
|
Comment on lines
121
to
131
|
||||||||||||||||||||||
| end | ||||||||||||||||||||||
| init_by_node(node) | ||||||||||||||||||||||
|
|
@@ -134,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 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -147,15 +183,29 @@ 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) | ||||||||||||||||||||||
| return | ||||||||||||||||||||||
| 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 | ||||||||||||||||||||||
| -- 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 | ||||||||||||||||||||||
|
Comment on lines
+205
to
+208
|
||||||||||||||||||||||
| -- Convert 0-based to 1-based indexing to match vim coordinates | |
| -- 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 | |
| -- Convert 0-based to 1-based indexing to match vim coordinates. | |
| -- Treesitter rows/cols are 0-based; Vim's are 1-based. Treesitter end_col is | |
| -- 0-based *exclusive*, while Vim uses 1-based *inclusive* end columns. | |
| -- For the end column, the +1 (0-based → 1-based) and -1 (exclusive → inclusive) | |
| -- cancel out: (necol + 1) - 1 == necol, so we leave `necol` unchanged. | |
| nsrow, nscol, nerow, necol = nsrow + 1, nscol + 1, nerow + 1, necol |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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,14 @@ 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 | ||||||||||||||||||||||||||||||||
| -- 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 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 | ||||||||||||||||||||||||||||||||
|
Comment on lines
+13
to
+18
|
||||||||||||||||||||||||||||||||
| -- 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 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 | |
| -- Note: treesitter end_col is 0-based exclusive, while vim coordinates are 1-based inclusive. | |
| start_row = start_row + 1 | |
| start_col = start_col + 1 | |
| end_row = end_row + 1 | |
| -- Conceptually, converting end_col from 0-based exclusive to 1-based inclusive is: (end_col + 1) - 1. | |
| -- The +1 (0→1-based) and -1 (exclusive→inclusive) cancel each other, so the numeric value of end_col stays the same. |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment states that "end_col is already exclusive in treesitter (0-based), converting to 1-based inclusive means no change needed". However, this logic appears to be incorrect.
When treesitter returns an exclusive end column in 0-based indexing, and we need to convert it to 1-based inclusive indexing:
- A 0-based exclusive position N means "just before position N"
- Converting to 1-based: position N becomes N+1
- But we need inclusive, so the last character is at position N (0-based) = N+1 (1-based) - 1 = N
The comment suggests no adjustment is needed, which happens to arrive at the correct result (not adding 1), but the reasoning is misleading. The actual transformation is: (N + 1) - 1 = N, where +1 converts from 0-based to 1-based, and -1 converts from exclusive to inclusive.
| -- 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 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 | |
| -- Note: treesitter end_col is 0-based and exclusive, while vim uses 1-based inclusive positions. | |
| start_row = start_row + 1 | |
| start_col = start_col + 1 | |
| end_row = end_row + 1 | |
| -- For the end column, conceptually: (0-based exclusive N) -> (1-based exclusive N+1) -> (1-based inclusive (N+1)-1 = N). | |
| -- These steps cancel out numerically, so no adjustment to end_col is needed here. |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The validation on line 107 checks if start_col is less than 1, but after the coordinate conversion on line 93, start_col should always be at least 1 (since it's start_col_0based + 1). The only way start_col could be less than 1 after line 93 is if the original 0-based start_col from treesitter was less than 0, which should not happen with valid treesitter data.
Similarly, the check for end_col less than 0 on line 107 would only trigger if the original 0-based exclusive end_col from treesitter was less than 0. While these checks provide defensive programming, they're checking for conditions that indicate corrupted data from treesitter rather than normal edge cases. Consider whether these represent truly recoverable errors or if they should be assertions/errors instead of silent returns.
| -- Validate column bounds | |
| if start_col < 1 or end_col < 0 then | |
| -- Validate column bounds only for manually supplied ranges. | |
| -- Tree-sitter ranges are expected to be valid after conversion above. | |
| if type(node_or_range) == "table" and (start_col < 1 or end_col < 0) then |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The validation logic calls nvim_buf_get_lines twice to fetch line text for length calculation (lines 113-114), even though this information is only used for clamping. For large files or frequent calls to update_selection, this could have a performance impact.
Consider whether this validation is necessary in all cases. If the coordinates come from treesitter (which should always return valid ranges), the validation may be redundant. Alternatively, the validation could be made conditional or the line fetching could be optimized to fetch both lines in a single call when they're on adjacent or same lines.
| -- 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 "" | |
| -- Get the actual line lengths to validate column positions. | |
| -- Optimize for the common case where start and end are on the same line by | |
| -- fetching that line only once. | |
| local start_line_text, end_line_text | |
| if start_row == end_row then | |
| local line = api.nvim_buf_get_lines(buf, start_row - 1, start_row, false)[1] or "" | |
| start_line_text = line | |
| end_line_text = line | |
| else | |
| start_line_text = api.nvim_buf_get_lines(buf, start_row - 1, start_row, false)[1] or "" | |
| end_line_text = api.nvim_buf_get_lines(buf, end_row - 1, end_row, false)[1] or "" | |
| end |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The column clamping logic may allow the cursor to be positioned one past the end of a line. For a line with length N characters, valid 0-indexed column positions are 0 to N-1. However, the clamping uses math.min(col - 1, line_len), which allows column values up to N (the line length), not N-1 (the last valid character position).
While Vim/Neovim may accept a column value equal to the line length (positioning the cursor after the last character in some modes), this should be verified as intentional behavior. If the intention is to clamp to the last character position, the code should use math.min(col - 1, math.max(0, line_len - 1)) instead.
| 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)) | |
| start_col = math.max(0, math.min(start_col - 1, math.max(0, start_line_len - 1))) | |
| end_col = math.max(0, math.min(end_col - 1, math.max(0, end_line_len - 1))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The pcall on line 44 is used to safely handle potential errors from nvim_buf_get_text, but after the comprehensive bounds checking on lines 32-41, it's unclear what additional errors pcall is meant to catch.
The bounds checking validates:
However, it doesn't validate that ecol is within the length of the end line, which could cause nvim_buf_get_text to fail. If the intention is to handle cases where ecol exceeds the line length, it would be clearer to explicitly validate this condition rather than relying on pcall to catch the error. This would make the error handling more predictable and easier to debug.