mirror of
https://github.com/dwinkler1/nvimConfig.git
synced 2026-08-22 17:43:13 -04:00
- 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 `<Plug>` 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 `<Plug>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.
173 lines
4 KiB
Lua
173 lines
4 KiB
Lua
local M = {}
|
|
|
|
-- Define comment node types as constants
|
|
local COMMENT_TYPES = {
|
|
comment = true,
|
|
block_comment = true,
|
|
line_comment = true,
|
|
}
|
|
|
|
-- Helper function to check if value exists in list (optimized with early return)
|
|
local function is_in_list(list, value)
|
|
if not list or not value then
|
|
return false
|
|
end
|
|
|
|
for _, v in ipairs(list) do
|
|
if v == value then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Safely get the Tree-sitter node under the cursor.
|
|
function M.get_current_node()
|
|
local ok, node = pcall(vim.treesitter.get_node, { ignore_injections = false })
|
|
return ok and node or nil
|
|
end
|
|
|
|
-- Detect the root node type of the current buffer's Tree-sitter tree.
|
|
function M.detect_global_node()
|
|
local cur_node = M.get_current_node()
|
|
local root
|
|
|
|
if not cur_node then
|
|
local ok, parser = pcall(vim.treesitter.get_parser)
|
|
if not ok or not parser then
|
|
return nil
|
|
end
|
|
local trees = parser:parse()
|
|
if not trees or not trees[1] then
|
|
return nil
|
|
end
|
|
root = trees[1]:root()
|
|
else
|
|
root = cur_node:root()
|
|
end
|
|
|
|
return root and root:type() or nil
|
|
end
|
|
|
|
-- Ascend the tree from the current node until we hit a node whose parent is a
|
|
-- "global" node (or the root). This is the unit of code we want to send.
|
|
local function get_target_node(global_nodes)
|
|
local root_type = M.detect_global_node()
|
|
global_nodes = global_nodes or {}
|
|
|
|
local node = M.get_current_node()
|
|
if not node then
|
|
return nil
|
|
end
|
|
|
|
while node do
|
|
local parent = node:parent()
|
|
if not parent then
|
|
break
|
|
end
|
|
|
|
local p_type = parent:type()
|
|
if is_in_list(global_nodes, p_type) or p_type == root_type then
|
|
break
|
|
end
|
|
node = parent
|
|
end
|
|
|
|
return node
|
|
end
|
|
|
|
-- Move the cursor to the next named sibling that is not a comment.
|
|
-- Operates on the Tree-sitter AST instead of scanning lines, so it is fast and
|
|
-- language-agnostic.
|
|
function M.move_to_next_non_empty_line(current_node)
|
|
local node = current_node
|
|
if not node then
|
|
node = get_target_node({})
|
|
end
|
|
if not node then
|
|
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
|
|
local start_row, start_col = node:range()
|
|
pcall(vim.api.nvim_win_set_cursor, 0, { start_row + 1, start_col })
|
|
return true, node
|
|
end
|
|
node = node:next_named_sibling()
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function M.vselect_node(node)
|
|
if not node then
|
|
return false
|
|
end
|
|
|
|
local start_row, _, end_row, _ = node:range()
|
|
|
|
vim.api.nvim_win_set_cursor(0, { start_row + 1, 0 })
|
|
vim.cmd("normal! V")
|
|
vim.api.nvim_win_set_cursor(0, { end_row + 1, 0 })
|
|
|
|
return true
|
|
end
|
|
|
|
function M.select_until_global(global_nodes)
|
|
local target = get_target_node(global_nodes)
|
|
if not target then
|
|
return nil
|
|
end
|
|
|
|
if M.vselect_node(target) then
|
|
return target
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
function M.slime_send_region()
|
|
vim.api.nvim_feedkeys(
|
|
vim.api.nvim_replace_termcodes("<Plug>SlimeRegionSend", true, false, true),
|
|
"m",
|
|
false
|
|
)
|
|
end
|
|
|
|
function M.send_repl(global_nodes)
|
|
local target_node = get_target_node(global_nodes)
|
|
if not target_node then
|
|
return
|
|
end
|
|
|
|
-- If sitting on a comment, step forward first so we don't send comments.
|
|
if COMMENT_TYPES[target_node:type()] then
|
|
local moved, next_node = M.move_to_next_non_empty_line(target_node)
|
|
if not moved or not next_node then
|
|
return
|
|
end
|
|
target_node = next_node
|
|
end
|
|
|
|
-- 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
|
|
|
|
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)
|
|
end
|
|
|
|
return M
|