Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions lua/dap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,13 @@ providers.configs["dap.launch.json"] = function()
return ok and configs or {}
end

providers.configs["dap.neoconf"] = function(bufnr)
local ok, configs = pcall(function()
return require("dap.ext.neoconf").getconfigs(bufnr)
end)
return ok and configs or {}
end

do
local function eval_option(option)
if type(option) == 'function' then
Expand Down Expand Up @@ -1209,5 +1216,6 @@ api.nvim_create_autocmd("ExitPre", {
end
})

require("dap.ext.neoconf").register()

return M
146 changes: 146 additions & 0 deletions lua/dap/ext/neoconf.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
local ext_utils = require('dap.ext.utils')
local M = {}

---@param bufnr integer
---@return dap.Configuration[]
function M.getconfigs(bufnr)
local ok, neoconf = pcall(require, "neoconf")
if not ok then
return {}
end

local data = neoconf.get("dap", nil, { buffer = bufnr }) or {}
return ext_utils.load_configs(data)
end

function M.register()
if not pcall(require, "neoconf") then
return
end

require("neoconf.plugins").register({
name = "dap",
on_schema = function(schema)
schema:set("dap", {
description = "nvim-dap settings",
type = "object",
properties = {
configurations = {
description = "List of debug configurations",
type = "array",
items = {
type = "object",
properties = {
-- required properties
name = {
description = "A user-readable name for the configuration",
type = "string",
},
type = {
description = "Which debug adapter to use",
type = "string",
},
request = {
description = "Indicates whether the debug adapter should launch a debugee or attach to one that is already running",
type = "string",
enum = { "launch", "attach" },
},
-- extra properties, supported by many debuggers
-- https://code.visualstudio.com/docs/editor/debugging#_launchjson-attributes
-- types are omitted to be safe
program = {
description = "Path to the program to be debugged",
},
args = {
description = "Arguments passed to the program to debug",
},
env = {
description = "Environment variables to set for the program being debugged",
},
envFile = {
description = "Path to a file containing environment variable definitions",
},
cwd = {
description = "The working directory of the program being debugged",
},
host = {
description = "Host to use when attaching to a running process",
},
port = {
description = "Port to use when attaching to a running process",
},
stopOnEntry = {
description = "Break immediately when the program launches",
},
console = {
description = "What kind of console to use, valid values are usually `internalConsole`, `integratedTerminal` and `externalTerminal`",
},
},
required = { "name", "type", "request" },
},
},
inputs = {
description = "List of custom input prompts",
type = "array",
items = {
type = "object",
properties = {
id = {
description = "A unique identifier for the input",
type = "string",
},
type = {
description = "The input type - `pickString` to choose from a list of options, or `promptString` to input arbitrary text",
type = "string",
enum = { "pickString", "promptString" },
},
description = {
description = "Descriptive text shown to the user",
type = "string",
},
default = {
description = "The default value for the input",
type = "string",
default = "",
},
},
["if"] = {
properties = {
type = {
const = "pickString",
},
},
},
["then"] = {
properties = {
options = {
description = "The list of options shown to the user",
type = "array",
items = {
type = { "string", "object" },
properties = {
label = {
description = "The label shown for the option",
type = "string",
},
value = {
description = "The value of the option",
type = "string",
},
},
required = { "label", "value" },
},
},
},
required = { "options" },
},
},
required = { "id", "type" },
},
},
})
end,
})
end

return M
174 changes: 174 additions & 0 deletions lua/dap/ext/utils.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
local notify = require('dap.utils').notify
local M = {}


---@class dap.ext.utils.Input
---@field id string
---@field type "promptString"|"pickString"
---@field description string
---@field default? string
---@field options string[]|{label: string, value: string}[]


---@param input dap.ext.utils.Input
---@return function
local function create_input(input)
if input.type == "promptString" then
return function()
local description = input.description or 'Input'
if not vim.endswith(description, ': ') then
description = description .. ': '
end
if vim.ui.input then
local co = coroutine.running()
local opts = {
prompt = description,
default = input.default or '',
}
vim.ui.input(opts, function(result)
vim.schedule(function()
coroutine.resume(co, result)
end)
end)
return coroutine.yield()
else
return vim.fn.input(description, input.default or '')
end
end
elseif input.type == "pickString" then
return function()
local options = assert(input.options, "input of type pickString must have an `options` property")
local opts = {
prompt = input.description,
format_item = function(x)
return x.label and x.label or x
end,
}
local co = coroutine.running()
vim.ui.select(options, opts, function(option)
vim.schedule(function()
local value = option and option.value or option
coroutine.resume(co, value or (input.default or ''))
end)
end)
return coroutine.yield()
end
else
local msg = "Unsupported input type: " .. input.type
notify(msg, vim.log.levels.WARN)
return function()
return "${input:" .. input.id .. "}"
end
end
end


---@param inputs dap.ext.utils.Input[]
---@return table<string, function> inputs map from ${input:<id>} to function resolving the input value
local function create_inputs(inputs)
local result = {}
for _, input in ipairs(inputs) do
local id = assert(input.id, "input must have a `id`")
local key = "${input:" .. id .. "}"
assert(input.type, "input must have a `type`")
local fn = create_input(input)
if fn then
result[key] = fn
end
end
return result
end


---@param inputs table<string, function>
---@param value any
---@param cache table<string, any>
local function apply_input(inputs, value, cache)
if type(value) == "table" then
local new_value = {}
for k, v in pairs(value) do
new_value[k] = apply_input(inputs, v, cache)
end
value = new_value
end
if type(value) ~= "string" then
return value
end

local matches = string.gmatch(value, "${input:([%w_]+)}")
for input_id in matches do
local input_key = "${input:" .. input_id .. "}"
local result = cache[input_key]
if not result then
local input = inputs[input_key]
if not input then
local msg = "No input with id `" .. input_id .. "` found in inputs"
notify(msg, vim.log.levels.WARN)
else
result = input()
cache[input_key] = result
end
end
if result then
value = value:gsub(input_key, result)
end
end
return value
end


---@param config table<string, any>
---@param inputs table<string, function>
local function apply_inputs(config, inputs)
local result = {}
local cache = {}
for key, value in pairs(config) do
result[key] = apply_input(inputs, value, cache)
end
return result
end


--- Lift properties of a child table to top-level
local function lift(tbl, key)
local child = tbl[key]
if child then
tbl[key] = nil
return vim.tbl_extend('force', tbl, child)
end
return tbl
end


---@param data table<string, any>
---@return dap.Configuration[]
function M.load_configs(data)
local inputs = create_inputs(data.inputs or {})
local has_inputs = next(inputs) ~= nil

local sysname
if vim.fn.has('linux') == 1 then
sysname = 'linux'
elseif vim.fn.has('mac') == 1 then
sysname = 'osx'
elseif vim.fn.has('win32') == 1 then
sysname = 'windows'
end

local configs = {}
for _, config in ipairs(data.configurations or {}) do
config = lift(config, sysname)
if (has_inputs) then
config = setmetatable(config, {
__call = function()
local c = vim.deepcopy(config)
return apply_inputs(c, inputs)
end
})
end
table.insert(configs, config)
end
return configs
end

return M
Loading