mfussenegger / nvim-dap

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

Windows: add support for named pipes #1230

Open TheLeoP opened 5 months ago

TheLeoP commented 5 months ago

Problem Statement

Currently, the plugin supports Unix domain sockets, but not Windows named pipes.

I was trying to come up with a minimal solution before opening an issue, but I've encountered a few problems.

Here, a temp file is created on the file to use it as a Unix domain socket, but this isn't valid in Windows.

https://github.com/mfussenegger/nvim-dap/blob/922ebc75c2fa9305e36402fbd8c984c8638770a0/lua/dap/session.lua#L1238

So, I tried to do something like

if vim.fn.has("win32") == 1 then
  -- named pipes on windows follow the format `\\ServerName\pipe\PipeName`
  -- `ServerName` can be `.` to specify the local computer. For more info,
  -- check https://learn.microsoft.com/en-us/windows/win32/ipc/pipe-names
  filepath = vim.fs.basename(filepath)
  filepath = string.format([[\\.\pipe\%s]], filepath)
else
  os.remove(filepath)
  adapter.pipe = filepath
end

But it turns out that Powershell Editor Services (what I'm trying to integrate nvim-dap with) doesn't like to receive the full path of the pipe, it only wants to receive the name. So, for testing purposes, I changed my code to:

if vim.fn.has("win32") == 1 then
  -- named pipes on windows follow the format `\\ServerName\pipe\PipeName`
  -- `ServerName` can be `.` to specify the local computer. For more info,
  -- check https://learn.microsoft.com/en-us/windows/win32/ipc/pipe-names
  filepath = vim.fs.basename(filepath)
  adapter.pipe = string.format([[\\.\pipe\%s]], filepath)
else
  os.remove(filepath)
  adapter.pipe = filepath
end

Now, the executable gets the pipe name and nvim-dap connects to the pipe path.

Surprisingly, the following check does (almost) work also on Windows

vim.wait(timeout, function()
  return uv.fs_stat(adapter.pipe) ~= nil
end)

uv.fs_stat does return something once the named pipe is created, but the named pipe can be created without it being available for connections yet. So, another problem, nvim-dap can fail (with a timeout error) to connect to the named pipe if pipe:connect(...) is called too early.

As fas as I can tell, there is no way to check if a named pipe is ready to receive connections and the only workaround is to retry the connection if it has failed with a timeout error (which seems messy).

Possible Solutions

No idea

Considered Alternatives

~I'm trying not to rely on nvim-dap launching the Powershell Editor Services executable by creating a plugin powershell.nvim. This allows me to use some custom logic to wait for the pipe to be opened (the executable creates a json file when the pipe is ready to receive connections). But, I'm facing what I think it's a Neovim problem (?), I haven't fully debuged it yet.~

~I launch the executable using :h termopen() (since one of the Powershell Editor Services features is an integrated Powershell terminal) and the debugging session is launched correctly. However, the external executable does not process some actions until a new command (or even an empty line) is sent through the integrated terminal. My guess is that it has something to do with Neovim not providing separated handles for tty and stdin when opening a terminal (relevant issue), which causes the input to go through tty and hence the need for a new command (even an empty line) to be send via the tty for the input to be received by the executable (but, as I said, I haven't debugged this on my Linux machine yet to confirm my suspicions).~

It turned out to be an Powershell Editor Services issue. I can't think of any alternatives :c link to the issue

freddiehaddad commented 4 months ago

Since debugging works in VSCode on Windows, could there be any clues there that might help us?

TheLeoP commented 4 months ago

The approach mentioned in this issue involves nvim-dap launching the debugger. The VSCode extension launches the debugger themselves and waits for a session file specifig to powershell editor services.

I remember trying a simmilar approach on powershell.nvim (currently, the only debug config is broken on Windows, so the repo does not contain said attempt), but I don't remember why I didn't continued using it. I think I may have posponed it while waiting for a response on https://github.com/PowerShell/PowerShellEditorServices/issues/2164

freddiehaddad commented 4 months ago

I installed powershell.nvim and I'm attempting to start a debug session with your changes in the issue but am seeing a timeout (even with an additional 5 second wait after the pipe gets created).

---@param adapter dap.PipeAdapter
---@param opts? table
---@param on_connect fun(err?: string)
---@return dap.Session
function Session.pipe(adapter, opts, on_connect)
  local pipe = assert(uv.new_pipe(), "Must be able to create pipe")
  local session = new_session(adapter, opts or {}, pipe)

  if adapter.executable then
    if adapter.pipe == "${pipe}" then
      -- don't mutate original adapter definition
      adapter = vim.deepcopy(adapter)
      session.adapter = adapter

      local filepath = os.tmpname()
      local is_windows = vim.fn.has("win32")
      if is_windows then
        filepath = vim.fs.basename(filepath)
        adapter.pipe = string.format([[\\.\pipe\%s]], filepath)
      else
        os.remove(filepath)
        adapter.pipe = filepath
      end
      session.on_close["dap.server_executable_pipe"] = function()
        pcall(os.remove, filepath)
      end
      -- adapter.pipe = filepath
      if adapter.executable.args then
        local args = assert(adapter.executable.args)
        for idx, arg in pairs(args) do
          args[idx] = arg:gsub('${pipe}', filepath)
        end
      end
    end
    spawn_server_executable(adapter.executable, session)
    log.debug(
      "Debug adapter server executable started with pipe " .. adapter.pipe)
    -- The adapter should create the pipe

    local adapter_opts = adapter.options or {}
    local timeout = adapter_opts.timeout or 5000
    vim.print("waiting for pipe: " .. adapter.pipe)
    vim.wait(timeout, function()
      local ready = uv.fs_stat(adapter.pipe) ~= nil
      if ready then
        vim.print("pipe created")
      end
      return ready
    end)
  end

Error:

Couldn't connect to pipe \\.\pipe\sdns.0: ETIMEOUT

It seems like the pipe gets created but never accepts connections.

freddiehaddad commented 4 months ago

I'm experimenting with manually running Start-EditorServices.ps1 and I'm experiencing hangs. I'll provide updates if I can find anything in the script that could be causing an issue.

TheLeoP commented 4 months ago

I installed powershell.nvim and I'm attempting to start a debug session with your changes in the issue but am seeing a timeout (even with an additional 5 second wait after the pipe gets created).

That's because nvim-dap is trying to conect to the pipe after it has been opened (the file is created and read by stat) but before it accepts any connections. If you change

    vim.wait(timeout, function()
      return uv.fs_stat(adapter.pipe) ~= nil
    end)

to

    vim.wait(timeout, function()
      return false
    end)

(to always wait 5 seconds before connecting to the pipe), it'll work (because now nvim-dap is waiting enough time for the named pipe to be opened and expecting connections)

freddiehaddad commented 4 months ago

I did that as well. Can confirm it still doesn't connect. It's not in the example, but I did add a wait call in the connect method.

TheLeoP commented 4 months ago

For the sake of completion, @freddiehaddad I just pushed DAP support to powershel.nvim, but it still doesn't support using an integrated debug terminal