mirror of
https://github.com/mfussenegger/nvim-dap
synced 2024-09-16 14:24:03 +02:00
Add support for sessionless expression interpreters
This commit is contained in:
parent
6f79b82299
commit
3be7caf8a5
8 changed files with 160 additions and 31 deletions
75
lua/dap.lua
75
lua/dap.lua
|
@ -268,9 +268,20 @@ M.adapters = {}
|
|||
---@type table<string, dap.Configuration[]>
|
||||
M.configurations = {}
|
||||
|
||||
|
||||
---@class dap.Interpreter
|
||||
---@field evaluate fun(expression: string): dap.InterpreterResult
|
||||
|
||||
---@class dap.InterpreterResult : dap.VariableContainer
|
||||
---@field result string
|
||||
---@field type? string
|
||||
|
||||
local providers = {
|
||||
---@type table<string, fun(bufnr: integer): dap.Configuration[]>
|
||||
configs = {},
|
||||
|
||||
---@type table<string, dap.Interpreter>
|
||||
interpreters = {},
|
||||
}
|
||||
do
|
||||
local providers_mt = {
|
||||
|
@ -279,6 +290,70 @@ do
|
|||
end,
|
||||
}
|
||||
M.providers = setmetatable(providers, providers_mt)
|
||||
|
||||
local lua_interpreter = {}
|
||||
providers.interpreters.lua = lua_interpreter
|
||||
|
||||
---@param key any
|
||||
---@param value any
|
||||
---@return dap.Variable
|
||||
local function to_variable(key, value)
|
||||
local result = {
|
||||
name = tostring(key),
|
||||
value = tostring(value),
|
||||
type = type(value)
|
||||
}
|
||||
if type(value) == "table" then
|
||||
result.value = result.value .. " size=" .. vim.tbl_count(value)
|
||||
result.variables = {}
|
||||
for k, v in pairs(value) do
|
||||
table.insert(result.variables, to_variable(k, v))
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
---@param expression string
|
||||
---@result dap.InterpreterResult
|
||||
function lua_interpreter.evaluate(expression)
|
||||
local parser = vim.treesitter.get_string_parser(expression, "lua")
|
||||
local trees = parser:parse()
|
||||
local root = trees[1]:root() -- root is likely chunk
|
||||
local child = root:child(root:child_count() - 1)
|
||||
if child and child:type() ~= "return_statement" then
|
||||
local slnum, scol, _, _ = child:range()
|
||||
local lines = vim.split(expression, "\n", { plain = true })
|
||||
local line = lines[slnum + 1]
|
||||
lines[slnum + 1] = line:sub(1, scol) .. "return " .. line:sub(scol)
|
||||
expression = table.concat(lines, "\n")
|
||||
end
|
||||
local fn, err = loadstring(expression)
|
||||
local result
|
||||
if fn then
|
||||
local env = getfenv(fn)
|
||||
local newenv = {}
|
||||
function newenv.print(...)
|
||||
local line = table.concat({...})
|
||||
M.repl.append(line .. "\n", nil, { newline = false })
|
||||
end
|
||||
setmetatable(newenv, {__index = env})
|
||||
setfenv(fn, newenv)
|
||||
result = fn() or "nil"
|
||||
if type(result) == "table" then
|
||||
local tbl = result
|
||||
result = tostring(result) .. " size=" .. tostring(vim.tbl_count(tbl))
|
||||
local variables = {}
|
||||
for k, v in pairs(tbl) do
|
||||
table.insert(variables, to_variable(k, v))
|
||||
end
|
||||
return {result = result, variables = variables}
|
||||
end
|
||||
end
|
||||
return {
|
||||
result = result and tostring(result) or ("Error: " .. err),
|
||||
variables = {}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -6,12 +6,14 @@ local variable = {}
|
|||
M.variable = variable
|
||||
|
||||
local syntax_mapping = {
|
||||
boolean = 'Boolean',
|
||||
String = 'String',
|
||||
int = 'Number',
|
||||
long = 'Number',
|
||||
double = 'Float',
|
||||
float = 'Float',
|
||||
boolean = "Boolean",
|
||||
String = "String",
|
||||
int = "Number",
|
||||
long = "Number",
|
||||
number = "Number",
|
||||
double = "Float",
|
||||
float = "Float",
|
||||
["function"] = "Function",
|
||||
}
|
||||
|
||||
|
||||
|
@ -35,7 +37,7 @@ function variable.render_parent(var)
|
|||
if var.name then
|
||||
return variable.render_child(var --[[@as dap.Variable]], 0)
|
||||
end
|
||||
local syntax_group = var.type and syntax_mapping[var.type]
|
||||
local syntax_group = var.type and syntax_mapping[var.type:lower()]
|
||||
if syntax_group then
|
||||
return var.result, {{syntax_group, 0, -1},}
|
||||
end
|
||||
|
@ -62,7 +64,7 @@ function variable.has_children(var)
|
|||
return (var.variables and #var.variables > 0) or var.variablesReference ~= 0
|
||||
end
|
||||
|
||||
---@param var dap.Variable|dap.Scope
|
||||
---@param var dap.VariableContainer
|
||||
---@result dap.Variable[]
|
||||
function variable.get_children(var)
|
||||
return var.variables or {}
|
||||
|
@ -82,7 +84,7 @@ local function cmp_vars(a, b)
|
|||
end
|
||||
|
||||
|
||||
---@param var dap.Variable|dap.Scope
|
||||
---@param var dap.VariableContainer
|
||||
---@param cb fun(variables: dap.Variable[])
|
||||
function variable.fetch_children(var, cb)
|
||||
local session = require('dap').session()
|
||||
|
|
|
@ -33,10 +33,10 @@ function M.check()
|
|||
health.info("Adapter is a function. Can't validate it")
|
||||
else
|
||||
if adapter.type == "executable" then
|
||||
adapter = adapter --[[@as ExecutableAdapter]]
|
||||
adapter = adapter --[[@as dap.ExecutableAdapter]]
|
||||
check_executable(adapter.command)
|
||||
elseif adapter.type == "server" then
|
||||
adapter = adapter --[[@as ServerAdapter]]
|
||||
adapter = adapter --[[@as dap.ServerAdapter]]
|
||||
if not adapter.port then
|
||||
health.error("Missing required `port` property")
|
||||
end
|
||||
|
@ -44,7 +44,7 @@ function M.check()
|
|||
check_executable(adapter.executable.command)
|
||||
end
|
||||
elseif adapter.type == "pipe" then
|
||||
adapter = adapter --[[@as PipeAdapter]]
|
||||
adapter = adapter --[[@as dap.PipeAdapter]]
|
||||
if not adapter.pipe then
|
||||
health.error("Missing required `pipe` property")
|
||||
end
|
||||
|
|
|
@ -98,18 +98,23 @@
|
|||
---@class dap.VariableResponse
|
||||
---@field variables dap.Variable[]
|
||||
|
||||
---@class dap.Variable
|
||||
|
||||
---@class dap.VariableContainer
|
||||
---@field variablesReference integer if > 0 the variable is structured
|
||||
---@field variables? dap.Variable[] resolved variablesReference. Not part of the spec; added by nvim-dap
|
||||
---@field parent? dap.VariableContainer injected by nvim-dap
|
||||
|
||||
|
||||
---@class dap.Variable : dap.VariableContainer
|
||||
---@field name string
|
||||
---@field value string
|
||||
---@field type? string
|
||||
---@field presentationHint? dap.VariablePresentationHint
|
||||
---@field evaluateName? string
|
||||
---@field variablesReference number if > 0 the variable is structured
|
||||
---@field namedVariables? number
|
||||
---@field indexedVariables? number
|
||||
---@field memoryReference? string
|
||||
---@field variables? dap.Variable[] resolved variablesReference. Not part of the spec; added by nvim-dap
|
||||
---@field parent? dap.Variable|dap.Scope injected by nvim-dap
|
||||
|
||||
|
||||
---@class dap.EvaluateArguments
|
||||
---@field expression string
|
||||
|
@ -117,11 +122,11 @@
|
|||
---@field context? "watch"|"repl"|"hover"|"clipboard"|"variables"|string
|
||||
---@field format? dap.ValueFormat
|
||||
|
||||
---@class dap.EvaluateResponse
|
||||
|
||||
---@class dap.EvaluateResponse : dap.VariableContainer
|
||||
---@field result string
|
||||
---@field type? string
|
||||
---@field presentationHint? dap.VariablePresentationHint
|
||||
---@field variablesReference number
|
||||
---@field namedVariables? number
|
||||
---@field indexedVariables? number
|
||||
---@field memoryReference? string
|
||||
|
|
|
@ -152,6 +152,8 @@ local function print_commands()
|
|||
end
|
||||
|
||||
|
||||
---@param err dap.ErrorResponse?
|
||||
---@param resp dap.EvaluateResponse|dap.InterpreterResult|nil
|
||||
local function evaluate_handler(err, resp)
|
||||
if err then
|
||||
local message = utils.fmt_error(err)
|
||||
|
@ -160,9 +162,15 @@ local function evaluate_handler(err, resp)
|
|||
end
|
||||
return
|
||||
end
|
||||
if not resp then
|
||||
M.append("No response", nil, { newline = false })
|
||||
return
|
||||
end
|
||||
local layer = ui.layer(repl.buf)
|
||||
local attributes = (resp.presentationHint or {}).attributes or {}
|
||||
if resp.variablesReference > 0 or vim.tbl_contains(attributes, 'rawString') then
|
||||
local var_ref = resp.variablesReference or 0
|
||||
local vars = resp.variables
|
||||
if (vars and next(vars)) or var_ref > 0 or vim.tbl_contains(attributes, 'rawString') then
|
||||
local spec = require('dap.entity').variable.tree_spec
|
||||
local tree = ui.new_tree(spec)
|
||||
-- tree.render would "append" twice, once for the top element and once for the children
|
||||
|
@ -217,7 +225,9 @@ local function print_threads(threads)
|
|||
end
|
||||
|
||||
|
||||
function execute(text)
|
||||
---@param opts dap.repl.execute.opts?
|
||||
function execute(text, opts)
|
||||
opts = opts or {}
|
||||
if text == '' then
|
||||
if history.last then
|
||||
text = history.last
|
||||
|
@ -252,15 +262,16 @@ function execute(text)
|
|||
end
|
||||
return
|
||||
end
|
||||
if not session then
|
||||
M.append('No active debug session')
|
||||
return
|
||||
end
|
||||
|
||||
if vim.tbl_contains(M.commands.continue, text) then
|
||||
require('dap').continue()
|
||||
elseif vim.tbl_contains(M.commands.next_, text) then
|
||||
require('dap').step_over()
|
||||
elseif vim.tbl_contains(M.commands.capabilities, text) then
|
||||
if not session then
|
||||
M.append('No active debug session')
|
||||
return
|
||||
end
|
||||
M.append(vim.inspect(session.capabilities))
|
||||
elseif vim.tbl_contains(M.commands.into, text) then
|
||||
require('dap').step_into()
|
||||
|
@ -269,24 +280,48 @@ function execute(text)
|
|||
elseif vim.tbl_contains(M.commands.out, text) then
|
||||
require('dap').step_out()
|
||||
elseif vim.tbl_contains(M.commands.up, text) then
|
||||
if not session then
|
||||
M.append('No active debug session')
|
||||
return
|
||||
end
|
||||
session:_frame_delta(1)
|
||||
M.print_stackframes()
|
||||
elseif vim.tbl_contains(M.commands.step_back, text) then
|
||||
require('dap').step_back()
|
||||
elseif vim.tbl_contains(M.commands.pause, text) then
|
||||
if not session then
|
||||
M.append('No active debug session')
|
||||
return
|
||||
end
|
||||
session:_pause()
|
||||
elseif vim.tbl_contains(M.commands.reverse_continue, text) then
|
||||
require('dap').reverse_continue()
|
||||
elseif vim.tbl_contains(M.commands.down, text) then
|
||||
if not session then
|
||||
M.append('No active debug session')
|
||||
return
|
||||
end
|
||||
session:_frame_delta(-1)
|
||||
M.print_stackframes()
|
||||
elseif vim.tbl_contains(M.commands.goto_, splitted_text[1]) then
|
||||
if not session then
|
||||
M.append('No active debug session')
|
||||
return
|
||||
end
|
||||
if splitted_text[2] then
|
||||
session:_goto(tonumber(splitted_text[2]))
|
||||
end
|
||||
elseif vim.tbl_contains(M.commands.scopes, text) then
|
||||
if not session then
|
||||
M.append('No active debug session')
|
||||
return
|
||||
end
|
||||
print_scopes(session.current_frame)
|
||||
elseif vim.tbl_contains(M.commands.threads, text) then
|
||||
if not session then
|
||||
M.append('No active debug session')
|
||||
return
|
||||
end
|
||||
print_threads(vim.tbl_values(session.threads))
|
||||
elseif vim.tbl_contains(M.commands.frames, text) then
|
||||
M.print_stackframes()
|
||||
|
@ -294,14 +329,26 @@ function execute(text)
|
|||
local command = splitted_text[1]
|
||||
local args = string.sub(text, string.len(command)+2)
|
||||
M.commands.custom_commands[command](args)
|
||||
else
|
||||
elseif session then
|
||||
session:evaluate(text, evaluate_handler)
|
||||
else
|
||||
local dap = require("dap")
|
||||
local interpreter = dap.providers.interpreters[opts.filetype or "lua"]
|
||||
if interpreter then
|
||||
local result = interpreter.evaluate(text)
|
||||
evaluate_handler(nil, result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---@class dap.repl.execute.opts
|
||||
---@field filetype string?
|
||||
|
||||
|
||||
--- Add and execute text as if entered directly
|
||||
function M.execute(text)
|
||||
---@param opts dap.repl.execute.opts?
|
||||
function M.execute(text, opts)
|
||||
M.append("dap> " .. text .. "\n", "$", { newline = true })
|
||||
local numlines = api.nvim_buf_line_count(repl.buf)
|
||||
if repl.win and api.nvim_win_is_valid(repl.win) then
|
||||
|
@ -310,7 +357,7 @@ function M.execute(text)
|
|||
vim.cmd.normal({"zt", bang = true })
|
||||
end)
|
||||
end
|
||||
execute(text)
|
||||
execute(text, opts)
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -535,7 +535,7 @@ end
|
|||
local function jump_to_frame(session, frame, preserve_focus_hint, stopped)
|
||||
local source = frame.source
|
||||
if not source then
|
||||
utils.notify('Source not available, cannot jump to frame', vim.log.levels.INFO)
|
||||
utils.notify('Source missing, cannot jump to frame: ' .. frame.name, vim.log.levels.INFO)
|
||||
return
|
||||
end
|
||||
vim.fn.sign_unplace(session.sign_group)
|
||||
|
@ -544,7 +544,7 @@ local function jump_to_frame(session, frame, preserve_focus_hint, stopped)
|
|||
end
|
||||
local bufnr = frame_to_bufnr(session, frame)
|
||||
if not bufnr then
|
||||
utils.notify('Source not available, cannot jump to frame', vim.log.levels.INFO)
|
||||
utils.notify('Source missing, cannot jump to frame: ' .. frame.name, vim.log.levels.INFO)
|
||||
return
|
||||
end
|
||||
vim.fn.bufload(bufnr)
|
||||
|
|
|
@ -652,7 +652,7 @@ function M.sidebar(widget, winopts, wincmd)
|
|||
end
|
||||
|
||||
|
||||
---@param session Session
|
||||
---@param session dap.Session
|
||||
---@param expr string
|
||||
---@param max_level integer
|
||||
local function get_var_lines(session, expr, max_level)
|
||||
|
|
|
@ -154,7 +154,7 @@ if api.nvim_create_autocmd then
|
|||
vim.bo[args.buf].modified = false
|
||||
local repl = require("dap.repl")
|
||||
local lines = api.nvim_buf_get_lines(args.buf, 0, -1, true)
|
||||
repl.execute(table.concat(lines, "\n"))
|
||||
repl.execute(table.concat(lines, "\n"), { filetype = ft })
|
||||
repl.open()
|
||||
end,
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue