nvim-dap-virtual-text icon indicating copy to clipboard operation
nvim-dap-virtual-text copied to clipboard

virtual text does not hide while run lua require'dap'.stop()

Open y1rn opened this issue 4 years ago • 27 comments

text does not hide call lua require'dap'.stop() in debugging

y1rn avatar Jul 22 '21 02:07 y1rn

@mfussenegger at the moment virtual text get's hidden on

event_terminated
event_exited
event_continued

exited/terminated don't get triggered with all debug adapters. Any recommendations how I could reliably clear when the debug session stops?

theHamsta avatar Jul 25 '21 11:07 theHamsta

I think the problem here is that stop() is only closing the session, without telling the debug adapters about it.

Users have to call dap.disconnect(); dap.stop() to properly end the session. I'll try to improve the docs in nvim-dap to clarify the behavior.

mfussenegger avatar Jul 25 '21 13:07 mfussenegger

See https://github.com/mfussenegger/nvim-dap/pull/251

mfussenegger avatar Jul 25 '21 14:07 mfussenegger

dap.disconnect() or dap.close() no trigger event_terminated or event_exited

y1rn avatar Jul 27 '21 14:07 y1rn

That would also mean that the debug adapter and possibly the application you debug keep running. With which debug adapter does that happen, and how does your configuration look like?

It would be possible to generate some kind of client_closed_session event within nvim-dap, but it is possible that this would only hide problems and not fix the root cause.

mfussenegger avatar Jul 27 '21 16:07 mfussenegger

local dap = require('dap')
dap.adapters.go= function(cb)
  local stdout = vim.loop.new_pipe(false)
  local handle
  local pid_or_err
  local port = 38697
  local root_dir = require 'lspconfig/configs'.gopls.get_root_dir(vim.fn.expand '%:p:h')
  local opts = {
    stdio = {nil, stdout},
    args = {"dap", "-l", "127.0.0.1:" .. port},
    cwd = root_dir,
    detached = true
  }
  handle, pid_or_err = vim.loop.spawn("dlv", opts, function(code)
    stdout:close()
    handle:close()
    if code ~= 0 then
      print('dlv exited with code', code)
    end
  end)
  assert(handle, 'Error running dlv: ' .. tostring(pid_or_err))
  stdout:read_start(function(err, chunk)
    assert(not err, err)
    if chunk then
      vim.schedule(function()
        --- You could adapt this and send `chunk` to somewhere else
        require('dap.repl').append(chunk)
      end)
    end
  end)
  -- Wait for delve to start
  vim.defer_fn(
    function()
      cb({type = "server", host = "127.0.0.1", port = port})
    end,
    100)
end


dap.configurations.go = {
  {
    type = "go",
    name = "Debug",
    request = "launch",
    -- program = "${workspaceFolder}",
    program=function()
      return require 'lspconfig/configs'.gopls.get_root_dir(vim.fn.expand '%:p:h')
    end,
    showLog = "true"
  },
  {
    type = "go",
    name = "Debug file",
    request = "launch",
    -- program = "${file}",
    program = function()
      return vim.fn.fnamemodify(vim.fn.bufname(), ':p')
    end
  },
  {
    type = "go",
    name = "Debug test", -- configuration for debugging test files
    request = "launch",
    mode = "test",
    args = function()
      local lines = vim.fn.readfile(vim.fn.fnamemodify(vim.fn.bufname(), ':p'))
      local fns = {} 
      for _, l in pairs(lines) do
        local name = string.match(l,'^%s*func.*Test(.+)%(')
        if name then 
          table.insert(fns,name)
        end
      end
      if #fns < 1 then
        error("no test case found")
      elseif #fns == 1 then
        return {
          "--test.run",
          "Test"..fns[1]
        }
      else 
        local option_strings =  {"\n\nselect test case: "} 
        for i, case in ipairs(fns) do
          table.insert(option_strings, string.format("%d: %s", i, case))
        end
        local choice = vim.fn.inputlist(option_strings)
        if choice < 1 or choice > #fns then
          error('out of index') 
        end
        return {
          "--test.run",
          "Test"..fns[choice]
        }
      end
    end,
    --program = "${file}"
    program = function()
      return vim.fn.fnamemodify(vim.fn.bufname(), ':p:h')
    end
  },
}


local getExeFile=function()
  local cmd = "cargo metadata --no-deps --format-version 1"
  local cargo_metadata = vim.fn.system(cmd)
  local cargo_workspace_dir = nil
  if vim.v.shell_error == 0 then
    cargo_workspace_dir = vim.fn.json_decode(cargo_metadata)["workspace_root"]
  end
  local fp = cargo_workspace_dir .. '/target/debug/'
  local files = vim.fn.readdir(fp);
  local ss = {}
  for _, v in pairs(files) do
    local p = fp ..v
    if vim.fn.executable(p) == 1 then
      table.insert(ss, v)
    end
  end
  if #ss == 0 then
    return 
  end 
  if #ss == 1 then
    return fp .. ss[1]
  end
 
  local option_strings =  {"select crate: "} 
  for i, runnable in ipairs(ss) do
    table.insert(option_strings, string.format("%d: %s", i, runnable))
  end
  local choice = vim.fn.inputlist(option_strings)
  if choice < 1 or choice > #ss then
     return
  end
  return fp .. ss[choice]
end



dap.adapters.lldb = {
  type = 'executable',
  command = '/usr/bin/lldb-vscode-11', -- adjust as needed
  name = "lldb"
}
dap.configurations.rust = {
  {
    name = "Launch",
    type = "lldb",
    request = "launch",
    program = function()
      vim.fn.system('cargo build')
      -- return getExeFile(vim.fn.getcwd() .. '/target/debug/')
      return getExeFile()
    end,
    cwd = '${workspaceFolder}',
    stopOnEntry = false,
    args = {},
    runInTerminal = false,
  	env = function()
      local variables = {}
      for k, v in pairs(vim.fn.environ()) do
        table.insert(variables, string.format("%s=%s", k, v))
      end
      return variables
    end,
  },
}

--dap.listeners.after['event_terminated']['my-plugin'] = function(session, body)
dap.listeners.after['event_exited']['my-plugin'] = function(session, body)
  dap.repl.close() 
end
dap.listeners.after['output']['my-plugin'] = function(session, body)
  dap.repl.open() 
end

require('dap.ext.vscode').load_launchjs()

y1rn avatar Jul 28 '21 03:07 y1rn

Maybe I should also react to the disconnect/terminate request to clear the text

theHamsta avatar Jul 28 '21 05:07 theHamsta

Users have to call dap.disconnect(); dap.stop() to properly end the session. I'll try to improve the docs in nvim-dap to clarify the behavior.

@mfussenegger I tried it, but the text still exists.

Shatur avatar Sep 09 '21 19:09 Shatur

@theHamsta any chance that you could expose command virtual_text.toggle or disable ?

JoseConseco avatar Sep 11 '21 19:09 JoseConseco

I wanted to refactor the plugin to allow more options and initialization with a proper setup call. When I started the plugin I wanted to have minimal setup and I couldn't think other option than activated and deactivated. I can also add a toggle command

I guess toggling should be something like

vim.g.dap_virtual_text = not vim.g.dap_virtual_text
dap.listeners.after.variables["nvim-dap-virtual-text"](require'dap'.session())

theHamsta avatar Sep 11 '21 19:09 theHamsta

I'm seeing the same issue using cpptools and virtual text remaining after calling disconnect then stop.

ranebrown avatar Oct 27 '21 22:10 ranebrown

There is now :DapVirualTextForceRefresh as a mitigation, for a proper fix I would need to get notified when one of the termination methods get call in nvim-dap. Right now I'm just reacting to events in the DAP protocol. When a debug adapter fails to terminate properly won't receive a notification that it is not there anymore.

@JoseConseco there are now :DapVirualTextEnable, :DapVirualTextDisable, :DapVirualTextToggle

theHamsta avatar Oct 31 '21 20:10 theHamsta

There is now also a terminate function in nvim-dap which helps terminate some debug adapters. See https://github.com/mfussenegger/nvim-dap/pull/345

mfussenegger avatar Nov 01 '21 09:11 mfussenegger

@theHamsta thanks. It works great now. I can combine now: dap.close()| DapVirtualTextDisable so I no longer have to restart vim to remove the virtual text.

JoseConseco avatar Nov 01 '21 18:11 JoseConseco

@JoseConseco you probably want a dap.close() | DapVirtualTextForceRefresh. Or you else you'd need to enable the virtual text again when you start your next debug session DapVirtualTextEnable

theHamsta avatar Nov 01 '21 18:11 theHamsta

DapVirtualTextForceRefresh

I have E5108: Error executing lua [string ":lua"]:1: attempt to call field 'refresh' (a nil value).

Shatur avatar Nov 01 '21 21:11 Shatur

Sorry, I wanted refresh originally to be private. Should be exported now.

theHamsta avatar Nov 01 '21 21:11 theHamsta

Sorry, I wanted refresh originally to be private. Should be exported now.

Now I have another error: E5108: Error executing lua ...tart/nvim-dap-virtual-text/lua/nvim-dap-virtual-text.lua:46: attempt to index local 'session' (a nil value)

Shatur avatar Nov 01 '21 22:11 Shatur

I'm really stupid today :sweat: Refresh should clear virtual text now only when there's no session (https://github.com/theHamsta/nvim-dap-virtual-text/commit/bcea8a48eaf9228025dfdcfeec108920db727a58)

theHamsta avatar Nov 01 '21 22:11 theHamsta

Now works, thank you!

Shatur avatar Nov 01 '21 22:11 Shatur

DapVirtualTextForceRefresh works indeed better than DapVirualTextDisable. Btw I think this issue can be closed.

JoseConseco avatar Nov 04 '21 19:11 JoseConseco

You're sure? I feel this should happen automatically and without you needing to invoke a command.

theHamsta avatar Nov 04 '21 19:11 theHamsta

I think upstream has done a lot to trigger the event even if the debug adapter hang up .

theHamsta avatar Mar 28 '23 12:03 theHamsta

Even :DapVirualTextDisable doesn't clear the virt text for me: no new text is produced, but the old text does not hide.

crhf avatar Jul 07 '23 09:07 crhf

I've figured it out. The virt text hides when the debugee executes to the end and exits, and when I call dap.terminate. But if I call dap.close, then the virt text persists, and nothing I do afterwards can ever get them hidden (DapVirtualTextDisable, dap.terminate and DapVirtualTextRefresh, etc.).

So I can just use dap.terminate, though I still wonder why dap.close() | DapVirtualTextForceRefresh doesn't work for me.

crhf avatar Jul 07 '23 12:07 crhf

I still wonder why dap.close() | DapVirtualTextForceRefresh doesn't work for me.

dap.close() closes the session object on the client, it doesn't go through the proper termination routine specified by the debug adapter protocol.

See :h dap.close():

Closes the current session.

This does NOT terminate the debug adapter or debugee. You usually want to use either |dap.terminate()| or |dap.disconnect()| instead.

mfussenegger avatar Jul 08 '23 17:07 mfussenegger

I've figured it out. The virt text hides when the debugee executes to the end and exits, and when I call dap.terminate. But if I call dap.close, then the virt text persists, and nothing I do afterwards can ever get them hidden (DapVirtualTextDisable, dap.terminate and DapVirtualTextRefresh, etc.).

So I can just use dap.terminate, though I still wonder why dap.close() | DapVirtualTextForceRefresh doesn't work for me.

Can confirm. Looks like closing the session is not the correct approach, for me dap.terminate();dap.disconnect() seems to do the trick.

h0m3 avatar Feb 09 '24 03:02 h0m3