Configuring debugging in Neovim

I’m setting up Neovim as an IDE for the first time and I got stuck trying to setup debugging in OCaml. I have general OCaml environment setup in the system, I can build and run projects. LSP and none-ls in Neovim are configured and working fine.

I created a new Hello World project with dune init proj hello and added (modes byte exe) to the dune file. Simple code I used for testing:

let s = "Hello, OCaml debug"
let () = print_endline s

I built it with dune build, main.bc is created as expected and it does print the line on dune exec hello.

I set the breakpoint on any or both lines with <leader>b, which causes B markers to appear next to the line. Then I try to launch main.bc in debugger with <F5> and the B breakpoint marker in Neovim window changes to R. No DAP UI appears, just a quick screen glitch. In dap.log it only says:

[ INFO ] 2024-08-13T00:52:39Z+0200 ] ...ster/.local/share/nvim/lazy/nvim-dap/lua/dap/session.lua:911 ]	"Breakpoint unverified"	{
  id = 1,
  verified = false
}
[ INFO ] 2024-08-13T00:52:39Z+0200 ] ...ster/.local/share/nvim/lazy/nvim-dap/lua/dap/session.lua:1447 ]	"Process closed"	290638

Manual debugging outside of Neovim works fine. Not sure if and how can I test ocamlearlybird itself.

❯ ocamldebug _build/default/bin/main.bc
        OCaml Debugger version 5.2.0

(ocd) r
Loading program... done.
Hello, OCaml debug
Time: 32
Program exit.
(ocd)

I pretty much copied kickstart.nvim/lua/kickstart/plugins/debug.lua at master · nvim-lua/kickstart.nvim · GitHub and added setup for ocamlearlybird as per nvim-dap documentation. My complete plugin configuration for debug is:

return {
	"mfussenegger/nvim-dap",
	dependencies = {
		-- Creates a beautiful debugger UI
		"rcarriga/nvim-dap-ui",

		-- Required dependency for nvim-dap-ui
		"nvim-neotest/nvim-nio",

		-- Installs the debug adapters for you
		-- 'williamboman/mason.nvim',
		"jay-babu/mason-nvim-dap.nvim",
                "hackwaly/ocamlearlybird",
	},
	keys = function(_, keys)
		local dap = require("dap")
		local dapui = require("dapui")

		return {
			-- Basic debugging keymaps, feel free to change to your liking!
			{ "<F5>", dap.continue, desc = "Debug: Start/Continue" },
			{ "<F1>", dap.step_into, desc = "Debug: Step Into" },
			{ "<F2>", dap.step_over, desc = "Debug: Step Over" },
			{ "<F3>", dap.step_out, desc = "Debug: Step Out" },
			{ "<leader>b", dap.toggle_breakpoint, desc = "Debug: Toggle Breakpoint" },
			{
				"<leader>B",
				function()
					dap.set_breakpoint(vim.fn.input("Breakpoint condition: "))
				end,
				desc = "Debug: Set Breakpoint",
			},
			-- Toggle to see last session result. Without this, you can't see session output in case of unhandled exception.
			{ "<F7>", dapui.toggle, desc = "Debug: See last session result." },
			unpack(keys),
		}
	end,
	config = function()
		local dap = require("dap")
		local dapui = require("dapui")

		require("mason-nvim-dap").setup({
			-- Makes a best effort to setup the various debuggers with
			-- reasonable debug configurations
			automatic_installation = true,

			-- You can provide additional configuration to the handlers,
			-- see mason-nvim-dap README for more information
			handlers = {},
		})

		-- Dap UI setup
		-- For more information, see |:help nvim-dap-ui|
		dapui.setup({
			-- Set icons to characters that are more likely to work in every terminal.
			--    Feel free to remove or use ones that you like more! :)
			--    Don't feel like these are good choices.
			icons = { expanded = "▾", collapsed = "▸", current_frame = "*" },
			controls = {
				icons = {
					pause = "⏸",
					play = "▶",
					step_into = "⏎",
					step_over = "⏭",
					step_out = "⏮",
					step_back = "b",
					run_last = "▶▶",
					terminate = "⏹",
					disconnect = "⏏",
				},
			},
		})

		dap.listeners.after.event_initialized["dapui_config"] = dapui.open
		dap.listeners.before.event_terminated["dapui_config"] = dapui.close
		dap.listeners.before.event_exited["dapui_config"] = dapui.close

                dap.adapters.ocamlearlybird = {
                        type = 'executable',
                        command = 'ocamlearlybird',
                        args = { 'debug' }
                }

                dap.configurations.ocaml = {
                        {
                                name = 'OCaml Debug test.bc',
                                type = 'ocamlearlybird',
                                request = 'launch',
                                program = '${workspaceFolder}/_build/default/test/test.bc',
                        },
                        {
                                name = 'OCaml Debug main.bc',
                                type = 'ocamlearlybird',
                                request = 'launch',
                                program = '${workspaceFolder}/_build/default/bin/main.bc',
                        },
                }

	end,
}

I did also try to use there configurations found in set-me-up/files/conf/nvim/after/plugin/dap.lua at master · rbjorklin/set-me-up · GitHub instead, but the only difference is that DAP UI appears and it executes without hitting any breakpoints:

dap.adapters.ocaml = {
    type = 'executable',
    command = 'ocamlearlybird',
    args = {'debug'},
}

dap.configurations.ocaml = {
  {
    type = "ocaml",
    request = "launch",
    name = "Launch debug test",
    console = "integratedTerminal",
    program = "_build/default/${relativeFileDirname}/${fileBasenameNoExtension}.bc",
    cwd = "${workspaceFolder}",
    stopOnEntry = true,
    yieldSteps = 4096,
    onlyDebugGlob = "<${workspaceFolder}/**/*>",
  }
}

What am I missing here?

1 Like