mfussenegger / nvim-dap

Debug Adapter Protocol client implementation for Neovim
GNU General Public License v3.0
5.46k stars 194 forks source link

Won't terminate after the first encountered breakpoint on vscode-js-debug #1166

Open foxt451 opened 7 months ago

foxt451 commented 7 months ago

Debug adapter definition and debug configuration

{ "microsoft/vscode-js-debug", build = "npm install --legacy-peer-deps && npx gulp dapDebugServer && mv dist out", }

{ "mfussenegger/nvim-dap", dependencies = { "microsoft/vscode-js-debug", }, config = function() require("dap").adapters["pwa-node"] = { type = "server", host = "localhost", port = "${port}", executable = { command = "node", args = { vim.call("stdpath", "data") .. "/lazy/vscode-js-debug/out/src/dapDebugServer.js", "${port}", }, } } for _, language in ipairs({ "typescript", "javascript" }) do require("dap").configurations[language] = { { type = "pwa-node", name = "Node", request = "launch", program = "${file}", cwd = "${workspaceFolder}", console = "integratedTerminal", skipFiles = { "/", "${workspaceFolder}/node_modules/" }, }, } end

Debug adapter version

vscode-js-debug - latest commit 1e5bf60; nvim-dap - too

Steps to Reproduce

  1. Open this code:
    console.log('Hello, world!');
    await new Promise((resolve) => setTimeout(resolve, 5000));
    console.log('Hello, world!'); // <---------------------BREAKPOINT HERE
    await new Promise((resolve) => setTimeout(resolve, 5000));
    console.log('Hello, world!'); 
  2. Try to terminate during first await - the process exits
  3. Then restart, stop on first breakpoint, continue, and terminate on the second await - nothing happens

Expected Result

Process terminates successfully during the second await too

Actual Result

Nothing happens when terminating during the second await

foxt451 commented 7 months ago

I've delved a bit more into it and I can actually terminate the debuggee after the first breakpoint with firing 3 terminate commands (and in case of the second breakpoint too). I don't think this is expected... The terminate requests have to occur with some delay in between

Btw, node version doesn't seem to matter

mfussenegger commented 7 months ago

vscode-js-debug starts multiple sub-sessions and terminate currently only terminates the active session. The multi-session feature is unfortunately a bit underspecified in the spec. E.g. debugpy uses it too for python multiprocessing, and there I haven't noticed that it's necessary to terminate multiple times. But I think it might be reasonable to broadcast a terminate to all children of a session.

You can see the sessions using something like:

:lua local w = require("dap.ui.widgets"); w.sidebar(w.sessions, {}, '5 sp').toggle()
foxt451 commented 7 months ago

Thanks! Do you imagine some clean way of terminating all the children as well? I tried this:

      {
        "<leader>dt",
        function()
          require("dap").terminate()
          local all_sessions = require("dap").sessions()
          for _, session in ipairs(all_sessions) do
            session:disconnect({
              terminateDebuggee = true
            })
          end
        end,
        desc = "Terminate"
      },

(and without require("dap").terminate() and with the order swapped) but when I start the previous debug configuration again, it will either not react to dt at all, or disconnect without temrinating debugee, or the terminal is empty, or it doesn't continue after the breakpoint, depending on the order of the statements

maxbol commented 4 months ago

+1 for this experience not being very good, whether or not it is in the scope of nvim-dap to fix it...

maxbol commented 4 months ago

Interestingly, I was experience the same "three strikes" behavior as you did @foxt451 , but if I open up the sessions list as described by @mfussenegger and then selecting the top level session before running terminate(), the debugger and debuggee both immediately terminate. So if there was some way of programatically finding out which the top level session is, switching to it as the active one, and running terminate, that would likely fix the issue.

maxbol commented 4 months ago

The following hackish bind solves the issue for me right now. If someone can tell me how to actually tell it to set the top level session of the branch that I'm currently on, and not just the first best one, that would be awesome!

         {
        "<leader>dt",
        function()
            local dap = require("dap")

            local session_to_activate = nil
            local sessions = dap.sessions()

            for _, s in pairs(sessions) do
                session_to_activate = s
                break
            end

            if session_to_activate ~= nil then
                dap.set_session(session)
            end

            dap.terminate()
        end,
        desc = "Terminate",
    }
mfussenegger commented 4 months ago

Could you try with:

local dap = require("dap")
local session = dap.session()
if session and session.parent then
    dap.set_session(session.parent)
end
dap.terminate()

Doing this implicitly within terminate() could be an option

maxbol commented 4 months ago

Cool, didn't know about session.parent! Will try.

How about if the focused session is more than one level deep? Maybe a while loop to traverse until session.parent is nil? Not sure if this is a real world case though (but I know that these things go more than one level deep with JS, but it seems to always break at the 1-level deep session)

maxbol commented 4 months ago

Btw, how would you recommend setting a bind to terminate ALL sessions?