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

Feature Request: Breakpoint persistence

Open yingzhu146 opened this issue 4 years ago • 18 comments

Hi there,

Would be great if I could persist the breakpoints over sessions - I often have to restart nvim (esp. because the dap UI is still a bit brittle over .stop() and .start() https://github.com/rcarriga/nvim-dap-ui/issues/18. Persisting this over sessions would be great! :)

thanks a lot this awesome plugin.

yingzhu146 avatar Jun 04 '21 14:06 yingzhu146

You could write some custom functions that let you export and import the active breakpoints:

local breakpoints = require('dap.breakpoints')


function M.store()
  local bps = {}
  local breakpoints_by_buf = breakpoints.get()
  for buf, buf_bps in pairs(breakpoints_by_buf) do
    bps[tostring(buf)] = buf_bps
  end
  local fp = io.open('/tmp/breakpoints.json', 'w')
  fp:write(vim.fn.json_encode(bps))
  fp:close()
end


function M.load()
  local fp = io.open('/tmp/breakpoints.json', 'r')
  local content = fp:read('*a')
  local bps = vim.fn.json_decode(content)
  for buf, buf_bps in pairs(bps) do
    for _, bp in pairs(buf_bps) do
      local line = bp.line
      local opts = {
        condition = bp.condition,
        log_message = bp.logMessage,
        hit_condition = bp.hitCondition
      }
      breakpoints.set(opts, tonumber(buf), line)
    end
  end
end

Not sure if this is a common enough use-case to warrant including something like that out of the box. (And the dap.breakpoints API is currently considered internal - meaning I might break BWC without going through a deprecation phase)

mfussenegger avatar Jun 04 '21 21:06 mfussenegger

Thanks for the pointer and implementation suggestion here! I'm fine having a local function and dealing with breakages. Nonetheless I do think normal IDEs persist by default (although it's been a while so might be wrong here) so my biased opinion is that this should likely be default behaviour :) In any event, this solves my issue so thanks a lot, and feel free to notfix or close this issue if you don't think you'll implement it!

yingzhu146 avatar Jun 06 '21 01:06 yingzhu146

Just my two cents, I think persisting breakpoints would be great. At least for the life of the current open file.

rmagillxyz avatar Jun 08 '21 06:06 rmagillxyz

At least for the life of the current open file.

This is currently already the case, or maybe I misunderstand what you mean with persisting?

mfussenegger avatar Jul 04 '21 19:07 mfussenegger

I'm all for persistent breakpoints! I think it would make sense to make it an optional feature that is off by default but having the option built in would be great!

jrmoulton avatar Feb 10 '22 02:02 jrmoulton

If anyone is interested I've added to the functions that @mfussenegger left in a comment so that the breakpoints persist even when neovim is closed and the numbers of the buffers change.

HOME = os.getenv("HOME")
local breakpoints = require('dap.breakpoints')

function _G.store_breakpoints(clear)
    local load_bps_raw = io.open(HOME .. '/.cache/dap/breakpoints.json', 'r'):read("*a")
    local bps = vim.fn.json_decode(load_bps_raw)
    local breakpoints_by_buf = breakpoints.get()
    if (clear) then
        for _, bufrn in ipairs(vim.api.nvim_list_bufs()) do
            local file_path = vim.api.nvim_buf_get_name(bufrn)
            if (bps[file_path] ~= nil) then
                bps[file_path] = {}
            end
        end
    else
        for buf, buf_bps in pairs(breakpoints_by_buf) do
            bps[vim.api.nvim_buf_get_name(buf)] = buf_bps
        end
    end
    local fp = io.open(HOME .. '/.cache/dap/breakpoints.json', 'w')
    local final = vim.fn.json_encode(bps)
    fp:write(final)
    fp:close()
end

function _G.load_breakpoints()
    local fp = io.open(HOME .. '/.cache/dap/breakpoints.json', 'r')
    local content = fp:read('*a')
    local bps = vim.fn.json_decode(content)
    local loaded_buffers = {}
    local found = false
    for _, buf in ipairs(vim.api.nvim_list_bufs()) do
        local file_name = vim.api.nvim_buf_get_name(buf)
        if (bps[file_name] ~= nil and bps[file_name] ~= {}) then
            found = true
        end
        loaded_buffers[file_name] = buf
    end
    if (found == false) then
        return
    end
    for path, buf_bps in pairs(bps) do
        for _, bp in pairs(buf_bps) do
            local line = bp.line
            local opts = {
                condition = bp.condition,
                log_message = bp.logMessage,
                hit_condition = bp.hitCondition
            }
            breakpoints.set(opts, tonumber(loaded_buffers[path]), line)
        end
    end
end

Then I trigger the storing at the same time as a breakpoint toggle or clear

vim.keymap.set( {'n', 'i', 'v'}, '<F3>', '<cmd>lua require"dap".clear_breakpoints();store_breakpoints(true)<CR>' )
vim.keymap.set( {'n', 'i', 'v'}, '<F4>', '<cmd>lua require"dap".toggle_breakpoint();store_breakpoints(false)<CR>' )

and I load the breakpoints with an autocommand everytime a file opens

    autocmd BufRead * :lua load_breakpoints()

This could probably be cleaned up because this is my first time writing lua but so far its been working pretty well for me

jrmoulton avatar Feb 10 '22 22:02 jrmoulton

If anyone is interested I've added to the functions that @mfussenegger left in a comment so that the breakpoints persist even when neovim is closed and the numbers of the buffers change.

HOME = os.getenv("HOME")
local breakpoints = require('dap.breakpoints')

function _G.store_breakpoints(clear)
    local load_bps_raw = io.open(HOME .. '/.cache/dap/breakpoints.json', 'r'):read("*a")
    local bps = vim.fn.json_decode(load_bps_raw)
    local breakpoints_by_buf = breakpoints.get()
    if (clear) then
        for _, bufrn in ipairs(vim.api.nvim_list_bufs()) do
            local file_path = vim.api.nvim_buf_get_name(bufrn)
            if (bps[file_path] ~= nil) then
                bps[file_path] = {}
            end
        end
    else
        for buf, buf_bps in pairs(breakpoints_by_buf) do
            bps[vim.api.nvim_buf_get_name(buf)] = buf_bps
        end
    end
    local fp = io.open(HOME .. '/.cache/dap/breakpoints.json', 'w')
    local final = vim.fn.json_encode(bps)
    fp:write(final)
    fp:close()
end

function _G.load_breakpoints()
    local fp = io.open(HOME .. '/.cache/dap/breakpoints.json', 'r')
    local content = fp:read('*a')
    local bps = vim.fn.json_decode(content)
    local loaded_buffers = {}
    local found = false
    for _, buf in ipairs(vim.api.nvim_list_bufs()) do
        local file_name = vim.api.nvim_buf_get_name(buf)
        if (bps[file_name] ~= nil and bps[file_name] ~= {}) then
            found = true
        end
        loaded_buffers[file_name] = buf
    end
    if (found == false) then
        return
    end
    for path, buf_bps in pairs(bps) do
        for _, bp in pairs(buf_bps) do
            local line = bp.line
            local opts = {
                condition = bp.condition,
                log_message = bp.logMessage,
                hit_condition = bp.hitCondition
            }
            breakpoints.set(opts, tonumber(loaded_buffers[path]), line)
        end
    end
end

Then I trigger the storing at the same time as a breakpoint toggle or clear

vim.keymap.set( {'n', 'i', 'v'}, '<F3>', '<cmd>lua require"dap".clear_breakpoints();store_breakpoints(true)<CR>' )
vim.keymap.set( {'n', 'i', 'v'}, '<F4>', '<cmd>lua require"dap".toggle_breakpoint();store_breakpoints(false)<CR>' )

and I load the breakpoints with an autocommand everytime a file opens

    autocmd BufRead * :lua load_breakpoints()

This could probably be cleaned up because this is my first time writing lua but so far its been working pretty well for me

Hey! Thanks for your solution! I faced some bugs with this implementation, so I decided to rewrite it in a more concise manner. Please, check it out in my repo if you are interested

child404 avatar Apr 26 '22 09:04 child404

Haha I'll have to check it out because I've also had annoying bugs and have been too lazy to fix them. Thanks!

jrmoulton avatar Apr 26 '22 20:04 jrmoulton

I write a lua plugin for persistent checkpoints. https://github.com/Weissle/persistent-breakpoints.nvim

Weissle avatar Jul 11 '22 16:07 Weissle

Why not provide commands to export and import breakpoints? A better solution would be to record the actions in nvim-dap and let the user edit them.

The ideal solution can transpile the simple things between gdb/lldb and nvim-dap (probably only in one direction is feasible).

matu3ba avatar Oct 19 '22 20:10 matu3ba