Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debug Adapter Protocol Support #322

Open
tbo opened this issue Jan 5, 2019 · 35 comments
Open

Debug Adapter Protocol Support #322

tbo opened this issue Jan 5, 2019 · 35 comments

Comments

@tbo
Copy link

tbo commented Jan 5, 2019

Is your feature request related to a problem? Please describe.

I would like to have additional debugging support in coc.nvim. There are no debug extensions for neovim, that provide proper auto-completion, because this would require additional IDE level language support.

Describe the solution you'd like

VS Code integrates a multitude of debuggers via its Debug Adapter Protocol. They offer a node based sdk as a separate NPM Package. Additional info can be found here. coc.nvim would need to:

  • expose the DAP commands
  • react to DAP events
  • provide a terminal window with auto-completion

Describe alternatives you've considered

I checked two existing debugger extensions:

Additional context

All debug adapters known to me only exist as a vscode extension and in the case of Java Debug even require its LSP counterpart. I realize, that debugging is not coc.nvim's main focus, but if this can be implemented for neovim, then likely only on top of coc.nvim. I don't know any other plugin, that integrates the vscode ecosystem so well and also provides LSP-support.

@chemzqm
Copy link
Member

chemzqm commented Jan 9, 2019

Is possbile to execute command and send request to a running language server started by coc, so it's possible to make a coc extension for it.

@puremourning
Copy link

I would consider vimspector support for neovim, but only after I have made vimspector fully tested and stable (e.g. a "v1 version") on vim, as that's the platform I use. Contributions are of course welcome.

@LinArcX
Copy link
Contributor

LinArcX commented Feb 18, 2019

@chemzqm How can we write these kind of extensions? I can help you.

@XVilka
Copy link

XVilka commented Jul 3, 2019

@tbo just an update - there is an WIP PR to add support of Neovim in Vimspector by @romgrk.

@tbo
Copy link
Author

tbo commented Jul 24, 2019

@XVilka I checked it out, but the last few comments do not look promising.

@puremourning
Copy link

Better ref puremourning/vimspector#29

@tbo
Copy link
Author

tbo commented Jul 24, 2019

@puremourning Thanks for reference. That actually looks quite promising.

@LinArcX
Copy link
Contributor

LinArcX commented Dec 9, 2019

Is possbile to execute command and send request to a running language server started by coc, so it's possible to make a coc extension for it.

@chemzqm
Could you please help us how to create this kind of extension?

@adelarsq
Copy link

@chemzqm I'm interested in help too.

@hariamoor-zz
Copy link

Better ref neovim/neovim#11732. That way, it isn't bound to Coc and can be packaged with a stable release of Neovim one day.

@puremourning
Copy link

Vimspector neovim support is now fully functional and just needs testing.

@hariamoor-zz
Copy link

@chemzqm @LinArcX Would you two consider doing it in Lua and shipping it with Neovim? Like I said, I'd rather have it as a Lua plugin shipped with the editor than a Coc plugin, because that way, the functionality isn't bound to Coc.

@tbo
Copy link
Author

tbo commented Jan 19, 2020

@hariamoor What should this plugin provide, that isn't already provided by Vimspector? AFAIK auto-completion is the only thing missing and also the original reason I created this feature request here.
I just saw, that @puremourning mentioned, that DAP has support for this as well (puremourning/vimspector#52 (comment)). So Coc might not be necessary for this after all. @puremourning please correct me, if I'm wrong.

@puremourning
Copy link

You’re right. Now that vimspector has neovim support (nearly).

Vimspector has cmdline completion (sort of) for evaluate and watch. And for the console prompt buffer, it’s possible manually/planned generally to make an omnifunc which would work with standard completion and/or YCM etc.

I suppose one thing that would help is coc could expose an equivalent to the command to that YCM does to allow starting the java debug adapter (which is a plugin to jdt.ls). In YCM user runs :YcmCompleter ExecuteCommand vscode.java.startDebugSession That’s an LSP command within the java server and a mechanism by which the plugin is discovered and loaded.

But other than that it seems out of scope for coc.

@oblitum
Copy link
Member

oblitum commented Jan 21, 2020

@hariamoor this is coc issue tracker, for a possible coc debugging extension. It doesn't make sense to request coc author to write a standalone debugging interface, for NeoVim only, in Lua.

@rockneverdies55
Copy link

So based on @puremourning's comment... currently is there a way to start debug session on java language server from within coc (vscode.java.startDebugSession)? Or a workaround?

@dansomething
Copy link

dansomething commented Feb 2, 2020

Here's an example:

function! JavaStartDebugCallback(err, port)
  execute "cexpr! 'Java debug started on port: " . a:port . "'"
  call vimspector#LaunchWithSettings({ "configuration": "Java Attach", "AdapterPort": a:port })
endfunction

function! JavaStartDebug()
  call CocActionAsync('runCommand', 'vscode.java.startDebugSession', function('JavaStartDebugCallback'))
endfunction

nmap <F1> :call JavaStartDebug()<CR>

@rockneverdies55
Copy link

Thank you @dansomething. Here how it goes when I press F1 to invoke your function:

(1 of 1): Java debug started on port: null

Press ENTER or type command to continue

Enter value for debugPort: 8000

Here it opens complete empty vimspector window with asking for the port at the bottom again:

Enter port to connect to: 8000
Request for initialize aborted: Closing down

Debugging works flawlessly in vscode but can't get it to work in nvim. Not sure if it's caused by vimspector itself or coc is somehow preventing vscode-debugger and vimspector to talk to each other.

@dansomething
Copy link

Yeah, you'll need to get the Java Debug extension loaded into jdt.ls. Currently Vimspector won't do this for you. The vscode-java-debug plugin does it like this. See the jdt.ls extension docs for more info.

I've hacked to together an extension for coc.nvim to do this same thing. You're welcome to try it, but I can't make any guarantees for your setup. Being experimental, its currently not available as an npm package.

To install the coc-java-debug extension:

 CocInstall https://github.com/dansomething/coc-java-debug

To uninstall it

CocUninstall coc-java-debug

@dansomething
Copy link

dansomething commented Feb 2, 2020

Oh, here's the .vimspector.json I'm using with this setup to attached to an existing Java process that's waiting for a debug connection.

{
  "adapters": {
    "java-debug-server": {
      "name": "vscode-java",
      "port": "${AdapterPort}"
    }
  },
  "configurations": {
    "Java Attach": {
      "adapter": "java-debug-server",
      "configuration": {
        "request": "attach",
        "host": "127.0.0.1",
        "port": "5005"
      },
      "breakpoints": {
        "exception": {
          "caught": "N",
          "uncaught": "N"
        }
      }
    }
  }
}

See the Vimspector config for more info on this setup.

@rockneverdies55
Copy link

Great. Thanks for the guidance. After hours of struggling... finally once your plugin was installed, it started to work like a charm.

Oh man this is plain awesome 🙂 With coc and vimspector, I don't ever need to touch my ide anymore. 👍

@dansomething
Copy link

With coc and vimspector, I don't ever need to touch my ide anymore.

this!

Let's hope the process keeps on getting better and smoother thanks to all the hard work of these project maintainers.

@puremourning
Copy link

puremourning commented Feb 2, 2020

Currently Vimspector won't do this for you

correction : can't.

@puremourning
Copy link

@dansomething - if you're feeling generous, would you mind writing this up on the vimspector wiki? I really appreciate the work you put in to make that smooth and to help others to get it working too

@dansomething
Copy link

Sure, I'll give it a shot. And thanks for the clarification above. Its the responsibility of the code that starts jdt.ls to provide the config to enable the java-debug server extension.

@dansomething
Copy link

dansomething commented Feb 2, 2020

@puremourning I had some progress started on the readme for coc-java-debug so I just pushed that up. Feel free to copy/tweak or link to that for the vimspector wiki.

@luisdavim
Copy link

what about https://github.com/mfussenegger/nvim-dap and https://github.com/rcarriga/nvim-dap-ui ?

@martin-braun
Copy link

coc.nvim is the easier alternative to mason.nvim. I love it so much, but unfortunately, coc.nvim won't support nvim-dap unlike mason.nvim, leaving with no debugger. I was looking for any other DAP manager that can complement coc.nvim in that regard, preferably with nvim-dap integration, but I found none that is maintained. (dap-buddy would be it, if it was maintained.)

To install mason along coc.nvim feels redundant. Can anybody suggest a maintained alternative to dap-buddy?

@luisdavim
Copy link

It would be awesome if coc could support dap like it does lsp...

@oblitum
Copy link
Member

oblitum commented Dec 26, 2022

@luisdavim what do you mean by "support dap like it does lsp"? LSP is a protocol, and DAP is another, so, how does former supports the latter?

@luisdavim
Copy link

They are different protocols, currently, coc supports one but not the other. What I'm saying is that coc should support both.

@Nate-Wilkins
Copy link

Is the request to replace vimspector in a coc plugin?

I just got into vimspector and haven't configured it all the way yet but came here thinking that coc might have a debugging interface that I wasn't aware of.

@Frederick888
Copy link

Frederick888 commented Jan 3, 2023

OP probably meant integrating nvim-dap(-ui)/vimspector like vscode?

For example, using rust analyzer, there are run/debug commands (and virtual texts) for main function and tests atm. I just tried debugging one, and instead of starting my DAP client automatically, I got

[coc.nvim]: Error on notification "runCommand": Vim:E492: Not an editor command: TermdebugCommand /home/frederick/Programming/Rust/external-editor-revived/target/debug/deps/external_editor_revived-0ce839c598c3e140 util::tests --nocapture

I'm not sure if TermdebugCommand is something generic, but I think e.g. https://github.com/simrat39/rust-tools.nvim can do this for Rust at least.

Edit: Ah TermdebugCommand is a Neovim command :h terminal-debug. It's quite basic and just brings up an interactive gdb. Not sure how rust-tools.nvim handles this (it uses nvim-dap).

@follyzx
Copy link

follyzx commented May 29, 2023

I found the auto-completion feature can be achieved in the varies watch window of vimspector by introduce plug Youcompleteme. And I tried to modify the vimrc file to do the same thing by coc.vim but failed. Can this feature be realized in the future?

@asmodeus812
Copy link
Contributor

asmodeus812 commented Mar 21, 2024

Here is something i have whipped up for java, if someone is interested, it integrates both CoC and native nvim lsp with nvim-dap (install coc-java-debug (needed to inject the debug jar bundles) and coc-java for coc, or configure init_options for nvim-jdtls with java-debug)

local HOTCODEREPLACE_TYPE = {
    END = "END",
    ERROR = "ERROR",
    WARNING = "WARNING",
    STARTING = "STARTING",
    BUILD_COMPLETE = "BUILD_COMPLETE",
}
local SETTINGS = {}

local function cache_settings()
    if vim.g.did_coc_loaded ~= nil then
        SETTINGS = vim.fn["coc#util#get_config"]("java")
    else
        SETTINGS = {}
    end
end
require("dap").listeners.before["event_hotcodereplace"]["jdtls"] = function(session, body)
    if body.changeType == HOTCODEREPLACE_TYPE.BUILD_COMPLETE then
        session:request("redefineClasses", nil, function(err)
            if err then
                vim.notify("Error during hot reload", vim.log.levels.WARN)
            else
                vim.notify("Applied hot code reload", vim.log.levels.WARN)
            end
        end)
    elseif body.message then
        vim.notify(body.message, vim.log.levels.WARN)
    end
end

local function execute_command(command, callback, bufnr)
    if vim.g.did_coc_loaded ~= nil then
        if not command.arguments then
            command.arguments = {}
        end
        if type(command.arguments) ~= "table" then
            command.arguments = { command.arguments }
        end
        table.insert(command.arguments, function(error, result)
            error = error ~= vim.NIL and { message = error } or nil
            callback(error, result, {
                name = "jdtls",
                config = {
                    settings = {
                        java = SETTINGS,
                    },
                },
            })
        end)
        vim.fn.CocActionAsync("runCommand", command.command, unpack(command.arguments))
    else
        local clients = {}
        for _, c in pairs(vim.lsp.get_active_clients({ bufnr = bufnr }) or {}) do
            local command_provider = c.server_capabilities.executeCommandProvider
            local commands = type(command_provider) == "table" and command_provider.commands or {}
            if vim.tbl_contains(commands, command.command) then
                table.insert(clients, c)
            end
        end
        if vim.tbl_count(clients) == 0 then
            vim.notify(string.format("Unable to find client that supports %s", command.command), vim.log.levels.WARN)
        else
            clients[1].request("workspace/executeCommand", command, function(err, resp)
                if callback then
                    callback(err, resp)
                elseif err then
                    vim.notify(string.format("Could not execute command action: %s", err.message), vim.log.levels.WARN)
                end
            end)
        end
    end
end

local function init_debug_session(cb, bufnr)
    execute_command({
        command = "vscode.java.startDebugSession",
    }, function(err, port)
        if err ~= nil then
            vim.notify(string.format("Unable to initiate java debug session %s", err.message), vim.log.levels.WARN)
        else
            cb(port)
        end
    end, bufnr)
end

local function fetch_java_executable(mainclass, project, cb, bufnr)
    if not project or #project == 0 then
        local binary = vim.env.JAVA_HOME and string.format("%s/%s", vim.fs.normalize(vim.env.JAVA_HOME), "bin/java") or "java"
        if vim.fn.executable(binary) then
            cb(binary)
        else
            vim.notify(string.format("Unable to resolve default system java executable for %s", mainclass), vim.log.levels.WARN)
        end
    else
        execute_command({
            command = "vscode.java.resolveJavaExecutable",
            arguments = { mainclass, project },
        }, function(err, java_exec)
            if err then
                vim.notify(
                    string.format("Unable to resolve java executable for %s/%s: %s", project, mainclass, err.message),
                    vim.log.levels.WARN
                )
            else
                cb(java_exec)
            end
        end, bufnr)
    end
end

local function fetch_needs_preview(mainclass, project, cb, bufnr)
    if not project or #project == 0 then
        cb("")
    else
        execute_command({
            command = "vscode.java.checkProjectSettings",
            arguments = vim.fn.json_encode({
                className = mainclass,
                projectName = project,
                inheritedOptions = true,
                expectedOptions = { ["org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures"] = "enabled" },
            }),
        }, function(err, use_preview)
            vim.print(err, use_preview, "preview")
            if err then
                vim.notify(
                    string.format("Unable to resolve preview feature for %s/%s: %s", project, mainclass, err.message),
                    vim.log.levels.WARN
                )
            else
                use_preview = use_preview and "--enable-preview" or ""
                cb(use_preview)
            end
        end, bufnr)
    end
end

local function fetch_main_definitions(cb, bufnr)
    execute_command({
        command = "vscode.java.resolveMainClass",
    }, function(err, mainclasses)
        if err then
            vim.notify(string.format("Unable to resolve any main project definitions", err.message), vim.log.levels.WARN)
        else
            cb(mainclasses)
        end
    end, bufnr)
end

local function fetch_project_paths(mainclass, project, cb, bufnr)
    if not project or #project == 0 then
        cb(nil, nil)
    else
        execute_command({
            command = "vscode.java.resolveClasspath",
            arguments = { mainclass, project },
        }, function(err, paths)
            if err then
                vim.notify(string.format("Unable to resolve classpath for %s/%s: %s", project, mainclass, err.message), vim.log.levels.WARN)
            else
                cb(paths[1], paths[2])
            end
        end, bufnr)
    end
end

local function fetch_dap_configs(callback)
    cache_settings()
    local bufnr = vim.api.nvim_get_current_buf()
    fetch_main_definitions(function(mainclasses)
        local configurations = {} -- clean config map
        if mainclasses == nil or #mainclasses == 0 then
            callback(configurations)
            return
        end
        local remaining = #mainclasses
        for _, mc in pairs(mainclasses) do
            local mainclass = mc.mainClass
            local project = mc.projectName
            fetch_java_executable(mainclass, project, function(java_exec)
                fetch_needs_preview(mainclass, project, function(use_preview)
                    fetch_project_paths(mainclass, project, function(module_paths, class_paths)
                        remaining = remaining - 1
                        if module_paths and class_paths then
                            -- insert new fresh config entry
                            table.insert(configurations, {
                                vmArgs = use_preview,
                                javaExec = java_exec,
                                projectName = project,
                                mainClass = mainclass,
                                classPaths = class_paths,
                                modulePaths = module_paths,
                            })
                        end
                        if remaining == 0 then
                            callback(configurations)
                        end
                    end, bufnr)
                end, bufnr)
            end, bufnr)
        end
    end, bufnr)
end

return function(opts)
    local function attempt_enrich_config(config, on_config)
        if not config.__template then
            -- flat config, already resolved, just enrich
            -- we have nothing more to do to this config
            opts.enricher(config, on_config)
        else
            -- config table here is a copy of the original,
            -- but the result will have the main class resolved
            -- we know it was 'template' but yet not resolved
            -- we resolve it and the template flag will be cleared
            -- within the default dap enricher later on
            fetch_dap_configs(function(configs)
                if #configs > 1 then
                    local list = {}
                    for i, c in ipairs(configs) do
                        table.insert(list, string.format("%s: %s", i, c.mainClass))
                    end
                    vim.ui.select(list, { prompt = "Select application entry point" }, function(selection)
                        if selection == nil or selection == "" then
                            return
                        end
                        local mainid = tonumber(selection:match("(%d+)")) -- get the config index
                        opts.enricher(vim.tbl_extend("force", config, configs[mainid]), on_config)
                    end)
                    vim.notify("Multiple entrypoints have been detected", vim.log.levels.WARN)
                else
                    opts.enricher(vim.tbl_extend("force", config, configs[1]), on_config)
                end
            end)
        end
    end

    return {
        adapter = function(callback, _)
            init_debug_session(function(port)
                callback({
                    type = "server",
                    enrich_config = attempt_enrich_config,
                    host = opts.server_host or "127.0.0.1",
                    port = opts.server_port or port,
                })
            end, vim.api.nvim_get_current_buf())
        end,
        configurations = {
            default_launch = {
                __template = true,
                type = "java",
                request = "launch",
                cwd = "${workspaceFolder}",
                console = "integratedTerminal",
                name = "Default launch (java)",
            },
        },
    }
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests