From b28db9dafd2f20c44ecc839daba09ed16988da07 Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Tue, 24 Dec 2024 14:20:26 +0100 Subject: [PATCH] Allow listeners to return `true` to remove the listener --- doc/dap.txt | 3 ++ lua/dap.lua | 119 ++++++++++++++++++++++--------------------- lua/dap/protocol.lua | 1 + lua/dap/session.lua | 68 ++++++++++++------------- 4 files changed, 98 insertions(+), 93 deletions(-) diff --git a/doc/dap.txt b/doc/dap.txt index 062fcb9a..8e5a88fe 100644 --- a/doc/dap.txt +++ b/doc/dap.txt @@ -1307,6 +1307,9 @@ For events, the listeners are called with two arguments: 2. The event payload +Both command and event listeners can return a boolean `true` to remove the +registered listener. + ============================================================================== BUILT-IN CLIENT EVENTS *dap-listeners-ext* diff --git a/lua/dap.lua b/lua/dap.lua index 392cc893..b18f79d7 100644 --- a/lua/dap.lua +++ b/lua/dap.lua @@ -56,66 +56,69 @@ M.repl = setmetatable({}, { end }) +---@alias dap.RequestListener fun(session: dap.Session, err: dap.ErrorResponse?, response: T, args: U, seq: number):boolean? + +---@alias dap.EventListener fun(session: dap.Session, body: T):boolean? ---@class dap.listeners ----@field event_breakpoint table ----@field event_capabilities table ----@field event_continued table ----@field event_exited table ----@field event_initialized table ----@field event_invalidated table ----@field event_loadedSource table ----@field event_memory table ----@field event_module table ----@field event_output table ----@field event_process table ----@field event_progressEnd table ----@field event_progressStart table ----@field event_progressUpdate table ----@field event_stopped table ----@field event_terminated table ----@field event_thread table ----@field attach table ----@field breakpointLocations table ----@field completions table ----@field configurationDone table ----@field continue table ----@field dataBreakpointInfo table ----@field disassemble table ----@field disconnect table ----@field evaluate table ----@field exceptionInfo table ----@field goto table ----@field gotoTargets table ----@field initialize table ----@field launch table ----@field loadedSources table ----@field modules table ----@field next table ----@field pause table ----@field readMemory table ----@field restart table ----@field restartFrame table ----@field reverseContinue table ----@field scopes table ----@field setBreakpoints table ----@field setDataBreakpoints table ----@field setExceptionBreakpoints table ----@field setExpression table ----@field setFunctionBreakpoints table ----@field setInstructionBreakpoints table ----@field setVariable table ----@field source table ----@field stackTrace table ----@field stepBack table ----@field stepIn table ----@field stepInTargets table ----@field stepOut table ----@field terminate table ----@field terminateThreads table ----@field threads table ----@field variables table ----@field writeMemory table +---@field event_breakpoint table> +---@field event_capabilities table> +---@field event_continued table> +---@field event_exited table> +---@field event_initialized table> +---@field event_invalidated table> +---@field event_loadedSource table> +---@field event_memory table> +---@field event_module table> +---@field event_output table> +---@field event_process table> +---@field event_progressEnd table> +---@field event_progressStart table> +---@field event_progressUpdate table> +---@field event_stopped table> +---@field event_terminated table> +---@field event_thread table> +---@field attach table +---@field breakpointLocations table +---@field completions table> +---@field configurationDone table +---@field continue table +---@field dataBreakpointInfo table +---@field disassemble table +---@field disconnect table> +---@field evaluate table> +---@field exceptionInfo table +---@field goto table +---@field gotoTargets table +---@field initialize table +---@field launch table +---@field loadedSources table +---@field modules table +---@field next table +---@field pause table +---@field readMemory table +---@field restart table +---@field restartFrame table +---@field reverseContinue table +---@field scopes table +---@field setBreakpoints table +---@field setDataBreakpoints table +---@field setExceptionBreakpoints table +---@field setExpression table +---@field setFunctionBreakpoints table +---@field setInstructionBreakpoints table +---@field setVariable table +---@field source table +---@field stackTrace table +---@field stepBack table +---@field stepIn table +---@field stepInTargets table +---@field stepOut table +---@field terminate table +---@field terminateThreads table +---@field threads table +---@field variables table> +---@field writeMemory table M.listeners = { diff --git a/lua/dap/protocol.lua b/lua/dap/protocol.lua index fa2b2724..a7b49d6d 100644 --- a/lua/dap/protocol.lua +++ b/lua/dap/protocol.lua @@ -297,6 +297,7 @@ ---@field instructionReference? string ---@field offset? number +---@class dap.InitializedEvent ---@class dap.StoppedEvent ---@field reason "step"|"breakpoint"|"exception"|"pause"|"entry"|"goto"|"function breakpoint"|"data breakpoint"|"instruction breakpoint"|string; diff --git a/lua/dap/session.lua b/lua/dap/session.lua index 3b9a242f..67ff8727 100644 --- a/lua/dap/session.lua +++ b/lua/dap/session.lua @@ -1048,6 +1048,17 @@ function Session:set_exception_breakpoints(filters, exceptionOptions, on_done) end +---@param listeners table|dap.EventListener> +local function process_listener(listeners, ...) + for key, listener in pairs(listeners) do + local remove = listener(...) + if remove then + listeners[key] = nil + end + end +end + + function Session:handle_body(body) local decoded = assert(json_decode(body), "Debug adapter must send JSON objects") log:debug(self.id, decoded) @@ -1061,49 +1072,36 @@ function Session:handle_body(body) log:error('No callback found. Did the debug adapter send duplicate responses?', decoded) return end + local err = nil + local response = nil if decoded.success then - vim.schedule(function() - for _, c in pairs(listeners.before[decoded.command]) do - c(self, nil, decoded.body, request, decoded.request_seq) - end - callback(nil, decoded.body, decoded.request_seq) - for _, c in pairs(listeners.after[decoded.command]) do - c(self, nil, decoded.body, request, decoded.request_seq) - end - end) + response = decoded.body else - vim.schedule(function() - local err = { - message = decoded.message, - body = decoded.body, - } - setmetatable(err, err_mt) - for _, c in pairs(listeners.before[decoded.command]) do - c(self, err, nil, request, decoded.request_seq) - end - callback(err, nil, decoded.request_seq) - for _, c in pairs(listeners.after[decoded.command]) do - c(self, err, nil, request, decoded.request_seq) - end - end) + err = { + message = decoded.message, + body = decoded.body, + } + setmetatable(err, err_mt) end + vim.schedule(function() + local before = listeners.before[decoded.command] + process_listener(before, self, err, response, request, decoded.request_seq) + callback(err, decoded.body, decoded.request_seq) + local after = listeners.after[decoded.command] + process_listener(after, self, err, response, request, decoded.request_seq) + end) elseif decoded.event then - local callback = self['event_' .. decoded.event] + local callback_name = "event_" .. decoded.event + local callback = self[callback_name] vim.schedule(function() - local event_handled = false - for _, c in pairs(listeners.before['event_' .. decoded.event]) do - event_handled = true - c(self, decoded.body) - end + local before = listeners.before[callback_name] + process_listener(before, self, decoded.body) if callback then - event_handled = true callback(self, decoded.body) end - for _, c in pairs(listeners.after['event_' .. decoded.event]) do - event_handled = true - c(self, decoded.body) - end - if not event_handled then + local after = listeners.after[callback_name] + process_listener(after, self, decoded.body) + if not callback and not next(before) and not next(after) then log:warn('No event handler for ', decoded) end end)