NeoVim and Xdebug
post

NeoVim and Xdebug

As I continue to work on the configuration for the One True Editor I had everything working just about the way that duplicated how I use PhpStorm except for one thing -- using Xdebug.

As I write this blog post I have an article submitted for php[architect] on how I use NeoVim to do PHP development work and in that article I talked about the next few things I was going to work on adding support for ($100 a year to do enemy research on Copilot AI?!?) and getting the tool that I regret not embracing earlier but now find it indispensable.

This is not the blog post if you are looking to figure out how to get Xdebug running with whatever version of PHP you are trying to use. The excellent documentation on the Xdebug web site will absolutely point you in the right direction.

In my case, I went the route of manually compiling the 3.2.x series (I honestly cannot remember why) but most package managers for your operating system of choice can help you with that.

Xdebug and DAP

DAP (which is short for the Debug Anywhere Protocol. Look, you can quibble about who is behind DAP but the overarching goal of providing a standardized way for both debugging tools and programming languages to talk to each other makes a lot of sense. The Language Server Protocol is a similar effort that has found a lot of traction outside of Microsoft.

Prerequisites

You to have the following before we get started:

  • a version of PHP with Xdebug support
  • NeoVim 0.5+ or better
  • the ability to edit your NeoVim configuration files
  • the ability to install NeoVim plugins
  • Node installed (need it to install some tooling)
  • Git CLI tools installed

Debug Adapter for PHP

To start, I followed the excellent instructions found here but to replicate them here:

git clone https://github.com/xdebug/vscode-php-debug.git
cd vscode-php-debug
npm install && npm run build

Leveraging an existing tool to make it work is always galaxy-brain-level thinking.

Next, I decided on which plugins I wanted to use to interact with Xdebug while inside NeoVim. I chose:

Again, I encourage people to start with a base that WORKS and then start experimenting to get it the way you want it.

DAP Plugin Configuration

Again, this is for my own setup. It might be different for your own.

I needed to make sure that I turn on Telescope support in my init.vim file. I need it for other plugins but make sure you enable it if you are using telescope-dap.nvim.

lua require('telescope').setup

Note that the above is how to load Lua files inside a Vimscript file. I am starting to work on the outline of using Lua for all my NeoVim configuration settings.

Next, I have a configuration file just for DAP that I also load in my init.vim file:

local dap = require('dap')
require('telescope').load_extension('dap')

dap.adapters.php = {
    type = "executable",
    command = "node",
    args = { os.getenv("HOME") .. "/vscode-php-debug/out/phpDebug.js" }
}

dap.configurations.php = {
    {
        type = "php",
        request = "launch",
        name = "Listen for Xdebug",
        port = 9003
    }
}

In here I tell NeoVim:

  • load the DAP extension for Telescope (it overrides the default UI)
  • I want to use the VS Code DAP adapter
  • what port to listen for Xdebug info on (Xdebug 3.2.x goes with 9003 as the standard port)

On the Xdebug web site section about VS Code it points out that you can also add in a pathMappings value in dap.configurations.php to "map the files on the server to the right files on your local machine." and gives an example.

I don't use pathMappings and my setup works just fine.

Of course, I could not really use Xdebug from inside the editor without some keyboard mappings:

vim.keymap.set('n', '<F5>', function() require('dap').continue() end)
vim.keymap.set('n', '<F10>', function() require('dap').step_over() end)
vim.keymap.set('n', '<F11>', function() require('dap').step_into() end)
vim.keymap.set('n', '<F12>', function() require('dap').step_out() end)
vim.keymap.set('n', '<Leader>b', function() require('dap').toggle_breakpoint() end)
vim.keymap.set('n', '<Leader>B', function() require('dap').set_breakpoint() end)
vim.keymap.set('n', '<Leader>lp', function() require('dap').set_breakpoint(nil, nil, vim.fn.input('Log point message: ')) end)
vim.keymap.set('n', '<Leader>dr', function() require('dap').repl.open() end)
vim.keymap.set('n', '<Leader>dl', function() require('dap').run_last() end)
vim.keymap.set({'n', 'v'}, '<Leader>dh', function()
  require('dap.ui.widgets').hover()
end)
vim.keymap.set({'n', 'v'}, '<Leader>dp', function()
  require('dap.ui.widgets').preview()
end)
vim.keymap.set('n', '<Leader>df', function()
  local widgets = require('dap.ui.widgets')
  widgets.centered_float(widgets.frames)
end)
vim.keymap.set('n', '<Leader>ds', function()
  local widgets = require('dap.ui.widgets')
  widgets.centered_float(widgets.scopes)
end)

My usual workflow for debugging is:

  • code I am debugging open in the editor
  • if I didn't already start debugging, hit F5 to turn on the debugging session
  • set breakpoints where I want them with \b
  • run the code, step through the breakpoints, and see what's going on

Not different in any significant way than how I would do things in PhpStorm.

So there you have it! If you are a NeoVim user and want to start utilizing Xdebug to help you, well, debug your code faster my documentation above should get you pointed in the correct direction.

Categories: testing, php, tools