From 6142f7d55eaa64cb4d7a62398c2ba881b73d9000 Mon Sep 17 00:00:00 2001 From: Daniel <22460147+dwinkler1@users.noreply.github.com> Date: Sun, 26 Jul 2026 06:57:57 +0000 Subject: [PATCH] fix: harden REPL/terminal code-sending paths - lua/keymap/repl.lua - Replace non-existent `:SlimeSendCurrentLine` with synchronous `vim.fn["slime#send"](line .. "\n")` fallback. - Support both modern `r.send` and older `r.run` R.nvim Lua APIs, falling back to `` mappings if neither module is available. - lua/nix_smart_send.lua - Refactor `send_repl()` to extract Tree-sitter node text and send it directly via `slime#send`, eliminating the `feedkeys(..., "x", true)` race between visual selection and cursor movement. - Replace internal `slime#send_op` call in `slime_send_region()` with the public `SlimeRegionSend` mapping. - Improve `move_to_next_non_empty_line()` to walk up the AST when a node has no next sibling, so the cursor escapes nested blocks. - plugin/03_terminal.lua - Set `vim.g.slime_target = "neovim"` at the top of the module so the target is guaranteed before any slime send. - plugin/21_datascience.lua - Remove duplicate `vim.g.slime_target = "neovim"` now that the terminal module owns the setting. --- lua/keymap/repl.lua | 42 ++++++++++++++++++++++++++++----------- lua/nix_smart_send.lua | 31 +++++++++++++++-------------- plugin/03_terminal.lua | 4 ++++ plugin/21_datascience.lua | 1 - 4 files changed, 50 insertions(+), 28 deletions(-) diff --git a/lua/keymap/repl.lua b/lua/keymap/repl.lua index f43cb9b..35425aa 100644 --- a/lua/keymap/repl.lua +++ b/lua/keymap/repl.lua @@ -20,12 +20,20 @@ function M.send_line() local ft = dispatch_ft() if ft == "r" then - -- R.nvim v1+ exposes a Lua API; fall back to the legacy mappings - -- if a v0.x build is still in use. - local ok, rrun = pcall(require, "r.run") - if ok and rrun and type(rrun.send_line) == "function" then - rrun.send_line() - return + -- R.nvim v1+ exposes a Lua API; try the modern `r.send` module first, + -- then fall back to the older `r.run` module, and finally to . + local ok, rmod = pcall(require, "r.send") + if not ok or not rmod then + ok, rmod = pcall(require, "r.run") + end + if ok and rmod then + if type(rmod.line) == "function" then + rmod.line() + return + elseif type(rmod.send_line) == "function" then + rmod.send_line() + return + end end vim.api.nvim_feedkeys( vim.api.nvim_replace_termcodes("RDSendLine", true, false, true), @@ -48,7 +56,8 @@ function M.send_line() end -- Default: vim-slime (terminal). - vim.cmd("SlimeSendCurrentLine") + local line = vim.api.nvim_get_current_line() + vim.fn["slime#send"](line .. "\n") -- Move to the next line, matching the previous behaviour. vim.cmd("normal! j") end @@ -58,11 +67,20 @@ function M.send_selection() local ft = dispatch_ft() if ft == "r" then - -- Prefer R.nvim v1+ Lua API; fall back to if unavailable. - local ok, rrun = pcall(require, "r.run") - if ok and rrun and type(rrun.send_selection) == "function" then - rrun.send_selection() - return + -- Prefer R.nvim v1+ Lua API; try the modern `r.send` module first, + -- then fall back to the older `r.run` module, and finally to . + local ok, rmod = pcall(require, "r.send") + if not ok or not rmod then + ok, rmod = pcall(require, "r.run") + end + if ok and rmod then + if type(rmod.selection) == "function" then + rmod.selection() + return + elseif type(rmod.send_selection) == "function" then + rmod.send_selection() + return + end end vim.api.nvim_feedkeys( vim.api.nvim_replace_termcodes("RSendSelection", true, false, true), diff --git a/lua/nix_smart_send.lua b/lua/nix_smart_send.lua index 7906d25..51e9611 100644 --- a/lua/nix_smart_send.lua +++ b/lua/nix_smart_send.lua @@ -88,6 +88,12 @@ function M.move_to_next_non_empty_line(current_node) return false end + -- Walk up the tree until we find a node with a next named sibling, + -- so we escape nested blocks when we are on the last statement. + while node and not node:next_named_sibling() do + node = node:parent() + end + node = node:next_named_sibling() while node do if not COMMENT_TYPES[node:type()] then @@ -129,15 +135,11 @@ function M.select_until_global(global_nodes) end function M.slime_send_region() - if vim.fn.exists('*slime#send_op') == 0 then - vim.notify("slime plugin not available", vim.log.levels.ERROR) - return - end - - local slime_command = ":call slime#send_op(visualmode(), 1)" - local termcodes = vim.api.nvim_replace_termcodes(slime_command, true, true, true) - - vim.api.nvim_feedkeys(termcodes, "x", true) + vim.api.nvim_feedkeys( + vim.api.nvim_replace_termcodes("SlimeRegionSend", true, false, true), + "m", + false + ) end function M.send_repl(global_nodes) @@ -155,15 +157,14 @@ function M.send_repl(global_nodes) target_node = next_node end - -- Select the target node and send it to the REPL. - if not M.vselect_node(target_node) then + -- Extract node text and send directly to avoid visual-mode/feedkeys races. + local ok, text = pcall(vim.treesitter.get_node_text, target_node, 0) + if not ok or not text then + vim.notify("Could not extract code from Tree-sitter node", vim.log.levels.WARN) return end - M.slime_send_region() - -- Place cursor at end of visual block - local _, _, er, ec = target_node:range() - vim.api.nvim_win_set_cursor(0, { er + 1, ec }) + vim.fn["slime#send"](text .. "\n") -- Jump to the next relevant AST node instead of scanning lines M.move_to_next_non_empty_line(target_node) diff --git a/plugin/03_terminal.lua b/plugin/03_terminal.lua index 9408760..95d7a29 100644 --- a/plugin/03_terminal.lua +++ b/plugin/03_terminal.lua @@ -1,5 +1,9 @@ local Config = require('config') +-- vim-slime target: use Neovim's built-in terminal. +-- Must be set before any slime send happens. +vim.g.slime_target = "neovim" + local M = {} -- Configuration diff --git a/plugin/21_datascience.lua b/plugin/21_datascience.lua index cb496b9..807d8ed 100644 --- a/plugin/21_datascience.lua +++ b/plugin/21_datascience.lua @@ -22,7 +22,6 @@ end -- terminal later(function() - vim.g.slime_target = "neovim" vim.g.slime_no_mappings = true add("vim-slime") vim.g.slime_cell_delimiter = vim.g.slime_cell_delimiter or "# %%"