From a9950f3f06f3902fe2c4e2e7bdd900dc88378195 Mon Sep 17 00:00:00 2001 From: Philippe Massicotte Date: Mon, 9 Feb 2026 08:23:29 -0500 Subject: [PATCH] refactor(run.lua): improve comment stripping and code extraction logic Enhance the `insert_commented` function to handle multi-line code blocks by stripping comments and collapsing them into a single line. Improve cursor movement to the end of the chain for better user experience. refactor(send.lua): modularize pipe chain extraction logic Introduce `get_pipe_node` and `get_pipe_chain` functions to modularize the logic for extracting pipe chain nodes and code. This improves readability and maintainability. Add support for handling comments and cursor positioning within piped expressions. Thank you to @guilhermegarcia for the initial implementation and the general idea. Co-authored-by: Guilherme D. Garcia --- lua/r/run.lua | 36 ++++++++++++++++++++---- lua/r/send.lua | 76 +++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 90 insertions(+), 22 deletions(-) diff --git a/lua/r/run.lua b/lua/r/run.lua index 03cd3580..1e09a0d7 100644 --- a/lua/r/run.lua +++ b/lua/r/run.lua @@ -433,13 +433,39 @@ M.insert = function(cmd, type) end M.insert_commented = function() - local lin = vim.api.nvim_get_current_line() - local cleanl = lin:gsub('".-"', "") - if cleanl:find(";") then + local bufnr = require("r.buffer").create_r_buffer() + local code, end_row + + if bufnr then + code, end_row = send.get_pipe_chain(bufnr, true) + end + + if code then + -- Strip comments and collapse to single line + local lines = {} + for line in code:gmatch("[^\n]+") do + local stripped = line:gsub("%s*#.*", "") + if stripped:match("%S") then + table.insert(lines, stripped) + end + end + code = table.concat(lines, " ") + -- Move cursor to end of chain + if end_row then + vim.api.nvim_win_set_cursor(0, { end_row, 0 }) + end + else + -- Fallback to single line + code = vim.api.nvim_get_current_line():gsub("%s*#.*", "") + end + + if not code:match("%S") then return end + + local check = code:gsub('".-"', "") + if check:find(";") then warn("`print(line)` works only if `line` is a single command") end - cleanl = string.gsub(lin, "%s*#.*", "") - M.insert("print(" .. cleanl .. ")", "comment") + M.insert("print(" .. code .. ")", "comment") end ---Call R functions for the word under cursor diff --git a/lua/r/send.lua b/lua/r/send.lua index db4baa08..6e25cc5e 100644 --- a/lua/r/send.lua +++ b/lua/r/send.lua @@ -638,16 +638,17 @@ M.line = function(m) end end ---- Send the above chain of piped commands -M.chain = function() - local bufnr = create_r_buffer() - if not bufnr then return end - +--- Get the pipe chain node at cursor position +--- @param bufnr integer Buffer number +--- @return table|nil pipe_node The pipe chain node, or nil +--- @return table|nil root The tree root +--- @return integer cursor_row The cursor row (0-indexed) +local function get_pipe_node(bufnr) local parser = vim.treesitter.get_parser(bufnr, "r") - if not parser then return end + if not parser then return nil end local tree = parser:parse()[1] - if not tree then return end + if not tree then return nil end local root = tree:root() local query = vim.treesitter.query.parse( @@ -677,17 +678,50 @@ M.chain = function() ) local cursor_row = vim.api.nvim_win_get_cursor(0)[1] - 1 - local pipe_block_node for _, node in query:iter_captures(root, bufnr, 0, -1) do local start_row, _, end_row = node:range() if cursor_row >= start_row and cursor_row <= end_row then - pipe_block_node = node - break + return node, root, cursor_row + end + end + return nil +end + +--- Get the full pipe chain code at cursor (with optional assignment) +--- @param bufnr integer Buffer number +--- @param include_assignment boolean Include parent assignment if present +--- @return string|nil code The code, or nil if not in a pipe chain +--- @return integer|nil end_row Last row of the chain (1-indexed) +M.get_pipe_chain = function(bufnr, include_assignment) + local pipe_node, _, _ = get_pipe_node(bufnr) + if not pipe_node then return nil, nil end + + local node = pipe_node + if include_assignment then + local parent = pipe_node:parent() + if parent and parent:type() == "binary_operator" then + local op = parent:field("operator")[1] + if op then + local op_text = vim.treesitter.get_node_text(op, bufnr) + if op_text == "<-" or op_text == "=" then + node = parent + end + end end end - if not pipe_block_node then + local _, _, end_row = node:range() + return vim.treesitter.get_node_text(node, bufnr), end_row + 1 +end + +--- Send the above chain of piped commands +M.chain = function() + local bufnr = create_r_buffer() + if not bufnr then return end + + local pipe_node, root, cursor_row = get_pipe_node(bufnr) + if not pipe_node then inform("The cursor is not inside a piped expression.") return end @@ -703,21 +737,29 @@ M.chain = function() ) local sibling = nil + local last_sibling = nil + local pipe_start_row, _, pipe_end_row = pipe_node:range() - local pipe_start_row, _, pipe_end_row = pipe_block_node:range() + -- Check if cursor is on a comment line + local cur_line = vim.api.nvim_buf_get_lines(bufnr, cursor_row, cursor_row + 1, false)[1] or "" + local on_comment = cur_line:match("^%s*#") ~= nil for id, node, _ in call_query:iter_captures(root, bufnr, pipe_start_row, pipe_end_row) do local capture_name = call_query.captures[id] local _, _, end_row = node:range() - if capture_name == "operator" and cursor_row <= end_row then - sibling = node:prev_sibling() - break + if capture_name == "operator" then + if cursor_row <= end_row then + sibling = node:prev_sibling() + break + end + -- Track last operator before cursor (for comment line case) + last_sibling = node:prev_sibling() end end - local captured_node = sibling or pipe_block_node - + -- If on comment and no match found, use last operator before comment + local captured_node = sibling or (on_comment and last_sibling) or pipe_node M.source_lines({ vim.treesitter.get_node_text(captured_node, bufnr) }, nil) end