Skip to content

Commit

Permalink
Add startDebugging support
Browse files Browse the repository at this point in the history
  • Loading branch information
mfussenegger committed Feb 17, 2023
1 parent 82e98f3 commit 39bc5ae
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 23 deletions.
27 changes: 19 additions & 8 deletions lua/dap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,17 @@ function M.set_breakpoint(condition, hit_condition, log_message)
M.toggle_breakpoint(condition, hit_condition, log_message, true)
end


---@param lsessions table<integer, Session>
---@param fn fun(lsession: Session)
local function broadcast(lsessions, fn)
for _, lsession in pairs(lsessions) do
fn(lsession)
broadcast(lsession.children, fn)
end
end


function M.toggle_breakpoint(condition, hit_condition, log_message, replace_old)
lazy.breakpoints.toggle({
condition = condition,
Expand All @@ -753,11 +764,9 @@ function M.toggle_breakpoint(condition, hit_condition, log_message, replace_old)
})
local bufnr = api.nvim_get_current_buf()
local bps = lazy.breakpoints.get(bufnr)
for _, lsession in pairs(sessions) do
if lsession.initialized then
lsession:set_breakpoints(bps)
end
end
broadcast(sessions, function(s)
s:set_breakpoints(bps)
end)
if vim.fn.getqflist({context = DAP_QUICKFIX_CONTEXT}).context == DAP_QUICKFIX_CONTEXT then
M.list_breakpoints(false)
end
Expand All @@ -770,9 +779,9 @@ function M.clear_breakpoints()
bps[bufnr] = {}
end
lazy.breakpoints.clear()
for _, lsession in pairs(sessions) do
broadcast(sessions, function(lsession)
lsession:set_breakpoints(bps)
end
end)
end


Expand Down Expand Up @@ -1023,7 +1032,9 @@ end
---@param new_session Session|nil
function M.set_session(new_session)
if new_session then
sessions[new_session.id] = new_session
if new_session.parent == nil then
sessions[new_session.id] = new_session
end
session = new_session
else
local _, lsession = next(sessions)
Expand Down
1 change: 1 addition & 0 deletions lua/dap/entity.lua
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ function threads_spec.compute_actions(info)
fn = function()
if session.stopped_thread_id == thread.id then
session:_step('continue')
context.refresh()
else
thread.stopped = false
session:request('continue', { threadId = thread.id }, function(err)
Expand Down
5 changes: 5 additions & 0 deletions lua/dap/protocol.lua
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,8 @@
---@class dap.ProgressEndEvent
---@field progressId string
---@field message? string


---@class dap.StartDebuggingRequestArguments
---@field configuration table<string, any>
---@field request 'launch'|'attach'
86 changes: 77 additions & 9 deletions lua/dap/session.lua
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ end
---@field sign_group string
---@field closed boolean
---@field on_close table<string, fun(session: Session)> Handler per plugin-id. Invoked when a session closes (due to terminated event, disconnect or error cases like initialize errors, debug adapter process exit, ...). Session is assumed non-functional at this point and handler can be invoked within luv event loop (not API safe, may require vim.schedule)
---@field children table<number, Session>
---@field parent Session|nil


---@class Client
Expand Down Expand Up @@ -89,6 +91,22 @@ local function defaults(session)
return dap().defaults[session.config.type]
end

local function co_resume_schedule(co)
return function(...)
local args = {...}
vim.schedule(function()
coroutine.resume(co, unpack(args))
end)
end
end


local function co_resume(co)
return function(...)
coroutine.resume(co, ...)
end
end


local function signal_err(err, cb)
if err then
Expand Down Expand Up @@ -489,9 +507,7 @@ local function frame_to_bufnr(session, frame)
end
local co = coroutine.running()
assert(co, 'Must run in coroutine')
session:source(source, function(err, bufnr)
coroutine.resume(co, err, bufnr)
end)
session:source(source, co_resume(co))
local _, bufnr = coroutine.yield()
return bufnr
end
Expand Down Expand Up @@ -897,7 +913,7 @@ end

function Session:handle_body(body)
local decoded = json_decode(body)
log.debug(decoded)
log.debug(self.id, decoded)
local listeners = dap().listeners
if decoded.request_seq then
local callback = self.message_callbacks[decoded.request_seq]
Expand Down Expand Up @@ -963,8 +979,60 @@ function Session:handle_body(body)
end


---@param self Session
local function start_debugging(self, request)
local body = request.arguments --[[@as dap.StartDebuggingRequestArguments]]
coroutine.wrap(function()
local co = coroutine.running()
local opts = {
filetype = self.filetype
}
local config = body.configuration
local adapter = dap().adapters[config.type or self.config.type]
config.request = body.request

if type(adapter) == "function" then
adapter(co_resume_schedule(co), config)
adapter = coroutine.yield()
end

local expected_types = {"executable", "server"}
if type(adapter) ~= "table" or not vim.tbl_contains(expected_types, adapter.type) then
local msg = "Invalid adapter definition. Expected a table with type `executable` or `server`: "
utils.notify(msg .. vim.inspect(adapter), vim.log.levels.ERROR)
return
end

if adapter.type == "executable" then
local session = Session:spawn(adapter, opts)
self.children[session.id] = session
session:initialize(config)
self:response(request, {success = true})
elseif adapter.type == "server" then
local session
session = Session:connect(adapter, opts, function(err)
if err then
utils.notify(string.format(
"Could not connect startDebugging child session %s:%s: %s",
adapter.host or '127.0.0.1',
adapter.port,
err
), vim.log.levels.WARN)
elseif session then
session.parent = self
self.children[session.id] = session
session:initialize(config)
self:response(request, {success = true})
end
end)
end
end)()
end


local default_reverse_request_handlers = {
runInTerminal = run_in_terminal
runInTerminal = run_in_terminal,
startDebugging = start_debugging,
}

local next_session_id = 1
Expand Down Expand Up @@ -997,6 +1065,7 @@ local function new_session(adapter, opts)
sign_group = 'dap-' .. tostring(ns),
closed = false,
on_close = {},
children = {},
}
next_session_id = next_session_id + 1
return setmetatable(state, { __index = Session })
Expand Down Expand Up @@ -1480,9 +1549,7 @@ function Session:request(command, arguments, callback)
if not callback then
co = coroutine.running()
if co then
callback = function(err, result)
coroutine.resume(co, err, result)
end
callback = co_resume(co)
else
-- Assume missing callback is intentional.
-- Prevent error logging in Session:handle_body
Expand Down Expand Up @@ -1525,7 +1592,8 @@ function Session:initialize(config)
linesStartAt1 = true;
supportsRunInTerminalRequest = true;
supportsVariableType = true;
supportsProgressReporting = true;
supportsProgressReporting = true,
supportsStartDebuggingRequest = true,
locale = os.getenv('LANG') or 'en_US';
}, function(err0, result)
if err0 then
Expand Down
38 changes: 32 additions & 6 deletions lua/dap/ui/widgets.lua
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,12 @@ M.frames = {


M.sessions = {
refresh_listener = {'event_initialized', 'event_terminated', 'disconnected'},
refresh_listener = {
'event_initialized',
'event_terminated',
'disconnect',
'event_stopped'
},
new_buf = function()
local buf = new_buf()
api.nvim_buf_set_name(buf, 'dap-sessions-' .. tostring(buf))
Expand All @@ -262,6 +267,18 @@ M.sessions = {
local dap = require('dap')
local sessions = dap.sessions()
local layer = view.layer()
local lsessions = {}

local add
add = function(s)
table.insert(lsessions, s)
for _, child in pairs(s.children) do
add(child)
end
end
for _, s in pairs(sessions) do
add(s)
end
local context = {}
context.actions = {
{
Expand All @@ -277,16 +294,25 @@ M.sessions = {
end
}
}
local focused = dap.session()
local render_session = function(s)
local focused = dap.session()
local text = s.id .. ': ' .. s.config.name
if s.id == focused.id then
return '' .. text
local parent = s.parent
local num_parents = 0
while parent ~= nil do
parent = parent.parent
num_parents = num_parents + 1
end
local prefix
if focused and s.id == focused.id then
prefix = ""
else
return ' ' .. text
prefix = " "
end
return prefix .. string.rep(" ", num_parents) .. text
end
layer.render(vim.tbl_values(sessions), render_session, context)
layer.render({}, tostring, nil, 0, -1)
layer.render(lsessions, render_session, context)
end,
}

Expand Down
14 changes: 14 additions & 0 deletions tests/server.lua
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ function Client:send_event(event, body)
end


---@param command string
---@param arguments any
function Client:send_request(command, arguments)
self.seq = self.seq + 1
local payload = {
seq = self.seq,
type = "request",
command = command,
arguments = arguments,
}
self.socket:write(rpc.msg_with_content_length(json_encode(payload)))
end


function Client:handle_input(body)
local request = json_decode(body)
table.insert(self.spy.requests, request)
Expand Down
22 changes: 22 additions & 0 deletions tests/sessions_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,26 @@ describe('sessions', function()
wait(function() return #dap.sessions() == 0 end, function() return dap.sessions() end)
assert.are.same(nil, dap.session())
end)

it("startDebugging starts a child session", function()
local conf1 = {
type = 'dummy1',
request = 'launch',
name = 'Launch file 1',
}
run_and_wait_until_initialized(conf1, srv1)
srv1.client:send_request("startDebugging", {
request = "laucnh",
configuration = {
type = "dummy2",
name = "Subprocess"
}
})
wait(
function() return vim.tbl_count(dap.session().children) == 1 end,
function() return dap.session() end
)
local _, child = next(dap.session().children)
assert.are.same("Subprocess", child.config.name)
end)
end)

0 comments on commit 39bc5ae

Please sign in to comment.