Add config providers; always load .vscode/launch.json

- Introduces a dap.providers.configs table which plugins can extend to
  add additional sources for dynamic configuration generation

- Implements the two built-in configuration sources as providers

- `.vscode/launch.json` files are now loaded automatically by one of
  these providers. The entries from the file always display even if the
  type doesn't match the current filetype. This gets rid of the
  `type_to_filetypes` mapping requirements.
This commit is contained in:
Mathias Fussenegger 2024-05-30 13:13:13 +02:00 committed by Mathias Fußenegger
parent 5e2895a8d2
commit 54f891ae4c
3 changed files with 155 additions and 33 deletions

View file

@ -1178,10 +1178,18 @@ You could also customize the buffer and window creation using a low-level builde
.build()
<
==============================================================================
EXTENSIONS API *dap-extensions*
nvim-dap provides extension points for plugins:
- |dap-listeners|
- |dap-providers|
==============================================================================
LISTENERS EXTENSIONS API *dap-listeners*
nvim-dap supports subscribing and listening to all responses or events that a
debug adapter might send to nvim-dap.
@ -1232,6 +1240,59 @@ For events, the listeners are called with two arguments:
1. The session
2. The event payload
==============================================================================
PROVIDERS EXTENSIONS API *dap-providers*
==============================================================================
CONFIG PROVIDERS EXTENSIONS API *dap-providers-configs*
If a user starts a debug session via |dap.continue()|, nvim-dap looks for
a suitable configuration (|dap-configuration|) to use.
To do so it uses so called configuration providers registered in a
`dap.providers.configs` table.
Plugins can extend this table with additional config providers.
There are two providers built-in:
- `dap.global` - looks for configuration entries in
`dap.configurations.<filetype>`
- `dap.launch.json` - looks for configuration entries in a
`.vscode/launch.json` file.
The key for the table is a `plugin-id`. Plugins should use their plugin name.
Do _not_ use the `dap.` namespace. It is reserved for nvim-dap itself.
The value for the table is a function that takes a buffer number as parameter
and must return |dap-configuration| entries as list.
An example:
>lua
local dap = require("dap")
dap.providers.configs["mydummy_provider"] = function(bufnr)
return {
{
name = "This config always shows up",
type = "gdb",
request = "launch",
program = "/usr/bin/zig",
args = {"run", "${file}"},
cwd = "${workspaceFolder}",
},
}
end
<
To support async operations, the config providers functions are called
within a coroutine and they can also return a `thread` which must be suspended
and which will be resumed at most once and must then yield the configurations
as list.
==============================================================================
UTILS API *dap-utils*

View file

@ -238,6 +238,7 @@ M.adapters = {}
---@field request "launch"|"attach"
---@field name string
--- Configurations per adapter. See `:help dap-configuration` for more help.
---
--- An example:
@ -255,6 +256,40 @@ M.adapters = {}
---@type table<string, Configuration[]>
M.configurations = {}
local providers_mt = {
__newindex = function()
error("Cannot add item to dap.providers")
end,
}
M.providers = setmetatable({
---@type table<string, fun(bufnr: integer): thread|Configuration[]>
configs = {
},
}, providers_mt)
M.providers.configs["dap.global"] = function(bufnr)
local filetype = vim.bo[bufnr].filetype
local configurations = M.configurations[filetype] or {}
assert(
islist(configurations),
string.format(
'`dap.configurations.%s` must be a list of configurations, got %s',
filetype,
vim.inspect(configurations)
)
)
return configurations
end
M.providers.configs["dap.launch.json"] = function()
local ok, configs = pcall(require("dap.ext.vscode").getconfigs)
return ok and configs or {}
end
local signs = {
DapBreakpoint = { text = "B", texthl = "", linehl = "", numhl = "" },
DapBreakpointCondition = { text = "C", texthl = "", linehl = "", numhl = "" },
@ -421,35 +456,55 @@ end
local function select_config_and_run(opts)
local filetype = vim.bo.filetype
local configurations = M.configurations[filetype] or {}
assert(
islist(configurations),
string.format(
'`dap.configurations.%s` must be a list of configurations, got %s',
filetype,
vim.inspect(configurations)
)
)
if #configurations == 0 then
local msg = 'No configuration found for `%s`. You need to add configs to `dap.configurations.%s` (See `:h dap-configuration`)'
notify(string.format(msg, filetype, filetype), vim.log.levels.INFO)
return
end
opts = opts or {}
opts.filetype = opts.filetype or filetype
lazy.ui.pick_if_many(
configurations,
"Configuration: ",
function(i) return i.name end,
function(configuration)
if configuration then
M.run(configuration, opts)
local bufnr = api.nvim_get_current_buf()
local filetype = vim.bo[bufnr].filetype
lazy.async.run(function()
local all_configs = {}
local co = coroutine.running()
local providers = vim.tbl_keys(M.providers.configs)
table.sort(providers)
for _, provider in ipairs(providers) do
local config_provider = M.providers.configs[provider]
local configs = config_provider(bufnr)
if type(configs) == "thread" then
assert(
coroutine.status(configs) == "suspended",
"If configs provider returns a thread it must be suspended"
)
vim.schedule(function()
coroutine.resume(configs, co)
end)
configs = coroutine.yield()
end
if islist(configs) then
vim.list_extend(all_configs, configs)
else
notify('No configuration selected', vim.log.levels.INFO)
local msg = "Configuration provider %s must return a list of configurations. Got: %s"
notify(msg:format(provider, vim.inspect(configs)), vim.log.levels.WARN)
end
end
)
if #all_configs == 0 then
local msg = 'No configuration found for `%s`. You need to add configs to `dap.configurations.%s` (See `:h dap-configuration`)'
notify(string.format(msg, filetype, filetype), vim.log.levels.INFO)
return
end
opts = opts or {}
opts.filetype = opts.filetype or filetype
lazy.ui.pick_if_many(
all_configs,
"Configuration: ",
function(i) return i.name end,
function(configuration)
if configuration then
M.run(configuration, opts)
else
notify('No configuration selected', vim.log.levels.INFO)
end
end
)
end)
end

View file

@ -174,13 +174,12 @@ function M._load_json(jsonstr)
return configs
end
--- Extends dap.configurations with entries read from .vscode/launch.json
function M.load_launchjs(path, type_to_filetypes)
type_to_filetypes = vim.tbl_extend('keep', type_to_filetypes or {}, M.type_to_filetypes)
---@param path string?
---@return Configuration[]
function M.getconfigs(path)
local resolved_path = path or (vim.fn.getcwd() .. '/.vscode/launch.json')
if not vim.loop.fs_stat(resolved_path) then
return
return {}
end
local lines = {}
for line in io.lines(resolved_path) do
@ -189,7 +188,14 @@ function M.load_launchjs(path, type_to_filetypes)
end
end
local contents = table.concat(lines, '\n')
local configurations = M._load_json(contents)
return M._load_json(contents)
end
--- Extends dap.configurations with entries read from .vscode/launch.json
function M.load_launchjs(path, type_to_filetypes)
type_to_filetypes = vim.tbl_extend('keep', type_to_filetypes or {}, M.type_to_filetypes)
local configurations = M.getconfigs(path)
assert(configurations, "launch.json must have a 'configurations' key")
for _, config in ipairs(configurations) do