mirror of
https://github.com/folke/snacks.nvim
synced 2025-08-04 10:49:08 +00:00
feat(debug): added Snacks.debug.run()
to execute the buffer or selection with inlined print and errors
This commit is contained in:
parent
353f0698ed
commit
e1fe4f5afe
3 changed files with 142 additions and 6 deletions
|
@ -10,6 +10,7 @@ Table of Contents *snacks-debug-table-of-contents*
|
|||
- Snacks.debug.inspect() |snacks-debug-module-snacks.debug.inspect()|
|
||||
- Snacks.debug.log() |snacks-debug-module-snacks.debug.log()|
|
||||
- Snacks.debug.profile() |snacks-debug-module-snacks.debug.profile()|
|
||||
- Snacks.debug.run() |snacks-debug-module-snacks.debug.run()|
|
||||
- Snacks.debug.stats() |snacks-debug-module-snacks.debug.stats()|
|
||||
- Snacks.debug.trace() |snacks-debug-module-snacks.debug.trace()|
|
||||
- Snacks.debug.tracemod() |snacks-debug-module-snacks.debug.tracemod()|
|
||||
|
@ -64,7 +65,9 @@ Whatthis does:
|
|||
Show a notification with a pretty backtrace
|
||||
|
||||
>lua
|
||||
Snacks.debug.backtrace()
|
||||
---@param msg? string|string[]
|
||||
---@param opts? snacks.notify.Opts
|
||||
Snacks.debug.backtrace(msg, opts)
|
||||
<
|
||||
|
||||
|
||||
|
@ -107,6 +110,17 @@ Very simple function to profile a lua function. **flush**set to `true` to use
|
|||
<
|
||||
|
||||
|
||||
`Snacks.debug.run()` *Snacks.debug.run()*
|
||||
|
||||
Run the current buffer or a range of lines. Shows the output of `print` inlined
|
||||
with the code. Any error will be shown as a diagnostic.
|
||||
|
||||
>lua
|
||||
---@param opts? {name?:string, buf?:number, print?:boolean}
|
||||
Snacks.debug.run(opts)
|
||||
<
|
||||
|
||||
|
||||
`Snacks.debug.stats()` *Snacks.debug.stats()*
|
||||
|
||||
>lua
|
||||
|
|
|
@ -48,7 +48,9 @@ Snacks.debug()
|
|||
Show a notification with a pretty backtrace
|
||||
|
||||
```lua
|
||||
Snacks.debug.backtrace()
|
||||
---@param msg? string|string[]
|
||||
---@param opts? snacks.notify.Opts
|
||||
Snacks.debug.backtrace(msg, opts)
|
||||
```
|
||||
|
||||
### `Snacks.debug.inspect()`
|
||||
|
@ -89,6 +91,17 @@ Very simple function to profile a lua function.
|
|||
Snacks.debug.profile(fn, opts)
|
||||
```
|
||||
|
||||
### `Snacks.debug.run()`
|
||||
|
||||
Run the current buffer or a range of lines.
|
||||
Shows the output of `print` inlined with the code.
|
||||
Any error will be shown as a diagnostic.
|
||||
|
||||
```lua
|
||||
---@param opts? {name?:string, buf?:number, print?:boolean}
|
||||
Snacks.debug.run(opts)
|
||||
```
|
||||
|
||||
### `Snacks.debug.stats()`
|
||||
|
||||
```lua
|
||||
|
|
|
@ -7,6 +7,12 @@ local M = setmetatable({}, {
|
|||
})
|
||||
|
||||
local uv = vim.uv or vim.loop
|
||||
local ns = vim.api.nvim_create_namespace("snacks_debug")
|
||||
|
||||
Snacks.util.set_hl({
|
||||
Indent = "LineNr",
|
||||
Print = "NonText",
|
||||
}, { prefix = "SnacksDebug" })
|
||||
|
||||
-- Show a notification with a pretty printed dump of the object(s)
|
||||
-- with lua treesitter highlighting and the location of the caller
|
||||
|
@ -31,12 +37,115 @@ function M.inspect(...)
|
|||
Snacks.notify.warn(vim.inspect(len == 1 and obj[1] or len > 0 and obj or nil), { title = title, ft = "lua" })
|
||||
end
|
||||
|
||||
--- Run the current buffer or a range of lines.
|
||||
--- Shows the output of `print` inlined with the code.
|
||||
--- Any error will be shown as a diagnostic.
|
||||
---@param opts? {name?:string, buf?:number, print?:boolean}
|
||||
function M.run(opts)
|
||||
opts = vim.tbl_extend("force", { print = true }, opts or {})
|
||||
local buf = opts.buf or 0
|
||||
buf = buf == 0 and vim.api.nvim_get_current_buf() or buf
|
||||
local name = opts.name or vim.fn.fnamemodify(vim.api.nvim_buf_get_name(buf), ":t")
|
||||
|
||||
-- Get the lines to run
|
||||
local lines ---@type string[]
|
||||
if vim.fn.mode():find("[vV]") then
|
||||
vim.fn.feedkeys(":", "nx")
|
||||
local from = vim.api.nvim_buf_get_mark(buf, "<")
|
||||
local to = vim.api.nvim_buf_get_mark(buf, ">")
|
||||
lines = vim.api.nvim_buf_get_text(buf, from[1] - 1, from[2], to[1] - 1, to[2] + 1, {})
|
||||
-- Insert empty lines to keep the line numbers
|
||||
for _ = 1, from[1] - 1 do
|
||||
table.insert(lines, 1, "")
|
||||
end
|
||||
vim.fn.feedkeys("gv", "nx")
|
||||
else
|
||||
lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
||||
end
|
||||
|
||||
-- Clear diagnostics and extmarks
|
||||
local function reset()
|
||||
vim.diagnostic.reset(ns, buf)
|
||||
vim.api.nvim_buf_clear_namespace(buf, ns, 0, -1)
|
||||
end
|
||||
reset()
|
||||
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, {
|
||||
group = vim.api.nvim_create_augroup("snacks_debug_run_" .. buf, { clear = true }),
|
||||
buffer = buf,
|
||||
callback = reset,
|
||||
})
|
||||
|
||||
-- Get the line number from the msg or stack
|
||||
local function get_line(msg)
|
||||
local line = msg and msg:match("^" .. vim.pesc(name) .. ":(%d+):")
|
||||
if line then
|
||||
return line
|
||||
end
|
||||
for level = 2, 20 do
|
||||
local info = debug.getinfo(level, "Sln")
|
||||
if info and info.source == "@" .. name then
|
||||
return info.currentline
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Error handler
|
||||
local function on_error(err)
|
||||
local line = get_line(err)
|
||||
if line then
|
||||
vim.diagnostic.set(ns, buf, {
|
||||
{ col = 0, lnum = line - 1, message = err, severity = vim.diagnostic.severity.ERROR },
|
||||
})
|
||||
end
|
||||
M.backtrace({ err, "" }, { title = "Error in " .. name, level = vim.log.levels.ERROR })
|
||||
end
|
||||
|
||||
-- Print handler
|
||||
local function on_print(...)
|
||||
local str = table.concat(
|
||||
vim.tbl_map(function(v)
|
||||
return type(v) == "string" and v or vim.inspect(v)
|
||||
end, { ... }),
|
||||
" "
|
||||
)
|
||||
---@type string[][][]
|
||||
local virt_lines = {}
|
||||
for _, line in ipairs(vim.split(str, "\n", { plain = true })) do
|
||||
table.insert(virt_lines, { { " │ ", "SnacksDebugIndent" }, { line, "SnacksDebugPrint" } })
|
||||
end
|
||||
vim.api.nvim_buf_set_extmark(buf, ns, (get_line() or 1) - 1, 0, {
|
||||
virt_lines = virt_lines,
|
||||
})
|
||||
end
|
||||
|
||||
-- Load the code
|
||||
local chunk, err = load(table.concat(lines, "\n"), "@" .. name)
|
||||
if not chunk then
|
||||
return on_error(err)
|
||||
end
|
||||
|
||||
-- Setup the env
|
||||
local env = { print = opts.print and on_print or nil }
|
||||
package.seeall(env)
|
||||
setfenv(chunk, env)
|
||||
xpcall(chunk, function(e)
|
||||
on_error(e)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Show a notification with a pretty backtrace
|
||||
function M.backtrace()
|
||||
local trace = {}
|
||||
---@param msg? string|string[]
|
||||
---@param opts? snacks.notify.Opts
|
||||
function M.backtrace(msg, opts)
|
||||
opts = vim.tbl_deep_extend("force", {
|
||||
level = vim.log.levels.WARN,
|
||||
title = "Backtrace",
|
||||
}, opts or {})
|
||||
---@type string[]
|
||||
local trace = type(msg) == "table" and msg or type(msg) == "string" and { msg } or {}
|
||||
for level = 2, 20 do
|
||||
local info = debug.getinfo(level, "Sln")
|
||||
if info and info.what == "Lua" and info.source ~= "lua" then
|
||||
if info and info.what ~= "C" and info.source ~= "lua" and not info.source:find("snacks[/\\]debug") then
|
||||
local line = "- `" .. vim.fn.fnamemodify(info.source:sub(2), ":p:~:.") .. "`:" .. info.currentline
|
||||
if info.name then
|
||||
line = line .. " _in_ **" .. info.name .. "**"
|
||||
|
@ -44,7 +153,7 @@ function M.backtrace()
|
|||
table.insert(trace, line)
|
||||
end
|
||||
end
|
||||
Snacks.notify.warn(#trace > 0 and (table.concat(trace, "\n")) or "", { title = "Backtrace" })
|
||||
Snacks.notify(#trace > 0 and (table.concat(trace, "\n")) or "", opts)
|
||||
end
|
||||
|
||||
-- Very simple function to profile a lua function.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue