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

Feature Request: Breakpoint persistence #198

Open
yingzhu146 opened this issue Jun 4, 2021 · 13 comments
Open

Feature Request: Breakpoint persistence #198

yingzhu146 opened this issue Jun 4, 2021 · 13 comments

Comments

@yingzhu146
Copy link

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() rcarriga/nvim-dap-ui#18. Persisting this over sessions would be great! :)

thanks a lot this awesome plugin.

@mfussenegger
Copy link
Owner

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)

@yingzhu146
Copy link
Author

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!

@rmagillxyz
Copy link

rmagillxyz commented Jun 8, 2021

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

@mfussenegger
Copy link
Owner

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?

@jrmoulton
Copy link

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
Copy link

jrmoulton commented Feb 10, 2022

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

@child404
Copy link

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

@jrmoulton
Copy link

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

@Weissle
Copy link

Weissle commented Jul 11, 2022

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

@matu3ba
Copy link

matu3ba commented Oct 19, 2022

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).

@BlueDrink9
Copy link

BlueDrink9 commented Dec 28, 2022

I'd like to suggest an alternative: set viminfo+=! and save breakpoints in a dictionary with all-caps name, eg

let g:DAPBREAKPOINTS={
\ "filepath"= { line = "condition"}
\}

That would save them to the viminfo file, which is where similar things should be stored and means users don't have another file cluttering things up - one which may not be writable if the code is in a system library.

https://learnvim.irian.to/basics/views_sessions_viminfo

@enesaltinkaya

This comment was marked as spam.

@orhnk
Copy link

orhnk commented Sep 25, 2023

@child404 Here is the linux version that creates the cache file if not exists:

M.store_breakpoints = function(clear)
  -- if doesn't exist create it:
  if vim.fn.filereadable(HOME .. "/.cache/dap/breakpoints.json") == 0 then
    -- Create file
    os.execute("mkdir -p " .. HOME .. "/.cache/dap")
    os.execute("touch " .. HOME .. "/.cache/dap/breakpoints.json")
  end

  local load_bps_raw = io.open(HOME .. "/.cache/dap/breakpoints.json", "r"):read "*a"
  if load_bps_raw == "" then
    load_bps_raw = "{}"
  end

  local bps = vim.fn.json_decode(load_bps_raw)
  local breakpoints_by_buf = require("dap.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

M.load_breakpoints = function()
  local fp = io.open(HOME .. "/.cache/dap/breakpoints.json", "r")
  if fp == nil then
    print "No breakpoints found."
    return
  end
  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,
      }
      require("dap.breakpoints").set(opts, tonumber(loaded_buffers[path]), line)
    end
  end
end

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

No branches or pull requests

10 participants