From cb6edf359672f1949d0a13fc45d9fa019523e5d2 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Wed, 26 Apr 2023 21:39:24 -0700 Subject: [PATCH 01/21] Added personal mappings to the window labels --- lua/dapui/elements/console.lua | 2 +- lua/dapui/elements/scopes.lua | 2 +- lua/dapui/elements/stacks.lua | 2 +- lua/dapui/elements/watches.lua | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lua/dapui/elements/console.lua b/lua/dapui/elements/console.lua index 84af2da9..75bd3457 100644 --- a/lua/dapui/elements/console.lua +++ b/lua/dapui/elements/console.lua @@ -18,7 +18,7 @@ return function() if async.api.nvim_buf_is_valid(console_buf) then return console_buf end - console_buf = util.create_buffer("DAP Console", { filetype = "dapui_console" })() + console_buf = util.create_buffer("DAP Console dc", { filetype = "dapui_console" })() if vim.fn.has("nvim-0.7") == 1 then vim.keymap.set("n", "G", function() autoscroll = true diff --git a/lua/dapui/elements/scopes.lua b/lua/dapui/elements/scopes.lua index c321ca34..f29e38c8 100644 --- a/lua/dapui/elements/scopes.lua +++ b/lua/dapui/elements/scopes.lua @@ -30,7 +30,7 @@ return function(client) end ---@nodoc - dapui.elements.scopes.buffer = util.create_buffer("DAP Scopes", { + dapui.elements.scopes.buffer = util.create_buffer("DAP Scopes ds", { filetype = "dapui_scopes", }) diff --git a/lua/dapui/elements/stacks.lua b/lua/dapui/elements/stacks.lua index f0b4a7e8..365f3198 100644 --- a/lua/dapui/elements/stacks.lua +++ b/lua/dapui/elements/stacks.lua @@ -29,7 +29,7 @@ return function(client) end ---@nodoc - dapui.elements.stacks.buffer = util.create_buffer("DAP Stacks", { + dapui.elements.stacks.buffer = util.create_buffer("DAP Stacks dt", { filetype = "dapui_stacks", }) diff --git a/lua/dapui/elements/watches.lua b/lua/dapui/elements/watches.lua index e98e4251..39c83bda 100644 --- a/lua/dapui/elements/watches.lua +++ b/lua/dapui/elements/watches.lua @@ -71,7 +71,7 @@ return function(client) end ---@nodoc - dapui.elements.watches.buffer = util.create_buffer("DAP Watches", { + dapui.elements.watches.buffer = util.create_buffer("DAP Watches dw", { filetype = "dapui_watches", omnifunc = "v:lua.require'dap'.omnifunc", }) From bce693e33dc816af33f58261a88cf2579f861595 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Wed, 8 Nov 2023 22:02:32 -0800 Subject: [PATCH 02/21] Added basic disassembly code --- doc/nvim-dap-ui.txt | 6 + lua/dapui/components/disassembly.lua | 217 +++++++++++++++++++++++++++ lua/dapui/config/init.lua | 18 ++- lua/dapui/elements/disassembly.lua | 36 +++++ lua/dapui/init.lua | 2 + 5 files changed, 275 insertions(+), 4 deletions(-) create mode 100644 lua/dapui/components/disassembly.lua create mode 100644 lua/dapui/elements/disassembly.lua diff --git a/doc/nvim-dap-ui.txt b/doc/nvim-dap-ui.txt index 22369e1f..d74e20ca 100644 --- a/doc/nvim-dap-ui.txt +++ b/doc/nvim-dap-ui.txt @@ -330,6 +330,12 @@ Alias~ Alias~ `dapui.FloatingAction` → `"close"` +============================================================================== +dapui.elements.disassembly *dapui.elements.disassembly* + +Displays the Assembly code of the current frame, if supported by the client +adapter. + ============================================================================== dapui.elements.scopes *dapui.elements.scopes* diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua new file mode 100644 index 00000000..a77bf575 --- /dev/null +++ b/lua/dapui/components/disassembly.lua @@ -0,0 +1,217 @@ +local config = require("dapui.config") +local util = require("dapui.util") + + +local _GROUP = vim.api.nvim_create_augroup("NvimDapUiDisassembly", { clear = false}) + + +local function _get_height(buffer) + return vim.api.nvim_win_get_height(window) +end + + +local function _get_spacing(count) + return string.rep("\t", count) +end + + +local function _get_column_aligned(instructions) + local address_max = 1 + local byte_max = 1 + local instruction_max = 1 + + for _, instruction in ipairs(instructions) + do + local address_count = #instruction.address + + if address_count > address_max then + address_max = address_count + end + + local byte_count = #instruction.instructionBytes + + if byte_count > byte_max then + byte_max = byte_count + end + + local instruction_count = #instruction.instruction + + if instruction_count > instruction_max then + instruction_max = instruction_count + end + end + + local spacing = "" + + if config.disassembly.instruction_spacing >= 1 then + -- Prevent an invalid configuration from accidentally breaking things + -- Note: Maybe this can be pre-validated so we don't have to check here? + spacing = _get_spacing(config.disassembly.instruction_spacing) + end + + local column_justified_template = string.format( + "%%-%ss%%s%%-%ss%%s%%-%ss\n", + address_max, + byte_max, + instruction_max + ) + + local output = {} + + for _, instruction in ipairs(instructions) do + table.insert( + output, + string.format( + column_justified_template, + instruction.address, + spacing, + instruction.instructionBytes, + spacing, + instruction.instruction + ) + ) + end + + return output +end + + +local function _get_lines(instructions) + if config.disassembly.column_aligned then + return _get_column_aligned(instructions) + end + + local spacing = "" + + if config.disassembly.instruction_spacing >= 1 then + -- Prevent an invalid configuration from accidentally breaking things + -- Note: Maybe this can be pre-validated so we don't have to check here? + -- + spacing = _get_spacing(config.disassembly.instruction_spacing) + end + + local lines = {} + + for _, instruction in ipairs(instructions) do + table.insert( + lines, + string.format( + "%s%s%s%s%s\n", + instruction.address, + spacing, + instruction.instructionBytes, + spacing, + instruction.instruction + ) + ) + end +end + + +---@param client dapui.DAPClient +---@param send_ready function +return function(client, buffer, send_ready) + -- Force a redraw of the window whenever its size has changed or the cursor is moving + vim.api.nvim_create_autocmd( + "WinScrolled", + { + buffer = buffer, + callback = function() + send_ready() + end, + group = _GROUP, + } + ) + + local on_exit = function() + -- Remove auto-commands as needed + vim.api.nvim_clear_autocmds({buffer=buffer, group=_GROUP}) + end + + client.listen.scopes(send_ready) -- TODO: Maybe should just a more generic event? + client.listen.disassemble(send_ready) + client.listen.disconnect(on_exit) + client.listen.exited(on_exit) + client.listen.terminated(on_exit) + + return { + ---@param canvas dapui.Canvas + render = function(canvas) + if client.session == nil + then + -- client.session will be nil when exiting so we stop before that can error. + return + end + + if not client.session.capabilities.supportsDisassembleRequest then + util.notify( + "Debug server doesn't support disassembly requests.", + vim.log.levels.WARN + ) + + return + end + + local memory_reference = client.session.current_frame["instructionPointerReference"] + + if memory_reference == nil then + util.notify( + "Disassembly could not get the starting memory address.", + vim.log.levels.WARN + ) + + return + end + + --- @source https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disassemble + --- @type dapui.types.DisassembleResponse + local response + local success + + local window = vim.fn.bufwinid(buffer) + local height = vim.api.nvim_win_get_height(window) + local count = height * 2 + local offset = -1 * height + + success, response = pcall( + client.request.disassemble, + { + -- TODO: Finish this part + memoryReference=memory_reference, + instructionOffset=offset, + instructionCount=count, + offset=0, + resolveSymbols=true, + } + ) + + if not success then + util.notify( + "Disassembly could not be found. Cannot continue.", + vim.log.levels.WARN + ) + + return + end + + if response == nil then + util.notify( + "Invalid disassembly response. Cannot continue.", + vim.log.levels.WARN + ) + + return + end + + local lines = _get_lines(response.instructions) + + for _, line in ipairs(lines) do + canvas:write(line) + end + + -- Move the cursor after the canvas has finished drawing to the buffer + local cursor_line = height + 1 + vim.schedule(function() vim.api.nvim_win_set_cursor(window, {cursor_line, 0}) end) + end + } +end diff --git a/lua/dapui/config/init.lua b/lua/dapui/config/init.lua index 376b1367..e72da4e1 100644 --- a/lua/dapui/config/init.lua +++ b/lua/dapui/config/init.lua @@ -4,6 +4,7 @@ local dapui = {} ---@toc_entry Configuration Options ---@class dapui.Config +---@field disassembly dapui.element_config.Disassembly ---@field icons dapui.Config.icons ---@field mappings table Keys to trigger actions in elements ---@field element_mappings table> Per-element overrides of global mappings @@ -20,6 +21,10 @@ local dapui = {} ---@field select_window? fun(): integer A function which returns a window to be --- used for opening buffers such as a stack frame location. +---@class dapui.element_config.Disassembly +---@field column_aligned boolean If `true`, Disassembly instructions align vertically +---@field instruction_spacing integer The spacing to place memory addresses, bytes, and instructions + ---@class dapui.Config.icons ---@field expanded string ---@field collapsed string @@ -75,6 +80,10 @@ local dapui = {} ---@type dapui.Config ---@nodoc local default_config = { + disassembly = { + column_aligned = true, + instruction_spacing = 1, + }, icons = { expanded = "", collapsed = "", current_frame = "" }, mappings = { -- Use a table to apply multiple mappings @@ -95,11 +104,12 @@ local default_config = { -- Provide IDs as strings or tables with "id" and "size" keys { id = "scopes", - size = 0.25, -- Can be float or integer > 1 + size = 0.20, -- Can be float or integer > 1 }, - { id = "breakpoints", size = 0.25 }, - { id = "stacks", size = 0.25 }, - { id = "watches", size = 0.25 }, + { id = "breakpoints", size = 0.20 }, + { id = "disassembly", size = 0.20 }, + { id = "stacks", size = 0.20 }, + { id = "watches", size = 0.20 }, }, size = 40, position = "left", -- Can be "left" or "right" diff --git a/lua/dapui/elements/disassembly.lua b/lua/dapui/elements/disassembly.lua new file mode 100644 index 00000000..63d7a98f --- /dev/null +++ b/lua/dapui/elements/disassembly.lua @@ -0,0 +1,36 @@ +local config = require("dapui.config") +local Canvas = require("dapui.render.canvas") +local util = require("dapui.util") + +return function(client) + local dapui = { elements = {} } + local auto_commands = {} + + ---@class dapui.elements.disassembly + --- Displays the Assembly code of the current frame, if supported by the client adapter. + dapui.elements.disassembly = {} + + local send_ready = util.create_render_loop(function() + dapui.elements.disassembly.render() + end) + + local buffer = util.create_buffer("DAP Disassembly", { + filetype = "dapui_disassembly", + })() + + local disassembly = require("dapui.components.disassembly")(client, buffer, send_ready) + + ---@nodoc + function dapui.elements.disassembly.render() + local canvas = Canvas.new() + disassembly.render(canvas) + canvas:render_buffer(dapui.elements.disassembly.buffer(), config.element_mapping("disassembly")) + end + + ---@nodoc + dapui.elements.disassembly.buffer = function() + return buffer + end + + return dapui.elements.disassembly +end diff --git a/lua/dapui/init.lua b/lua/dapui/init.lua index f7ade125..91260f93 100644 --- a/lua/dapui/init.lua +++ b/lua/dapui/init.lua @@ -83,6 +83,7 @@ function dapui.setup(user_config) "breakpoints", "repl", "scopes", + "disassembly", "stacks", "watches", "hover", @@ -365,6 +366,7 @@ end ---@field repl dapui.elements.repl ---@field scopes dapui.elements.scopes ---@field stack dapui.elements.stacks +---@field disassembly dapui.elements.disassembly ---@field watches dapui.elements.watches ---@field console dapui.elements.console dapui.elements = setmetatable({}, { From c7f736d36b855ac0043ff3298c9bce9a483de7d4 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Thu, 9 Nov 2023 23:24:46 -0800 Subject: [PATCH 03/21] Added WIP docstrings --- lua/dapui/client/types.lua | 3 + lua/dapui/components/disassembly.lua | 247 +++++++++++++++++++++++---- 2 files changed, 218 insertions(+), 32 deletions(-) diff --git a/lua/dapui/client/types.lua b/lua/dapui/client/types.lua index c968e445..067473be 100644 --- a/lua/dapui/client/types.lua +++ b/lua/dapui/client/types.lua @@ -269,6 +269,9 @@ function DAPUIRequestsClient.disconnect(args) end ---@param opts? dapui.client.ListenerOpts function DAPUIEventListenerClient.disconnect(listener, opts) end +--- @class dapui.types.RenderableComponent +--- @field render fun(canvas: dapui.Canvas) Populate `canvas` with text. + --- Provides formatting information for a value. ---@class dapui.types.ValueFormat ---@field hex? boolean Display the value in hex. diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua index a77bf575..60ba8b3a 100644 --- a/lua/dapui/components/disassembly.lua +++ b/lua/dapui/components/disassembly.lua @@ -4,18 +4,69 @@ local util = require("dapui.util") local _GROUP = vim.api.nvim_create_augroup("NvimDapUiDisassembly", { clear = false}) +--- @class vim.loop.timer +--- @field start fun(self: vim.loop.timer, timeout: integer, start: integer, function: fun(...)) +--- A function that, when called, starts in `start` milliseconds, +--- stops at `timeout`, and all the while calls `function`. + +--- Debounce a function and prefer the first call. +--- +--- If the debounced function is called repeatedly in the span of `timeout`, +--- ignore all but the first call. This is helpful to prevent spammy autocommands +--- from triggering too often. +--- +--- @source https://gist.github.com/runiq/31aa5c4bf00f8e0843cd267880117201#debouncing-on-the-leading-edge +--- +--- @param function_ fun(...) +--- A function to call or defer. +--- @param timeout number +--- Timeout in millisecond. +--- @return function, vim.loop.timer +--- # The debounced function and timer. Remember to call `timer:close()` at the end +--- or you will leak memory! +--- +local function _debounce_leading(function_, timeout) + --- @type vim.loop.timer + local timer = vim.loop.new_timer() + + local running = false + + local function wrapped(...) + timer:start( + timeout, + 0, + function() + running = false + end + ) -local function _get_height(buffer) - return vim.api.nvim_win_get_height(window) -end - + if not running then + running = true + pcall(vim.schedule_wrap(function_), select(1, ...)) + end + end -local function _get_spacing(count) - return string.rep("\t", count) + return wrapped, timer end -local function _get_column_aligned(instructions) +--- Figure out the spacing needed for every column in `instructions`. +--- +--- Each instruction address has potentially varying lengths of memory addresses, +--- memory bytes, and Assembly instruction text. However most developers enjoy +--- reading the text like this: +--- +--- 0x0004 85 ba 00 some assembly command +--- 0x0008 87 aa 11 34 77 another assembly command +--- +--- Use this function to produce a format string that will align that text. +--- +--- @param instructions dapui.types.DisassembledInstruction[] +--- Each of the instructions to query for individual element lengths. +--- @return string +--- # The recommended template that will provided aligned columns. +--- +local function _get_alignment_template(instructions) local address_max = 1 local byte_max = 1 local instruction_max = 1 @@ -41,6 +92,33 @@ local function _get_column_aligned(instructions) end end + return string.format( + "%%-%ss%%s%%-%ss%%s%%-%ss\n", + address_max, + byte_max, + instruction_max + ) +end + + +--- Repeat the "\t" character `count` number of times. +--- +--- @param count integer The number of times to repeat. Should be 1-or-more. +--- @return string # The generated "\t\t\t" text. +--- +local function _get_spacing(count) + return string.rep("\t", count) +end + + +--- Get the disassembly text from `instructions`, aligned by-column. +--- +--- @param instructions dapui.types.DisassembledInstruction[] +--- Each of the instructions to query for individual element lengths. +--- @return string[] +--- # The raw disassembly lines to display, later. +--- +local function _get_column_aligned(instructions) local spacing = "" if config.disassembly.instruction_spacing >= 1 then @@ -49,14 +127,8 @@ local function _get_column_aligned(instructions) spacing = _get_spacing(config.disassembly.instruction_spacing) end - local column_justified_template = string.format( - "%%-%ss%%s%%-%ss%%s%%-%ss\n", - address_max, - byte_max, - instruction_max - ) - local output = {} + local column_justified_template = _get_alignment_template(instructions) for _, instruction in ipairs(instructions) do table.insert( @@ -76,6 +148,13 @@ local function _get_column_aligned(instructions) end +--- Get the disassembly text from `instructions`. +--- +--- @param instructions dapui.types.DisassembledInstruction[] +--- Each of the instructions to query for individual element lengths. +--- @return string[] +--- # The raw disassembly lines to display, later. +--- local function _get_lines(instructions) if config.disassembly.column_aligned then return _get_column_aligned(instructions) @@ -90,11 +169,11 @@ local function _get_lines(instructions) spacing = _get_spacing(config.disassembly.instruction_spacing) end - local lines = {} + local output = {} for _, instruction in ipairs(instructions) do table.insert( - lines, + output, string.format( "%s%s%s%s%s\n", instruction.address, @@ -105,19 +184,83 @@ local function _get_lines(instructions) ) ) end + + return output end ----@param client dapui.DAPClient ----@param send_ready function +--- Copy `data` to a new table. +--- +--- @param data table The data to copy. +--- @return table # The copied data. +--- +local function _shallow_copy(data) + local output = {} + + for key, value in pairs(data) + do + output[key] = value + end + + return output +end + + +--- Configure `buffer` so it can display in `client` when buffer-rendering is requested. +--- +--- @param client dapui.DAPClient +--- The current DAP session's controller class. +--- @param send_ready function +--- A callback that will trigger `render()`, which updates the display of +--- the nvim-dap-ui Disassembly buffer +--- @return dapui.types.RenderableComponent +--- return function(client, buffer, send_ready) + -- TODO: Add type info here + local instruction_counter = { + offset = nil, + count = nil, + } + + --- @type table? + local previous_cursor = nil + local cursor_adjustment_needed = false + local mute_autocommands = false + local should_reset_the_cursor = false + -- Force a redraw of the window whenever its size has changed or the cursor is moving vim.api.nvim_create_autocmd( "WinScrolled", { buffer = buffer, callback = function() - send_ready() + if mute_autocommands + then + return + end + + local window = vim.fn.bufwinid(buffer) + local top_line = vim.fn.getwininfo(window)[1].topline + local height = vim.api.nvim_win_get_height(window) + + if top_line == 1 + then + -- We're at the top of the buffer, request another page above the cursor + instruction_counter.offset = instruction_counter.offset - height + instruction_counter.count = instruction_counter.count + height -- Get increasingly more + + previous_cursor = vim.api.nvim_win_get_cursor(window) + cursor_adjustment_needed = true + elseif top_line >= (vim.api.nvim_buf_line_count(buffer) - height) + then + -- We're at the botton page, request a new page below the cursor + instruction_counter.offset = math.min(0, instruction_counter.offset + height) + instruction_counter.count = instruction_counter.count + height -- Get increasingly more + + previous_cursor = vim.api.nvim_win_get_cursor(window) + cursor_adjustment_needed = true + send_ready() + end end, group = _GROUP, } @@ -128,15 +271,36 @@ return function(client, buffer, send_ready) vim.api.nvim_clear_autocmds({buffer=buffer, group=_GROUP}) end - client.listen.scopes(send_ready) -- TODO: Maybe should just a more generic event? + local on_reset = function() + should_reset_the_cursor = true + send_ready() + end + + client.listen.scopes(on_reset) -- TODO: Maybe `scopes` should just a more generic event? client.listen.disassemble(send_ready) client.listen.disconnect(on_exit) client.listen.exited(on_exit) client.listen.terminated(on_exit) + local _offset_current_cursor = _debounce_leading( + function(height) + local window = vim.fn.bufwinid(buffer) + + --- @diagnostic disable-next-line: param-type-mismatch + local cursor = _shallow_copy(previous_cursor) + cursor[1] = cursor[1] - height + mute_autocommands = true + vim.api.nvim_win_set_cursor(window, cursor) + mute_autocommands = false + end, + 200 -- TODO: Tune this value, later + ) + return { - ---@param canvas dapui.Canvas render = function(canvas) + local _cursor_adjustment_needed = cursor_adjustment_needed + cursor_adjustment_needed = false + if client.session == nil then -- client.session will be nil when exiting so we stop before that can error. @@ -170,16 +334,21 @@ return function(client, buffer, send_ready) local window = vim.fn.bufwinid(buffer) local height = vim.api.nvim_win_get_height(window) - local count = height * 2 - local offset = -1 * height + + if instruction_counter.count == nil or instruction_counter.offset == nil + then + -- Initialize the instructions for the first time + instruction_counter.count = height * 2 + instruction_counter.offset = -1 * height + end success, response = pcall( client.request.disassemble, { - -- TODO: Finish this part + -- TODO: Finish this part. Add more spec details memoryReference=memory_reference, - instructionOffset=offset, - instructionCount=count, + instructionOffset=instruction_counter.offset, + instructionCount=instruction_counter.count, offset=0, resolveSymbols=true, } @@ -203,15 +372,29 @@ return function(client, buffer, send_ready) return end - local lines = _get_lines(response.instructions) - - for _, line in ipairs(lines) do + -- TODO: Consider writing a single blob of text + for _, line in ipairs(_get_lines(response.instructions)) + do canvas:write(line) end - -- Move the cursor after the canvas has finished drawing to the buffer - local cursor_line = height + 1 - vim.schedule(function() vim.api.nvim_win_set_cursor(window, {cursor_line, 0}) end) + if _cursor_adjustment_needed + then + _offset_current_cursor(height) + -- TODO: Finish this + -- elseif should_reset_the_cursor + -- then + -- local cursor_row = height + 1 + -- -- Move the cursor after the canvas has finished drawing to the buffer + -- vim.schedule( + -- function() + -- should_reset_the_cursor = false + -- mute_autocommands = true + -- vim.api.nvim_win_set_cursor(window, {cursor_row, 0}) + -- mute_autocommands = false + -- end + -- ) + end end } end From d69223a67669f209e52ccf647ca44212fde45afc Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Fri, 10 Nov 2023 00:18:55 -0800 Subject: [PATCH 04/21] Disassembly view - Added WIP cursor-restore logic --- lua/dapui/components/disassembly.lua | 211 +++++++++++++++++---------- 1 file changed, 135 insertions(+), 76 deletions(-) diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua index 60ba8b3a..15fb24ca 100644 --- a/lua/dapui/components/disassembly.lua +++ b/lua/dapui/components/disassembly.lua @@ -9,47 +9,6 @@ local _GROUP = vim.api.nvim_create_augroup("NvimDapUiDisassembly", { clear = fal --- A function that, when called, starts in `start` milliseconds, --- stops at `timeout`, and all the while calls `function`. ---- Debounce a function and prefer the first call. ---- ---- If the debounced function is called repeatedly in the span of `timeout`, ---- ignore all but the first call. This is helpful to prevent spammy autocommands ---- from triggering too often. ---- ---- @source https://gist.github.com/runiq/31aa5c4bf00f8e0843cd267880117201#debouncing-on-the-leading-edge ---- ---- @param function_ fun(...) ---- A function to call or defer. ---- @param timeout number ---- Timeout in millisecond. ---- @return function, vim.loop.timer ---- # The debounced function and timer. Remember to call `timer:close()` at the end ---- or you will leak memory! ---- -local function _debounce_leading(function_, timeout) - --- @type vim.loop.timer - local timer = vim.loop.new_timer() - - local running = false - - local function wrapped(...) - timer:start( - timeout, - 0, - function() - running = false - end - ) - - if not running then - running = true - pcall(vim.schedule_wrap(function_), select(1, ...)) - end - end - - return wrapped, timer -end - - --- Figure out the spacing needed for every column in `instructions`. --- --- Each instruction address has potentially varying lengths of memory addresses, @@ -68,7 +27,14 @@ end --- local function _get_alignment_template(instructions) local address_max = 1 - local byte_max = 1 + + -- Note: To prevent any user disruption, we assume 8, 2-letter byte addresses + -- (8bytes * 2letters) + (7spaces) == 23 + -- + -- In short, it's a reasonable default to minimize disruption + -- + local byte_max = 23 + local instruction_max = 1 for _, instruction in ipairs(instructions) @@ -189,20 +155,111 @@ local function _get_lines(instructions) end ---- Copy `data` to a new table. +--- Parse `text` for a memory address. --- ---- @param data table The data to copy. ---- @return table # The copied data. +--- @param text string Some disassembly. e.g. "0x000000000040056f 48 83 ec 20 sub $0x20,%rsp". +--- @return string? # The found address, if any. e.g. "0x000000000040056f". --- -local function _shallow_copy(data) - local output = {} +local function _get_memory_address(text) + return string.match(text, "^0x%x+") +end + + +--- Find the Disassembly memory address that's located at the current `window` cursor. +--- +--- Note: +--- It's expected that `window` and `buffer` correspond to the same data. +--- +--- @param window integer A 0-or-more identifier to the window cursor to grab from. +--- @param buffer integer A 0-or-more identifier for the lines of text to query with. +--- @return string? # The found address, if any. e.g. "0x000000000040056f". +--- +local function _get_memory_address_at_current_cursor(window, buffer) + local buffer = buffer or vim.api.nvim_win_get_buf(window) + local cursor = vim.api.nvim_win_get_cursor(window) + local row = cursor[1] + local line = vim.api.nvim_buf_get_lines(buffer, row - 1, row, false)[1] + + return _get_memory_address(line) +end + - for key, value in pairs(data) +--- Debounce a function and prefer the first call. +--- +--- If the debounced function is called repeatedly in the span of `timeout`, +--- ignore all but the first call. This is helpful to prevent spammy autocommands +--- from triggering too often. +--- +--- @source https://gist.github.com/runiq/31aa5c4bf00f8e0843cd267880117201#debouncing-on-the-leading-edge +--- +--- @param function_ fun(...) +--- A function to call or defer. +--- @param timeout number +--- Timeout in millisecond. +--- @return function, vim.loop.timer +--- # The debounced function and timer. Remember to call `timer:close()` at the end +--- or you will leak memory! +--- +local function _debounce_leading(function_, timeout) + --- @type vim.loop.timer + local timer = vim.loop.new_timer() + + local running = false + + local function wrapped(...) + timer:start( + timeout, + 0, + function() + running = false + end + ) + + if not running then + running = true + pcall(vim.schedule_wrap(function_), select(1, ...)) + end + end + + return wrapped, timer +end + + +--- Force `window`'s cursor to point to the line that contains `address`. +--- +--- @param window integer A 0-or-more window identifier whose cursor may be moved. +--- @param address string Some memory address to look for. e.g. `"0x000000000040056f"`. +--- +local function _save_and_restore_cursor(window, address) + local buffer = vim.api.nvim_win_get_buf(window) + local disassembly_lines = vim.api.nvim_buf_get_lines( + buffer, + 0, + vim.api.nvim_buf_line_count(buffer), + false + ) + + local found_row = nil + for row, line in ipairs(disassembly_lines) do - output[key] = value + if _get_memory_address(line) == address + then + found_row = row + + break + end end - return output + if found_row == nil + then + return + end + + local old_cursor = vim.api.nvim_win_get_cursor(window) + local old_column = old_cursor[2] + local new_cursor = {found_row, old_column} + + vim.api.nvim_win_set_cursor(window, new_cursor) end @@ -222,8 +279,6 @@ return function(client, buffer, send_ready) count = nil, } - --- @type table? - local previous_cursor = nil local cursor_adjustment_needed = false local mute_autocommands = false local should_reset_the_cursor = false @@ -249,15 +304,14 @@ return function(client, buffer, send_ready) instruction_counter.offset = instruction_counter.offset - height instruction_counter.count = instruction_counter.count + height -- Get increasingly more - previous_cursor = vim.api.nvim_win_get_cursor(window) cursor_adjustment_needed = true + send_ready() elseif top_line >= (vim.api.nvim_buf_line_count(buffer) - height) then -- We're at the botton page, request a new page below the cursor instruction_counter.offset = math.min(0, instruction_counter.offset + height) instruction_counter.count = instruction_counter.count + height -- Get increasingly more - previous_cursor = vim.api.nvim_win_get_cursor(window) cursor_adjustment_needed = true send_ready() end @@ -282,15 +336,11 @@ return function(client, buffer, send_ready) client.listen.exited(on_exit) client.listen.terminated(on_exit) - local _offset_current_cursor = _debounce_leading( - function(height) - local window = vim.fn.bufwinid(buffer) - - --- @diagnostic disable-next-line: param-type-mismatch - local cursor = _shallow_copy(previous_cursor) - cursor[1] = cursor[1] - height + local _reset_cursor = _debounce_leading( + function(window, cursor_row) + should_reset_the_cursor = false mute_autocommands = true - vim.api.nvim_win_set_cursor(window, cursor) + vim.api.nvim_win_set_cursor(window, {cursor_row, 0}) mute_autocommands = false end, 200 -- TODO: Tune this value, later @@ -372,6 +422,14 @@ return function(client, buffer, send_ready) return end + -- @type string? + local address = nil + + if _cursor_adjustment_needed + then + address = _get_memory_address_at_current_cursor(window) + end + -- TODO: Consider writing a single blob of text for _, line in ipairs(_get_lines(response.instructions)) do @@ -380,20 +438,21 @@ return function(client, buffer, send_ready) if _cursor_adjustment_needed then - _offset_current_cursor(height) - -- TODO: Finish this - -- elseif should_reset_the_cursor - -- then - -- local cursor_row = height + 1 - -- -- Move the cursor after the canvas has finished drawing to the buffer - -- vim.schedule( - -- function() - -- should_reset_the_cursor = false - -- mute_autocommands = true - -- vim.api.nvim_win_set_cursor(window, {cursor_row, 0}) - -- mute_autocommands = false - -- end - -- ) + if address ~= nil + then + -- Save and restore the cursor row position + vim.schedule(function() _save_and_restore_cursor(window, address) end) + else + vim.api.nvim_err_writeln( + "nvim-dap-ui: Could not find an address at the current cursor. " + .. "Disassembly refresh may not work as expected." + ) + end + elseif should_reset_the_cursor + then + -- Set the cursor to the disassembly line that matches the source code line + local cursor_row = height + 1 + _reset_cursor(window, cursor_row) end end } From d7bfdee95083916e01a550cc3eb851ef2c184658 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Mon, 13 Nov 2023 14:27:30 -0800 Subject: [PATCH 05/21] The current Disassembly line now highlights correctly --- lua/dapui/components/disassembly.lua | 224 ++++++++++++++++++--------- 1 file changed, 150 insertions(+), 74 deletions(-) diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua index 15fb24ca..60d3a735 100644 --- a/lua/dapui/components/disassembly.lua +++ b/lua/dapui/components/disassembly.lua @@ -3,6 +3,8 @@ local util = require("dapui.util") local _GROUP = vim.api.nvim_create_augroup("NvimDapUiDisassembly", { clear = false}) +local _SELECTION_HIGHLIGHT_GROUP = "NvimDapUiDisassemblyHighlightLine" +local _VIRTUAL_SELECTION = vim.api.nvim_create_namespace("NvimDapUiDisassemblyVirtualSelection") --- @class vim.loop.timer --- @field start fun(self: vim.loop.timer, timeout: integer, start: integer, function: fun(...)) @@ -114,6 +116,46 @@ local function _get_column_aligned(instructions) end +local function _get_instructions(client, memory_reference, instruction_counter) + --- @source https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disassemble + --- @type dapui.types.DisassembleResponse + local response + local success + + success, response = pcall( + client.request.disassemble, + { + -- TODO: Finish this part. Add more spec details + memoryReference=memory_reference, + instructionOffset=instruction_counter.offset, + instructionCount=instruction_counter.count, + offset=0, + resolveSymbols=true, + } + ) + + if not success then + util.notify( + "Disassembly could not be found. Cannot continue.", + vim.log.levels.WARN + ) + + return nil + end + + if response == nil then + util.notify( + "Invalid disassembly response. Cannot continue.", + vim.log.levels.WARN + ) + + return nil + end + + return response.instructions +end + + --- Get the disassembly text from `instructions`. --- --- @param instructions dapui.types.DisassembledInstruction[] @@ -184,6 +226,41 @@ local function _get_memory_address_at_current_cursor(window, buffer) end +local function _get_memory_address_at_current_instruction(window, buffer) +end + + +local function _get_session_frame(client) + if client.session == nil + then + -- client.session will be nil when exiting so we stop before that can error. + return nil + end + + if not client.session.capabilities.supportsDisassembleRequest then + util.notify( + "Debug server doesn't support disassembly requests.", + vim.log.levels.WARN + ) + + return nil + end + + local memory_reference = client.session.current_frame["instructionPointerReference"] + + if memory_reference ~= nil then + return memory_reference + end + + util.notify( + "Disassembly could not get the starting memory address.", + vim.log.levels.WARN + ) + + return nil +end + + --- Debounce a function and prefer the first call. --- --- If the debounced function is called repeatedly in the span of `timeout`, @@ -225,12 +302,40 @@ local function _debounce_leading(function_, timeout) end +local function _highlight_buffer_line(window, buffer, row) + local row = row or vim.api.nvim_win_get_cursor(window)[1] + + vim.api.nvim_buf_set_extmark( + buffer, + _VIRTUAL_SELECTION, + row - 1, + 0, + { + end_line = row, + end_col = 0, + hl_group = _SELECTION_HIGHLIGHT_GROUP, + hl_mode = "blend", + } + ) +end + + +local function _initialize_counters(instruction_counter, height) + if instruction_counter.count == nil or instruction_counter.offset == nil + then + -- Initialize the instructions for the first time + instruction_counter.count = height * 2 + instruction_counter.offset = -1 * height + end +end + + --- Force `window`'s cursor to point to the line that contains `address`. --- --- @param window integer A 0-or-more window identifier whose cursor may be moved. ---- @param address string Some memory address to look for. e.g. `"0x000000000040056f"`. +--- @param cursor_address string Some memory address to look for. e.g. `"0x000000000040056f"`. --- -local function _save_and_restore_cursor(window, address) +local function _save_and_restore_cursor(window, cursor_address) local buffer = vim.api.nvim_win_get_buf(window) local disassembly_lines = vim.api.nvim_buf_get_lines( buffer, @@ -242,7 +347,9 @@ local function _save_and_restore_cursor(window, address) local found_row = nil for row, line in ipairs(disassembly_lines) do - if _get_memory_address(line) == address + local address = _get_memory_address(line) + + if address == cursor_address then found_row = row @@ -279,8 +386,17 @@ return function(client, buffer, send_ready) count = nil, } + local function _get_computed_instruction_line() + -- The current frame is the actual instruction line. The offset + -- is relative to this line. So to get the current line, we must get + -- a 0-or-more offset value (if the offset is negative, make it positive). + -- Then `+ 1` because our offset is 0-or-more but cursor rows start at 1, not 0. + -- + return math.max(0, -1 * instruction_counter.offset) + 1 + end + local cursor_adjustment_needed = false - local mute_autocommands = false + local mute_line_adjustments = false local should_reset_the_cursor = false -- Force a redraw of the window whenever its size has changed or the cursor is moving @@ -289,7 +405,7 @@ return function(client, buffer, send_ready) { buffer = buffer, callback = function() - if mute_autocommands + if mute_line_adjustments then return end @@ -320,6 +436,9 @@ return function(client, buffer, send_ready) } ) + -- TODO: Add configuration option from "Visual" to something else + vim.api.nvim_set_hl(0, _SELECTION_HIGHLIGHT_GROUP, {link="Visual"}) + local on_exit = function() -- Remove auto-commands as needed vim.api.nvim_clear_autocmds({buffer=buffer, group=_GROUP}) @@ -337,11 +456,12 @@ return function(client, buffer, send_ready) client.listen.terminated(on_exit) local _reset_cursor = _debounce_leading( - function(window, cursor_row) + function(window, buffer, cursor_row) should_reset_the_cursor = false - mute_autocommands = true + mute_line_adjustments = true vim.api.nvim_win_set_cursor(window, {cursor_row, 0}) - mute_autocommands = false + _highlight_buffer_line(window, buffer, cursor_row) + mute_line_adjustments = false end, 200 -- TODO: Tune this value, later ) @@ -349,99 +469,53 @@ return function(client, buffer, send_ready) return { render = function(canvas) local _cursor_adjustment_needed = cursor_adjustment_needed - cursor_adjustment_needed = false + cursor_adjustment_needed = false -- TODO: Double check if this stuff is useful - if client.session == nil - then - -- client.session will be nil when exiting so we stop before that can error. - return - end - - if not client.session.capabilities.supportsDisassembleRequest then - util.notify( - "Debug server doesn't support disassembly requests.", - vim.log.levels.WARN - ) - - return - end - - local memory_reference = client.session.current_frame["instructionPointerReference"] + local memory_reference = _get_session_frame(client) if memory_reference == nil then - util.notify( - "Disassembly could not get the starting memory address.", - vim.log.levels.WARN - ) - return end - --- @source https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disassemble - --- @type dapui.types.DisassembleResponse - local response - local success - local window = vim.fn.bufwinid(buffer) local height = vim.api.nvim_win_get_height(window) - if instruction_counter.count == nil or instruction_counter.offset == nil - then - -- Initialize the instructions for the first time - instruction_counter.count = height * 2 - instruction_counter.offset = -1 * height - end - - success, response = pcall( - client.request.disassemble, - { - -- TODO: Finish this part. Add more spec details - memoryReference=memory_reference, - instructionOffset=instruction_counter.offset, - instructionCount=instruction_counter.count, - offset=0, - resolveSymbols=true, - } - ) - - if not success then - util.notify( - "Disassembly could not be found. Cannot continue.", - vim.log.levels.WARN - ) - - return - end + _initialize_counters(instruction_counter, height) - if response == nil then - util.notify( - "Invalid disassembly response. Cannot continue.", - vim.log.levels.WARN - ) + local instructions = _get_instructions(client, memory_reference, instruction_counter) + if instructions == nil then return end -- @type string? - local address = nil + local cursor_address = nil if _cursor_adjustment_needed then - address = _get_memory_address_at_current_cursor(window) + cursor_address = _get_memory_address_at_current_cursor(window, buffer) end + vim.api.nvim_buf_clear_namespace(buffer, _VIRTUAL_SELECTION, 0, -1) + -- TODO: Consider writing a single blob of text - for _, line in ipairs(_get_lines(response.instructions)) + for _, line in ipairs(_get_lines(instructions)) do canvas:write(line) end if _cursor_adjustment_needed then - if address ~= nil + if cursor_address ~= nil then -- Save and restore the cursor row position - vim.schedule(function() _save_and_restore_cursor(window, address) end) + vim.schedule( + function() + _save_and_restore_cursor(window, cursor_address) + local current_instruction_line = _get_computed_instruction_line() + _highlight_buffer_line(window, buffer, current_instruction_line) + end + ) else vim.api.nvim_err_writeln( "nvim-dap-ui: Could not find an address at the current cursor. " @@ -451,8 +525,10 @@ return function(client, buffer, send_ready) elseif should_reset_the_cursor then -- Set the cursor to the disassembly line that matches the source code line - local cursor_row = height + 1 - _reset_cursor(window, cursor_row) + instruction_counter.count = height * 2 + instruction_counter.offset = -1 * height + local current_instruction_line = _get_computed_instruction_line() + _reset_cursor(window, buffer, current_instruction_line) end end } From d6c2a4bf9d88a3e3570a11d4076f0a8b7ae97ed7 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Mon, 13 Nov 2023 15:01:12 -0800 Subject: [PATCH 06/21] Added WIP cursor offset refactor --- lua/dapui/components/disassembly.lua | 153 +++++++++++++-------------- 1 file changed, 73 insertions(+), 80 deletions(-) diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua index 60d3a735..7a7b7b26 100644 --- a/lua/dapui/components/disassembly.lua +++ b/lua/dapui/components/disassembly.lua @@ -207,23 +207,23 @@ local function _get_memory_address(text) end ---- Find the Disassembly memory address that's located at the current `window` cursor. ---- ---- Note: ---- It's expected that `window` and `buffer` correspond to the same data. ---- ---- @param window integer A 0-or-more identifier to the window cursor to grab from. ---- @param buffer integer A 0-or-more identifier for the lines of text to query with. ---- @return string? # The found address, if any. e.g. "0x000000000040056f". ---- -local function _get_memory_address_at_current_cursor(window, buffer) - local buffer = buffer or vim.api.nvim_win_get_buf(window) - local cursor = vim.api.nvim_win_get_cursor(window) - local row = cursor[1] - local line = vim.api.nvim_buf_get_lines(buffer, row - 1, row, false)[1] - - return _get_memory_address(line) -end +-- --- Find the Disassembly memory address that's located at the current `window` cursor. +-- --- +-- --- Note: +-- --- It's expected that `window` and `buffer` correspond to the same data. +-- --- +-- --- @param window integer A 0-or-more identifier to the window cursor to grab from. +-- --- @param buffer integer A 0-or-more identifier for the lines of text to query with. +-- --- @return string? # The found address, if any. e.g. "0x000000000040056f". +-- --- +-- local function _get_memory_address_at_current_cursor(window, buffer) +-- local buffer = buffer or vim.api.nvim_win_get_buf(window) +-- local cursor = vim.api.nvim_win_get_cursor(window) +-- local row = cursor[1] +-- local line = vim.api.nvim_buf_get_lines(buffer, row - 1, row, false)[1] +-- +-- return _get_memory_address(line) +-- end local function _get_memory_address_at_current_instruction(window, buffer) @@ -330,44 +330,44 @@ local function _initialize_counters(instruction_counter, height) end ---- Force `window`'s cursor to point to the line that contains `address`. ---- ---- @param window integer A 0-or-more window identifier whose cursor may be moved. ---- @param cursor_address string Some memory address to look for. e.g. `"0x000000000040056f"`. ---- -local function _save_and_restore_cursor(window, cursor_address) - local buffer = vim.api.nvim_win_get_buf(window) - local disassembly_lines = vim.api.nvim_buf_get_lines( - buffer, - 0, - vim.api.nvim_buf_line_count(buffer), - false - ) - - local found_row = nil - for row, line in ipairs(disassembly_lines) - do - local address = _get_memory_address(line) - - if address == cursor_address - then - found_row = row - - break - end - end - - if found_row == nil - then - return - end - - local old_cursor = vim.api.nvim_win_get_cursor(window) - local old_column = old_cursor[2] - local new_cursor = {found_row, old_column} - - vim.api.nvim_win_set_cursor(window, new_cursor) -end +-- --- Force `window`'s cursor to point to the line that contains `address`. +-- --- +-- --- @param window integer A 0-or-more window identifier whose cursor may be moved. +-- --- @param cursor_address string Some memory address to look for. e.g. `"0x000000000040056f"`. +-- --- +-- local function _save_and_restore_cursor(window, cursor_address) +-- local buffer = vim.api.nvim_win_get_buf(window) +-- local disassembly_lines = vim.api.nvim_buf_get_lines( +-- buffer, +-- 0, +-- vim.api.nvim_buf_line_count(buffer), +-- false +-- ) +-- +-- local found_row = nil +-- for row, line in ipairs(disassembly_lines) +-- do +-- local address = _get_memory_address(line) +-- +-- if address == cursor_address +-- then +-- found_row = row +-- +-- break +-- end +-- end +-- +-- if found_row == nil +-- then +-- return +-- end +-- +-- local old_cursor = vim.api.nvim_win_get_cursor(window) +-- local old_column = old_cursor[2] +-- local new_cursor = {found_row, old_column} +-- +-- vim.api.nvim_win_set_cursor(window, new_cursor) +-- end --- Configure `buffer` so it can display in `client` when buffer-rendering is requested. @@ -455,6 +455,15 @@ return function(client, buffer, send_ready) client.listen.exited(on_exit) client.listen.terminated(on_exit) + local _move_cursor = _debounce_leading( + function(window, cursor_row) + mute_line_adjustments = true + vim.api.nvim_win_set_cursor(window, {cursor_row, 0}) + mute_line_adjustments = false + end, + 200 -- TODO: Tune this value, later + ) + local _reset_cursor = _debounce_leading( function(window, buffer, cursor_row) should_reset_the_cursor = false @@ -488,14 +497,6 @@ return function(client, buffer, send_ready) return end - -- @type string? - local cursor_address = nil - - if _cursor_adjustment_needed - then - cursor_address = _get_memory_address_at_current_cursor(window, buffer) - end - vim.api.nvim_buf_clear_namespace(buffer, _VIRTUAL_SELECTION, 0, -1) -- TODO: Consider writing a single blob of text @@ -506,25 +507,17 @@ return function(client, buffer, send_ready) if _cursor_adjustment_needed then - if cursor_address ~= nil - then - -- Save and restore the cursor row position - vim.schedule( - function() - _save_and_restore_cursor(window, cursor_address) - local current_instruction_line = _get_computed_instruction_line() - _highlight_buffer_line(window, buffer, current_instruction_line) - end - ) - else - vim.api.nvim_err_writeln( - "nvim-dap-ui: Could not find an address at the current cursor. " - .. "Disassembly refresh may not work as expected." - ) - end + local cursor_row = vim.api.nvim_win_get_cursor(window)[1] + height + local current_instruction_line = _get_computed_instruction_line() + + vim.schedule( + function() + _move_cursor(window, cursor_row) + _highlight_buffer_line(window, buffer, current_instruction_line) + end + ) elseif should_reset_the_cursor then - -- Set the cursor to the disassembly line that matches the source code line instruction_counter.count = height * 2 instruction_counter.offset = -1 * height local current_instruction_line = _get_computed_instruction_line() From 92c36e8baebb5f6dcc8bbbfcb7a8627197c2479b Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Mon, 13 Nov 2023 15:02:24 -0800 Subject: [PATCH 07/21] Added WIP _DisassemblyWindowState class --- lua/dapui/components/disassembly.lua | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua index 7a7b7b26..df00acc3 100644 --- a/lua/dapui/components/disassembly.lua +++ b/lua/dapui/components/disassembly.lua @@ -395,9 +395,12 @@ return function(client, buffer, send_ready) return math.max(0, -1 * instruction_counter.offset) + 1 end - local cursor_adjustment_needed = false - local mute_line_adjustments = false - local should_reset_the_cursor = false + --- @type _DisassemblyWindowState + local state = { + adjust_direction_down = nil, + mute_line_adjustments = false, + should_reset_the_cursor = false + } -- Force a redraw of the window whenever its size has changed or the cursor is moving vim.api.nvim_create_autocmd( @@ -405,7 +408,7 @@ return function(client, buffer, send_ready) { buffer = buffer, callback = function() - if mute_line_adjustments + if state.mute_line_adjustments then return end @@ -420,7 +423,7 @@ return function(client, buffer, send_ready) instruction_counter.offset = instruction_counter.offset - height instruction_counter.count = instruction_counter.count + height -- Get increasingly more - cursor_adjustment_needed = true + state.adjust_direction_down = true send_ready() elseif top_line >= (vim.api.nvim_buf_line_count(buffer) - height) then @@ -428,7 +431,7 @@ return function(client, buffer, send_ready) instruction_counter.offset = math.min(0, instruction_counter.offset + height) instruction_counter.count = instruction_counter.count + height -- Get increasingly more - cursor_adjustment_needed = true + state.adjust_direction_down = false send_ready() end end, @@ -445,7 +448,7 @@ return function(client, buffer, send_ready) end local on_reset = function() - should_reset_the_cursor = true + state.should_reset_the_cursor = true send_ready() end @@ -516,7 +519,7 @@ return function(client, buffer, send_ready) _highlight_buffer_line(window, buffer, current_instruction_line) end ) - elseif should_reset_the_cursor + elseif state.should_reset_the_cursor then instruction_counter.count = height * 2 instruction_counter.offset = -1 * height From 7ffaa1ab9c80fedc69594be5fe4d49a5b6539035 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Mon, 13 Nov 2023 15:33:52 -0800 Subject: [PATCH 08/21] Removed unused code + wrote docstrings --- lua/dapui/components/disassembly.lua | 123 +++++++-------------------- 1 file changed, 33 insertions(+), 90 deletions(-) diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua index df00acc3..e59f8c14 100644 --- a/lua/dapui/components/disassembly.lua +++ b/lua/dapui/components/disassembly.lua @@ -6,6 +6,20 @@ local _GROUP = vim.api.nvim_create_augroup("NvimDapUiDisassembly", { clear = fal local _SELECTION_HIGHLIGHT_GROUP = "NvimDapUiDisassemblyHighlightLine" local _VIRTUAL_SELECTION = vim.api.nvim_create_namespace("NvimDapUiDisassemblyVirtualSelection") +--- @class _DisassemblyWindowState +--- Control the refresh, draw, and other behaviors of a Disassembly Buffer +--- @field adjust_direction_down boolean? +--- A tri-state variable. +--- - `nil` means "do nothing" +--- - `true` means "offset the cursor downwards" +--- - `false` means "offset the cursor upwards" +--- @field mute_line_adjustments boolean TODO Check if still needed +--- If `true`, don't let auto-commands like WinScrolled trigger changes +--- to the Disassembly buffer. If `false`, allow it. +--- @field should_reset_the_cursor boolean +--- If `false`, do nothing. If `true`, the next time the Disassembly buffern +--- is re-rendered, the Disassembly cursor will sync to the current stack frame. + --- @class vim.loop.timer --- @field start fun(self: vim.loop.timer, timeout: integer, start: integer, function: fun(...)) --- A function that, when called, starts in `start` milliseconds, @@ -197,39 +211,6 @@ local function _get_lines(instructions) end ---- Parse `text` for a memory address. ---- ---- @param text string Some disassembly. e.g. "0x000000000040056f 48 83 ec 20 sub $0x20,%rsp". ---- @return string? # The found address, if any. e.g. "0x000000000040056f". ---- -local function _get_memory_address(text) - return string.match(text, "^0x%x+") -end - - --- --- Find the Disassembly memory address that's located at the current `window` cursor. --- --- --- --- Note: --- --- It's expected that `window` and `buffer` correspond to the same data. --- --- --- --- @param window integer A 0-or-more identifier to the window cursor to grab from. --- --- @param buffer integer A 0-or-more identifier for the lines of text to query with. --- --- @return string? # The found address, if any. e.g. "0x000000000040056f". --- --- --- local function _get_memory_address_at_current_cursor(window, buffer) --- local buffer = buffer or vim.api.nvim_win_get_buf(window) --- local cursor = vim.api.nvim_win_get_cursor(window) --- local row = cursor[1] --- local line = vim.api.nvim_buf_get_lines(buffer, row - 1, row, false)[1] --- --- return _get_memory_address(line) --- end - - -local function _get_memory_address_at_current_instruction(window, buffer) -end - - local function _get_session_frame(client) if client.session == nil then @@ -330,46 +311,6 @@ local function _initialize_counters(instruction_counter, height) end --- --- Force `window`'s cursor to point to the line that contains `address`. --- --- --- --- @param window integer A 0-or-more window identifier whose cursor may be moved. --- --- @param cursor_address string Some memory address to look for. e.g. `"0x000000000040056f"`. --- --- --- local function _save_and_restore_cursor(window, cursor_address) --- local buffer = vim.api.nvim_win_get_buf(window) --- local disassembly_lines = vim.api.nvim_buf_get_lines( --- buffer, --- 0, --- vim.api.nvim_buf_line_count(buffer), --- false --- ) --- --- local found_row = nil --- for row, line in ipairs(disassembly_lines) --- do --- local address = _get_memory_address(line) --- --- if address == cursor_address --- then --- found_row = row --- --- break --- end --- end --- --- if found_row == nil --- then --- return --- end --- --- local old_cursor = vim.api.nvim_win_get_cursor(window) --- local old_column = old_cursor[2] --- local new_cursor = {found_row, old_column} --- --- vim.api.nvim_win_set_cursor(window, new_cursor) --- end - - --- Configure `buffer` so it can display in `client` when buffer-rendering is requested. --- --- @param client dapui.DAPClient @@ -458,30 +399,21 @@ return function(client, buffer, send_ready) client.listen.exited(on_exit) client.listen.terminated(on_exit) - local _move_cursor = _debounce_leading( - function(window, cursor_row) - mute_line_adjustments = true - vim.api.nvim_win_set_cursor(window, {cursor_row, 0}) - mute_line_adjustments = false - end, - 200 -- TODO: Tune this value, later - ) - local _reset_cursor = _debounce_leading( function(window, buffer, cursor_row) - should_reset_the_cursor = false - mute_line_adjustments = true + state.should_reset_the_cursor = false + state.mute_line_adjustments = true vim.api.nvim_win_set_cursor(window, {cursor_row, 0}) _highlight_buffer_line(window, buffer, cursor_row) - mute_line_adjustments = false + state.mute_line_adjustments = false end, 200 -- TODO: Tune this value, later ) return { render = function(canvas) - local _cursor_adjustment_needed = cursor_adjustment_needed - cursor_adjustment_needed = false -- TODO: Double check if this stuff is useful + local _adjust_direction_down = state.adjust_direction_down + state.adjust_direction_down = nil local memory_reference = _get_session_frame(client) @@ -508,14 +440,25 @@ return function(client, buffer, send_ready) canvas:write(line) end - if _cursor_adjustment_needed + if _adjust_direction_down ~= nil then - local cursor_row = vim.api.nvim_win_get_cursor(window)[1] + height + --- @type integer + local offset + + if _adjust_direction_down then + offset = height + else + offset = -1 * height + end + + local current_row = vim.api.nvim_win_get_cursor(window)[1] + local new_row = current_row + offset local current_instruction_line = _get_computed_instruction_line() + -- Important: You need to schedule this or the line will not highlight correctly vim.schedule( function() - _move_cursor(window, cursor_row) + vim.api.nvim_win_set_cursor(window, {new_row, 0}) _highlight_buffer_line(window, buffer, current_instruction_line) end ) From 39c346d176352a45bf904b6f0638188b344fd067 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Mon, 13 Nov 2023 16:00:30 -0800 Subject: [PATCH 09/21] Added missing docstrings --- lua/dapui/components/disassembly.lua | 139 +++++++++++++++++++-------- 1 file changed, 101 insertions(+), 38 deletions(-) diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua index e59f8c14..44778e62 100644 --- a/lua/dapui/components/disassembly.lua +++ b/lua/dapui/components/disassembly.lua @@ -6,6 +6,13 @@ local _GROUP = vim.api.nvim_create_augroup("NvimDapUiDisassembly", { clear = fal local _SELECTION_HIGHLIGHT_GROUP = "NvimDapUiDisassemblyHighlightLine" local _VIRTUAL_SELECTION = vim.api.nvim_create_namespace("NvimDapUiDisassemblyVirtualSelection") +--- @class _DiassemblyInstructionCounter +--- Packed data that is needed in order to draw/redraw disassembly instructions. +--- @field count? integer +--- A 1-or-more value indicating how many lines of assembly to request. +--- @field offset? integer +--- A 0-or-more relative value indicating where to start looking for assembly lines. + --- @class _DisassemblyWindowState --- Control the refresh, draw, and other behaviors of a Disassembly Buffer --- @field adjust_direction_down boolean? @@ -15,9 +22,9 @@ local _VIRTUAL_SELECTION = vim.api.nvim_create_namespace("NvimDapUiDisassemblyVi --- - `false` means "offset the cursor upwards" --- @field mute_line_adjustments boolean TODO Check if still needed --- If `true`, don't let auto-commands like WinScrolled trigger changes ---- to the Disassembly buffer. If `false`, allow it. +--- to the Disassembly Buffer. If `false`, allow it. --- @field should_reset_the_cursor boolean ---- If `false`, do nothing. If `true`, the next time the Disassembly buffern +--- If `false`, do nothing. If `true`, the next time the Disassembly Buffer --- is re-rendered, the Disassembly cursor will sync to the current stack frame. --- @class vim.loop.timer @@ -130,6 +137,20 @@ local function _get_column_aligned(instructions) end +--- Disassemble at `memory_reference` and get the instructions back. +--- +--- @param client dapui.DAPClient +--- The current DAP session's controller class. +--- @param memory_reference string +--- The memory address used to anchor the disassembly. +--- Important: This is *not* always the starting line of the disassembly. +--- That depends on `instruction_counter`. +--- @param instruction_counter _DiassemblyInstructionCounter +--- A "number of disassembly lines to get" and a relative "offset" to start from. +--- The `memory_reference` + "offset" determines the 1st line of assembly returned. +--- @return dapui.types.DisassembledInstruction[]? +--- The found instructions, if any. +--- local function _get_instructions(client, memory_reference, instruction_counter) --- @source https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disassemble --- @type dapui.types.DisassembleResponse @@ -211,6 +232,13 @@ local function _get_lines(instructions) end +--- Find the current memory address of an active DAP `client`. +--- +--- @param client dapui.DAPClient +--- The current DAP session's controller class. +--- @return string? +--- The found memory address, if any. +--- local function _get_session_frame(client) if client.session == nil then @@ -283,9 +311,14 @@ local function _debounce_leading(function_, timeout) end -local function _highlight_buffer_line(window, buffer, row) - local row = row or vim.api.nvim_win_get_cursor(window)[1] - +--- Highlight the Disassembly instruction that is about to be executed. +--- +--- @param buffer integer +--- A 0-or-more Neovim Buffer ID which will be modified by this function. +--- @param row integer +--- A 1-or-more value indicating the cursor line to highlight. +--- +local function _highlight_current_instruction(buffer, row) vim.api.nvim_buf_set_extmark( buffer, _VIRTUAL_SELECTION, @@ -301,6 +334,13 @@ local function _highlight_buffer_line(window, buffer, row) end +--- Set-up `instruction_counter` for the first time using `height`, if needed. +--- +--- @param instruction_counter _DiassemblyInstructionCounter +--- The packed data to modify if it has not be set before. +--- @param height integer +--- A 1-or-more window vertical height indicator. +--- local function _initialize_counters(instruction_counter, height) if instruction_counter.count == nil or instruction_counter.offset == nil then @@ -311,38 +351,25 @@ local function _initialize_counters(instruction_counter, height) end ---- Configure `buffer` so it can display in `client` when buffer-rendering is requested. +--- Add auto-commands for the Disassembly `buffer`. --- ---- @param client dapui.DAPClient ---- The current DAP session's controller class. ---- @param send_ready function ---- A callback that will trigger `render()`, which updates the display of ---- the nvim-dap-ui Disassembly buffer ---- @return dapui.types.RenderableComponent +--- If the user scrolls the cursor in the current buffer, there's a chance that more +--- DAP Disassemble requests will be made (it depends on how close the cursor is to +--- the top ot bottom of the buffer). --- -return function(client, buffer, send_ready) - -- TODO: Add type info here - local instruction_counter = { - offset = nil, - count = nil, - } - - local function _get_computed_instruction_line() - -- The current frame is the actual instruction line. The offset - -- is relative to this line. So to get the current line, we must get - -- a 0-or-more offset value (if the offset is negative, make it positive). - -- Then `+ 1` because our offset is 0-or-more but cursor rows start at 1, not 0. - -- - return math.max(0, -1 * instruction_counter.offset) + 1 - end - - --- @type _DisassemblyWindowState - local state = { - adjust_direction_down = nil, - mute_line_adjustments = false, - should_reset_the_cursor = false - } - +--- @source +--- - https://github.com/mfussenegger/nvim-dap/issues/331#issuecomment-1801296947 +--- - https://github.com/puremourning/vimspector/blob/66617adda22d29c60ec2ee9bcb854329352ada80/python3/vimspector/disassembly.py#L229-L260 +--- +--- @param buffer integer +--- A 0-or-more Neovim Buffer ID which will be modified by this function. +--- @param state _DisassemblyWindowState +--- A secondary Disassembly Buffer controller object. Used to determine things +--- like whether or not the auto-commands should run and how. +--- @param send_ready function() +--- The callback which, when called, triggers the Disassembly Buffer to redraw. +--- +local function _setup_auto_commands(buffer, state, instruction_counter, send_ready) -- Force a redraw of the window whenever its size has changed or the cursor is moving vim.api.nvim_create_autocmd( "WinScrolled", @@ -379,6 +406,42 @@ return function(client, buffer, send_ready) group = _GROUP, } ) +end + + +--- Configure `buffer` so it can display in `client` when buffer-rendering is requested. +--- +--- @param client dapui.DAPClient +--- The current DAP session's controller class. +--- @param send_ready function +--- A callback that will trigger `render()`, which updates the display of +--- the nvim-dap-ui Disassembly Buffer +--- @return dapui.types.RenderableComponent +--- +return function(client, buffer, send_ready) + --- @type _DiassemblyInstructionCounter + local instruction_counter = { + offset = nil, + count = nil, + } + + local function _get_computed_instruction_line() + -- The current frame is the actual instruction line. The offset + -- is relative to this line. So to get the current line, we must get + -- a 0-or-more offset value (if the offset is negative, make it positive). + -- Then `+ 1` because our offset is 0-or-more but cursor rows start at 1, not 0. + -- + return math.max(0, -1 * instruction_counter.offset) + 1 + end + + --- @type _DisassemblyWindowState + local state = { + adjust_direction_down = nil, + mute_line_adjustments = false, + should_reset_the_cursor = false + } + + _setup_auto_commands(buffer, state, instruction_counter, send_ready) -- TODO: Add configuration option from "Visual" to something else vim.api.nvim_set_hl(0, _SELECTION_HIGHLIGHT_GROUP, {link="Visual"}) @@ -400,11 +463,11 @@ return function(client, buffer, send_ready) client.listen.terminated(on_exit) local _reset_cursor = _debounce_leading( - function(window, buffer, cursor_row) + function(window, buffer_, cursor_row) state.should_reset_the_cursor = false state.mute_line_adjustments = true vim.api.nvim_win_set_cursor(window, {cursor_row, 0}) - _highlight_buffer_line(window, buffer, cursor_row) + _highlight_current_instruction(buffer_, cursor_row) state.mute_line_adjustments = false end, 200 -- TODO: Tune this value, later @@ -459,7 +522,7 @@ return function(client, buffer, send_ready) vim.schedule( function() vim.api.nvim_win_set_cursor(window, {new_row, 0}) - _highlight_buffer_line(window, buffer, current_instruction_line) + _highlight_current_instruction(buffer, current_instruction_line) end ) elseif state.should_reset_the_cursor From b84a27d4940605ff96c9e244bb0348fd41595c8f Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Mon, 13 Nov 2023 16:10:21 -0800 Subject: [PATCH 10/21] Removed unused code --- lua/dapui/components/disassembly.lua | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua index 44778e62..6d36045f 100644 --- a/lua/dapui/components/disassembly.lua +++ b/lua/dapui/components/disassembly.lua @@ -20,9 +20,6 @@ local _VIRTUAL_SELECTION = vim.api.nvim_create_namespace("NvimDapUiDisassemblyVi --- - `nil` means "do nothing" --- - `true` means "offset the cursor downwards" --- - `false` means "offset the cursor upwards" ---- @field mute_line_adjustments boolean TODO Check if still needed ---- If `true`, don't let auto-commands like WinScrolled trigger changes ---- to the Disassembly Buffer. If `false`, allow it. --- @field should_reset_the_cursor boolean --- If `false`, do nothing. If `true`, the next time the Disassembly Buffer --- is re-rendered, the Disassembly cursor will sync to the current stack frame. @@ -465,10 +462,8 @@ return function(client, buffer, send_ready) local _reset_cursor = _debounce_leading( function(window, buffer_, cursor_row) state.should_reset_the_cursor = false - state.mute_line_adjustments = true vim.api.nvim_win_set_cursor(window, {cursor_row, 0}) _highlight_current_instruction(buffer_, cursor_row) - state.mute_line_adjustments = false end, 200 -- TODO: Tune this value, later ) From 2bc811e616f3ad8184f23dddf6eab2a7f23ca3af Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Mon, 13 Nov 2023 16:16:35 -0800 Subject: [PATCH 11/21] Small docstring changes --- lua/dapui/components/disassembly.lua | 214 +++++++++++++-------------- lua/dapui/config/init.lua | 2 + lua/dapui/elements/disassembly.lua | 8 +- 3 files changed, 109 insertions(+), 115 deletions(-) diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua index 6d36045f..356f9108 100644 --- a/lua/dapui/components/disassembly.lua +++ b/lua/dapui/components/disassembly.lua @@ -6,28 +6,28 @@ local _GROUP = vim.api.nvim_create_augroup("NvimDapUiDisassembly", { clear = fal local _SELECTION_HIGHLIGHT_GROUP = "NvimDapUiDisassemblyHighlightLine" local _VIRTUAL_SELECTION = vim.api.nvim_create_namespace("NvimDapUiDisassemblyVirtualSelection") ---- @class _DiassemblyInstructionCounter ---- Packed data that is needed in order to draw/redraw disassembly instructions. ---- @field count? integer ---- A 1-or-more value indicating how many lines of assembly to request. ---- @field offset? integer ---- A 0-or-more relative value indicating where to start looking for assembly lines. - ---- @class _DisassemblyWindowState ---- Control the refresh, draw, and other behaviors of a Disassembly Buffer ---- @field adjust_direction_down boolean? ---- A tri-state variable. ---- - `nil` means "do nothing" ---- - `true` means "offset the cursor downwards" ---- - `false` means "offset the cursor upwards" ---- @field should_reset_the_cursor boolean ---- If `false`, do nothing. If `true`, the next time the Disassembly Buffer ---- is re-rendered, the Disassembly cursor will sync to the current stack frame. - ---- @class vim.loop.timer ---- @field start fun(self: vim.loop.timer, timeout: integer, start: integer, function: fun(...)) ---- A function that, when called, starts in `start` milliseconds, ---- stops at `timeout`, and all the while calls `function`. +---@class _DiassemblyInstructionCounter +--- Packed data that is needed in order to draw/redraw disassembly instructions. +---@field count? integer +--- A 1-or-more value indicating how many lines of assembly to request. +---@field offset? integer +--- A 0-or-more relative value indicating where to start looking for assembly lines. + +---@class _DisassemblyWindowState +--- Control the refresh, draw, and other behaviors of a Disassembly Buffer +---@field adjust_direction_down boolean? +--- A tri-state variable. +--- - `nil` means "do nothing" +--- - `true` means "offset the cursor downwards" +--- - `false` means "offset the cursor upwards" +---@field should_reset_the_cursor boolean +--- If `false`, do nothing. If `true`, the next time the Disassembly Buffer +--- is re-rendered, the Disassembly cursor will sync to the current stack frame. + +---@class vim.loop.timer +---@field start fun(self: vim.loop.timer, timeout: integer, start: integer, function: fun(...)) +--- A function that, when called, starts in `start` milliseconds, +--- stops at `timeout`, and all the while calls `function`. --- Figure out the spacing needed for every column in `instructions`. --- @@ -40,10 +40,10 @@ local _VIRTUAL_SELECTION = vim.api.nvim_create_namespace("NvimDapUiDisassemblyVi --- --- Use this function to produce a format string that will align that text. --- ---- @param instructions dapui.types.DisassembledInstruction[] ---- Each of the instructions to query for individual element lengths. ---- @return string ---- # The recommended template that will provided aligned columns. +---@param instructions dapui.types.DisassembledInstruction[] +--- Each of the instructions to query for individual element lengths. +---@return string +--- # The recommended template that will provided aligned columns. --- local function _get_alignment_template(instructions) local address_max = 1 @@ -57,8 +57,7 @@ local function _get_alignment_template(instructions) local instruction_max = 1 - for _, instruction in ipairs(instructions) - do + for _, instruction in ipairs(instructions) do local address_count = #instruction.address if address_count > address_max then @@ -89,8 +88,8 @@ end --- Repeat the "\t" character `count` number of times. --- ---- @param count integer The number of times to repeat. Should be 1-or-more. ---- @return string # The generated "\t\t\t" text. +---@param count integer The number of times to repeat. Should be 1-or-more. +---@return string # The generated "\t\t\t" text. --- local function _get_spacing(count) return string.rep("\t", count) @@ -99,10 +98,10 @@ end --- Get the disassembly text from `instructions`, aligned by-column. --- ---- @param instructions dapui.types.DisassembledInstruction[] ---- Each of the instructions to query for individual element lengths. ---- @return string[] ---- # The raw disassembly lines to display, later. +---@param instructions dapui.types.DisassembledInstruction[] +--- Each of the instructions to query for individual element lengths. +---@return string[] +--- # The raw disassembly lines to display, later. --- local function _get_column_aligned(instructions) local spacing = "" @@ -136,21 +135,21 @@ end --- Disassemble at `memory_reference` and get the instructions back. --- ---- @param client dapui.DAPClient ---- The current DAP session's controller class. ---- @param memory_reference string ---- The memory address used to anchor the disassembly. ---- Important: This is *not* always the starting line of the disassembly. ---- That depends on `instruction_counter`. ---- @param instruction_counter _DiassemblyInstructionCounter ---- A "number of disassembly lines to get" and a relative "offset" to start from. ---- The `memory_reference` + "offset" determines the 1st line of assembly returned. ---- @return dapui.types.DisassembledInstruction[]? ---- The found instructions, if any. +---@param client dapui.DAPClient +--- The current DAP session's controller class. +---@param memory_reference string +--- The memory address used to anchor the disassembly. +--- Important: This is *not* always the starting line of the disassembly. +--- That depends on `instruction_counter`. +---@param instruction_counter _DiassemblyInstructionCounter +--- A "number of disassembly lines to get" and a relative "offset" to start from. +--- The `memory_reference` + "offset" determines the 1st line of assembly returned. +---@return dapui.types.DisassembledInstruction[]? +--- The found instructions, if any. --- local function _get_instructions(client, memory_reference, instruction_counter) - --- @source https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disassemble - --- @type dapui.types.DisassembleResponse + ---@source https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disassemble + ---@type dapui.types.DisassembleResponse local response local success @@ -190,10 +189,10 @@ end --- Get the disassembly text from `instructions`. --- ---- @param instructions dapui.types.DisassembledInstruction[] ---- Each of the instructions to query for individual element lengths. ---- @return string[] ---- # The raw disassembly lines to display, later. +---@param instructions dapui.types.DisassembledInstruction[] +--- Each of the instructions to query for individual element lengths. +---@return string[] +--- The raw disassembly lines to display, later. --- local function _get_lines(instructions) if config.disassembly.column_aligned then @@ -231,14 +230,13 @@ end --- Find the current memory address of an active DAP `client`. --- ---- @param client dapui.DAPClient ---- The current DAP session's controller class. ---- @return string? ---- The found memory address, if any. +---@param client dapui.DAPClient +--- The current DAP session's controller class. +---@return string? +--- The found memory address, if any. --- local function _get_session_frame(client) - if client.session == nil - then + if client.session == nil then -- client.session will be nil when exiting so we stop before that can error. return nil end @@ -273,18 +271,18 @@ end --- ignore all but the first call. This is helpful to prevent spammy autocommands --- from triggering too often. --- ---- @source https://gist.github.com/runiq/31aa5c4bf00f8e0843cd267880117201#debouncing-on-the-leading-edge +---@source https://gist.github.com/runiq/31aa5c4bf00f8e0843cd267880117201#debouncing-on-the-leading-edge --- ---- @param function_ fun(...) ---- A function to call or defer. ---- @param timeout number ---- Timeout in millisecond. ---- @return function, vim.loop.timer ---- # The debounced function and timer. Remember to call `timer:close()` at the end ---- or you will leak memory! +---@param function_ fun(...) +--- A function to call or defer. +---@param timeout number +--- Timeout in millisecond. +---@return function, vim.loop.timer +--- The debounced function and timer. Remember to call `timer:close()` at the end +--- or you will leak memory! --- local function _debounce_leading(function_, timeout) - --- @type vim.loop.timer + ---@type vim.loop.timer local timer = vim.loop.new_timer() local running = false @@ -310,10 +308,10 @@ end --- Highlight the Disassembly instruction that is about to be executed. --- ---- @param buffer integer ---- A 0-or-more Neovim Buffer ID which will be modified by this function. ---- @param row integer ---- A 1-or-more value indicating the cursor line to highlight. +---@param buffer integer +--- A 0-or-more Neovim Buffer ID which will be modified by this function. +---@param row integer +--- A 1-or-more value indicating the cursor line to highlight. --- local function _highlight_current_instruction(buffer, row) vim.api.nvim_buf_set_extmark( @@ -333,14 +331,13 @@ end --- Set-up `instruction_counter` for the first time using `height`, if needed. --- ---- @param instruction_counter _DiassemblyInstructionCounter ---- The packed data to modify if it has not be set before. ---- @param height integer ---- A 1-or-more window vertical height indicator. +---@param instruction_counter _DiassemblyInstructionCounter +--- The packed data to modify if it has not be set before. +---@param height integer +--- A 1-or-more window vertical height indicator. --- local function _initialize_counters(instruction_counter, height) - if instruction_counter.count == nil or instruction_counter.offset == nil - then + if instruction_counter.count == nil or instruction_counter.offset == nil then -- Initialize the instructions for the first time instruction_counter.count = height * 2 instruction_counter.offset = -1 * height @@ -354,17 +351,17 @@ end --- DAP Disassemble requests will be made (it depends on how close the cursor is to --- the top ot bottom of the buffer). --- ---- @source ---- - https://github.com/mfussenegger/nvim-dap/issues/331#issuecomment-1801296947 ---- - https://github.com/puremourning/vimspector/blob/66617adda22d29c60ec2ee9bcb854329352ada80/python3/vimspector/disassembly.py#L229-L260 +---@source +--- - https://github.com/mfussenegger/nvim-dap/issues/331#issuecomment-1801296947 +--- - https://github.com/puremourning/vimspector/blob/66617adda22d29c60ec2ee9bcb854329352ada80/python3/vimspector/disassembly.py#L229-L260 --- ---- @param buffer integer ---- A 0-or-more Neovim Buffer ID which will be modified by this function. ---- @param state _DisassemblyWindowState ---- A secondary Disassembly Buffer controller object. Used to determine things ---- like whether or not the auto-commands should run and how. ---- @param send_ready function() ---- The callback which, when called, triggers the Disassembly Buffer to redraw. +---@param buffer integer +--- A 0-or-more Neovim Buffer ID which will be modified by this function. +---@param state _DisassemblyWindowState +--- A secondary Disassembly Buffer controller object. Used to determine things +--- like whether or not the auto-commands should run and how. +---@param send_ready function() +--- The callback which, when called, triggers the Disassembly Buffer to redraw. --- local function _setup_auto_commands(buffer, state, instruction_counter, send_ready) -- Force a redraw of the window whenever its size has changed or the cursor is moving @@ -373,25 +370,18 @@ local function _setup_auto_commands(buffer, state, instruction_counter, send_rea { buffer = buffer, callback = function() - if state.mute_line_adjustments - then - return - end - local window = vim.fn.bufwinid(buffer) local top_line = vim.fn.getwininfo(window)[1].topline local height = vim.api.nvim_win_get_height(window) - if top_line == 1 - then + if top_line == 1 then -- We're at the top of the buffer, request another page above the cursor instruction_counter.offset = instruction_counter.offset - height instruction_counter.count = instruction_counter.count + height -- Get increasingly more state.adjust_direction_down = true send_ready() - elseif top_line >= (vim.api.nvim_buf_line_count(buffer) - height) - then + elseif top_line >= (vim.api.nvim_buf_line_count(buffer) - height) then -- We're at the botton page, request a new page below the cursor instruction_counter.offset = math.min(0, instruction_counter.offset + height) instruction_counter.count = instruction_counter.count + height -- Get increasingly more @@ -408,15 +398,15 @@ end --- Configure `buffer` so it can display in `client` when buffer-rendering is requested. --- ---- @param client dapui.DAPClient ---- The current DAP session's controller class. ---- @param send_ready function ---- A callback that will trigger `render()`, which updates the display of ---- the nvim-dap-ui Disassembly Buffer ---- @return dapui.types.RenderableComponent +---@param client dapui.DAPClient +--- The current DAP session's controller class. +---@param send_ready function +--- A callback that will trigger `render()`, which updates the display of +--- the nvim-dap-ui Disassembly Buffer +---@return dapui.types.RenderableComponent --- return function(client, buffer, send_ready) - --- @type _DiassemblyInstructionCounter + ---@type _DiassemblyInstructionCounter local instruction_counter = { offset = nil, count = nil, @@ -431,17 +421,20 @@ return function(client, buffer, send_ready) return math.max(0, -1 * instruction_counter.offset) + 1 end - --- @type _DisassemblyWindowState + ---@type _DisassemblyWindowState local state = { adjust_direction_down = nil, - mute_line_adjustments = false, should_reset_the_cursor = false } _setup_auto_commands(buffer, state, instruction_counter, send_ready) -- TODO: Add configuration option from "Visual" to something else - vim.api.nvim_set_hl(0, _SELECTION_HIGHLIGHT_GROUP, {link="Visual"}) + vim.api.nvim_set_hl( + 0, + _SELECTION_HIGHLIGHT_GROUP, + config.disassembly.styles.current_frame or {link="Visual"} + ) local on_exit = function() -- Remove auto-commands as needed @@ -493,14 +486,12 @@ return function(client, buffer, send_ready) vim.api.nvim_buf_clear_namespace(buffer, _VIRTUAL_SELECTION, 0, -1) -- TODO: Consider writing a single blob of text - for _, line in ipairs(_get_lines(instructions)) - do + for _, line in ipairs(_get_lines(instructions)) do canvas:write(line) end - if _adjust_direction_down ~= nil - then - --- @type integer + if _adjust_direction_down ~= nil then + ---@type integer local offset if _adjust_direction_down then @@ -520,8 +511,7 @@ return function(client, buffer, send_ready) _highlight_current_instruction(buffer, current_instruction_line) end ) - elseif state.should_reset_the_cursor - then + elseif state.should_reset_the_cursor then instruction_counter.count = height * 2 instruction_counter.offset = -1 * height local current_instruction_line = _get_computed_instruction_line() diff --git a/lua/dapui/config/init.lua b/lua/dapui/config/init.lua index e72da4e1..10a2c629 100644 --- a/lua/dapui/config/init.lua +++ b/lua/dapui/config/init.lua @@ -24,6 +24,7 @@ local dapui = {} ---@class dapui.element_config.Disassembly ---@field column_aligned boolean If `true`, Disassembly instructions align vertically ---@field instruction_spacing integer The spacing to place memory addresses, bytes, and instructions +---@field styles table Settings used to determine how the Disassembly Buffer should look. ---@class dapui.Config.icons ---@field expanded string @@ -83,6 +84,7 @@ local default_config = { disassembly = { column_aligned = true, instruction_spacing = 1, + styles = { current_frame = { link="Visual" } }, }, icons = { expanded = "", collapsed = "", current_frame = "" }, mappings = { diff --git a/lua/dapui/elements/disassembly.lua b/lua/dapui/elements/disassembly.lua index 63d7a98f..63ae3c63 100644 --- a/lua/dapui/elements/disassembly.lua +++ b/lua/dapui/elements/disassembly.lua @@ -2,12 +2,14 @@ local config = require("dapui.config") local Canvas = require("dapui.render.canvas") local util = require("dapui.util") +---@return dapui.elements.disassembly +--- Create the Disassembly Buffer interface and return it. +--- return function(client) local dapui = { elements = {} } - local auto_commands = {} - ---@class dapui.elements.disassembly - --- Displays the Assembly code of the current frame, if supported by the client adapter. + --- @class dapui.elements.disassembly + --- Displays the Assembly code of the current frame, if supported by the client. dapui.elements.disassembly = {} local send_ready = util.create_render_loop(function() From aa957264fae36287396f71a33516673553cebaa3 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Mon, 13 Nov 2023 16:20:26 -0800 Subject: [PATCH 12/21] Changed to a single canvas:write call --- lua/dapui/components/disassembly.lua | 50 +++++++++++----------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua index 356f9108..f45a5666 100644 --- a/lua/dapui/components/disassembly.lua +++ b/lua/dapui/components/disassembly.lua @@ -100,8 +100,8 @@ end --- ---@param instructions dapui.types.DisassembledInstruction[] --- Each of the instructions to query for individual element lengths. ----@return string[] ---- # The raw disassembly lines to display, later. +---@return string +--- The raw disassembly lines to display, later. --- local function _get_column_aligned(instructions) local spacing = "" @@ -112,20 +112,17 @@ local function _get_column_aligned(instructions) spacing = _get_spacing(config.disassembly.instruction_spacing) end - local output = {} + local output = "" local column_justified_template = _get_alignment_template(instructions) for _, instruction in ipairs(instructions) do - table.insert( - output, - string.format( - column_justified_template, - instruction.address, - spacing, - instruction.instructionBytes, - spacing, - instruction.instruction - ) + output = output .. string.format( + column_justified_template, + instruction.address, + spacing, + instruction.instructionBytes, + spacing, + instruction.instruction ) end @@ -191,7 +188,7 @@ end --- ---@param instructions dapui.types.DisassembledInstruction[] --- Each of the instructions to query for individual element lengths. ----@return string[] +---@return string --- The raw disassembly lines to display, later. --- local function _get_lines(instructions) @@ -208,19 +205,16 @@ local function _get_lines(instructions) spacing = _get_spacing(config.disassembly.instruction_spacing) end - local output = {} + local output = "" for _, instruction in ipairs(instructions) do - table.insert( - output, - string.format( - "%s%s%s%s%s\n", - instruction.address, - spacing, - instruction.instructionBytes, - spacing, - instruction.instruction - ) + output = output .. string.format( + "%s%s%s%s%s\n", + instruction.address, + spacing, + instruction.instructionBytes, + spacing, + instruction.instruction ) end @@ -429,7 +423,6 @@ return function(client, buffer, send_ready) _setup_auto_commands(buffer, state, instruction_counter, send_ready) - -- TODO: Add configuration option from "Visual" to something else vim.api.nvim_set_hl( 0, _SELECTION_HIGHLIGHT_GROUP, @@ -485,10 +478,7 @@ return function(client, buffer, send_ready) vim.api.nvim_buf_clear_namespace(buffer, _VIRTUAL_SELECTION, 0, -1) - -- TODO: Consider writing a single blob of text - for _, line in ipairs(_get_lines(instructions)) do - canvas:write(line) - end + canvas:write(_get_lines(instructions)) if _adjust_direction_down ~= nil then ---@type integer From a15363f824054868ed2803c6a8c76b00475f68eb Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Mon, 13 Nov 2023 16:22:40 -0800 Subject: [PATCH 13/21] Did more TODO notes --- lua/dapui/components/disassembly.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua index f45a5666..5504c693 100644 --- a/lua/dapui/components/disassembly.lua +++ b/lua/dapui/components/disassembly.lua @@ -451,7 +451,7 @@ return function(client, buffer, send_ready) vim.api.nvim_win_set_cursor(window, {cursor_row, 0}) _highlight_current_instruction(buffer_, cursor_row) end, - 200 -- TODO: Tune this value, later + 20 -- A small enough number that won't risk triggering `_reset_cursor` 2+ times ) return { From 3b7d0f651667de5700de137c5393141e248d1514 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Mon, 13 Nov 2023 16:49:00 -0800 Subject: [PATCH 14/21] General code clean-up --- lua/dapui/components/disassembly.lua | 34 ++++++++++++++++++++-------- lua/dapui/config/highlights.lua | 1 + 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua index 5504c693..7c6b92bb 100644 --- a/lua/dapui/components/disassembly.lua +++ b/lua/dapui/components/disassembly.lua @@ -3,7 +3,7 @@ local util = require("dapui.util") local _GROUP = vim.api.nvim_create_augroup("NvimDapUiDisassembly", { clear = false}) -local _SELECTION_HIGHLIGHT_GROUP = "NvimDapUiDisassemblyHighlightLine" +local _SELECTION_HIGHLIGHT_GROUP = "DapUIDisassemblyHighlightLine" local _VIRTUAL_SELECTION = vim.api.nvim_create_namespace("NvimDapUiDisassemblyVirtualSelection") ---@class _DiassemblyInstructionCounter @@ -423,12 +423,6 @@ return function(client, buffer, send_ready) _setup_auto_commands(buffer, state, instruction_counter, send_ready) - vim.api.nvim_set_hl( - 0, - _SELECTION_HIGHLIGHT_GROUP, - config.disassembly.styles.current_frame or {link="Visual"} - ) - local on_exit = function() -- Remove auto-commands as needed vim.api.nvim_clear_autocmds({buffer=buffer, group=_GROUP}) @@ -445,6 +439,13 @@ return function(client, buffer, send_ready) client.listen.exited(on_exit) client.listen.terminated(on_exit) + local _force_cursor = _debounce_leading( + function(window, cursor_row) + vim.api.nvim_win_set_cursor(window, {cursor_row, 0}) + end, + 20 -- A small enough number that won't risk triggering `_force_cursor` 2+ times + ) + local _reset_cursor = _debounce_leading( function(window, buffer_, cursor_row) state.should_reset_the_cursor = false @@ -456,6 +457,7 @@ return function(client, buffer, send_ready) return { render = function(canvas) + -- Reset any state that needs to be reset local _adjust_direction_down = state.adjust_direction_down state.adjust_direction_down = nil @@ -476,10 +478,24 @@ return function(client, buffer, send_ready) return end + -- Set-up the canvas for writing vim.api.nvim_buf_clear_namespace(buffer, _VIRTUAL_SELECTION, 0, -1) - canvas:write(_get_lines(instructions)) + -- Now that the canvas is updated, set up per-line mappings + local current_instruction_line = _get_computed_instruction_line() + + for line = 1, canvas:length() do + canvas:add_mapping( + "expand", + function() + _force_cursor(window, current_instruction_line) + end, + {line=line} + ) + end + + -- Since the canvas updated, the user's current cursor may be out of date. Fix it if _adjust_direction_down ~= nil then ---@type integer local offset @@ -492,7 +508,6 @@ return function(client, buffer, send_ready) local current_row = vim.api.nvim_win_get_cursor(window)[1] local new_row = current_row + offset - local current_instruction_line = _get_computed_instruction_line() -- Important: You need to schedule this or the line will not highlight correctly vim.schedule( @@ -504,7 +519,6 @@ return function(client, buffer, send_ready) elseif state.should_reset_the_cursor then instruction_counter.count = height * 2 instruction_counter.offset = -1 * height - local current_instruction_line = _get_computed_instruction_line() _reset_cursor(window, buffer, current_instruction_line) end end diff --git a/lua/dapui/config/highlights.lua b/lua/dapui/config/highlights.lua index d226cfb7..93b5286a 100644 --- a/lua/dapui/config/highlights.lua +++ b/lua/dapui/config/highlights.lua @@ -28,6 +28,7 @@ function M.setup() hi default DapUILineNumber guifg=#00f1f5 hi default link DapUIFloatNormal NormalFloat hi default DapUIFloatBorder guifg=#00F1F5 + hi default link DapUIDisassemblyHighlightLine Visual hi default DapUIWatchesEmpty guifg=#F70067 hi default DapUIWatchesValue guifg=#A9FF68 hi default DapUIWatchesError guifg=#F70067 From 8355c89127665d8f1185ac2418a63118cd8c4b23 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 14 Nov 2023 01:23:46 -0800 Subject: [PATCH 15/21] Removed old TODO notes --- lua/dapui/components/disassembly.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua index 7c6b92bb..f3953498 100644 --- a/lua/dapui/components/disassembly.lua +++ b/lua/dapui/components/disassembly.lua @@ -145,15 +145,14 @@ end --- The found instructions, if any. --- local function _get_instructions(client, memory_reference, instruction_counter) - ---@source https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disassemble ---@type dapui.types.DisassembleResponse local response local success + ---@source https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disassemble success, response = pcall( client.request.disassemble, { - -- TODO: Finish this part. Add more spec details memoryReference=memory_reference, instructionOffset=instruction_counter.offset, instructionCount=instruction_counter.count, From e6464de234199d1007b97b600e6fef677a972aa3 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Sun, 19 Nov 2023 19:09:22 -0800 Subject: [PATCH 16/21] Added better "current line indicator" for the Disassembly view --- README.md | 23 +++++++++++++++++++++++ lua/dapui/components/disassembly.lua | 18 ++++++------------ lua/dapui/init.lua | 5 +++++ 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9d98e887..15d8774c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,26 @@ +- Disassembly parse, allow any contents inside of a comment if we can + +- need error recovery when the user goes too far up or down +- New action (not . Use another action, maybe) + +--- Fix this bug + If error - stepping outside of the code window breaks things + Debug adapter reported a frame at line 8 column 1, but: Cursor position outside buffer. Ensure executable is up2date and if us + ing a source mapping ensure it is correct +- Make PR + - Suggest a tree-sitter-disassembly parser + - Copying the line is weird. Why? + - Ask about the weir coloring if it can be turned off + - Moving the cursor left/right in a line looks weird. Fix? + +https://github.com/microsoft/debug-adapter-protocol/issues/200 +:lua require("dap").session():request("readMemory", {memoryReference="0x0000000000400015", count=1000}, function(error, result +) print(vim.inspect(result)) end) + +- https://github.com/RaafatTurki/hex.nvim/issues/15 + - Could be a good idea to have a hex viewer + + # nvim-dap-ui ## Introduction diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua index f3953498..e6ae73ae 100644 --- a/lua/dapui/components/disassembly.lua +++ b/lua/dapui/components/disassembly.lua @@ -307,17 +307,12 @@ end --- A 1-or-more value indicating the cursor line to highlight. --- local function _highlight_current_instruction(buffer, row) - vim.api.nvim_buf_set_extmark( - buffer, - _VIRTUAL_SELECTION, - row - 1, - 0, - { - end_line = row, - end_col = 0, - hl_group = _SELECTION_HIGHLIGHT_GROUP, - hl_mode = "blend", - } + vim.fn.sign_place( + _VIRTUAL_SELECTION, + "", + "DapUIDisassemblyCurrentLineSign", + buffer, + { lnum = row } ) end @@ -478,7 +473,6 @@ return function(client, buffer, send_ready) end -- Set-up the canvas for writing - vim.api.nvim_buf_clear_namespace(buffer, _VIRTUAL_SELECTION, 0, -1) canvas:write(_get_lines(instructions)) -- Now that the canvas is updated, set up per-line mappings diff --git a/lua/dapui/init.lua b/lua/dapui/init.lua index 91260f93..a6956a9f 100644 --- a/lua/dapui/init.lua +++ b/lua/dapui/init.lua @@ -102,6 +102,11 @@ function dapui.setup(user_config) elements[module] = elem end + vim.fn.sign_define( + "DapUIDisassemblyCurrentLineSign", + { texthl = "DapUIDisassemblyHighlightLine", text = "►" } + ) + local element_buffers = {} for name, elem in pairs(elements) do element_buffers[name] = elem.buffer From 8a262d15358a10b16462d83b8ba87e901bb65dc4 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Sun, 3 Dec 2023 14:04:18 -0800 Subject: [PATCH 17/21] Added WIP tree-sitter disassembly support --- README.md | 81 ++++++++++++++++++++++++++---- lua/dapui/elements/disassembly.lua | 3 ++ 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 15d8774c..714a049c 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,83 @@ -- Disassembly parse, allow any contents inside of a comment if we can +- Need error recovery when the user goes too far up or down +- Consider a different action other than -- need error recovery when the user goes too far up or down -- New action (not . Use another action, maybe) ---- Fix this bug - If error - stepping outside of the code window breaks things - Debug adapter reported a frame at line 8 column 1, but: Cursor position outside buffer. Ensure executable is up2date and if us - ing a source mapping ensure it is correct - Make PR - - Suggest a tree-sitter-disassembly parser - Copying the line is weird. Why? - - Ask about the weir coloring if it can be turned off + - Ask about the weird coloring if it can be turned off - Moving the cursor left/right in a line looks weird. Fix? + +- New action for displaying memory under cursor + - Use treesitter to get the node under the cursor + - Read its address + - Do basically - lua require("my_custom.utilities.memory_test").dump_memory("0x0000000000400584") + - Make a pop-up, maybe? + - Make sure this window keeps a reference to that memory even when scopes change +- Disassembly HEX viewer + - https://github.com/microsoft/debug-adapter-protocol/issues/348 +- https://github.com/RaafatTurki/hex.nvim/issues/15 + - Could be a good idea to have a hex viewer + - /home/selecaoone/repositories/vimspector/python3/vimspector/code.py + + +--- Fix this bug + If error - stepping outside of the code window breaks things + Debug adapter reported a frame at line 8 column 1, but: Cursor position outside buffer. Ensure executable is up2date and if using a source mapping ensure it is correct + + https://github.com/microsoft/debug-adapter-protocol/issues/200 :lua require("dap").session():request("readMemory", {memoryReference="0x0000000000400015", count=1000}, function(error, result ) print(vim.inspect(result)) end) -- https://github.com/RaafatTurki/hex.nvim/issues/15 - - Could be a good idea to have a hex viewer +lua require("dap").session():request("readMemory", {memoryReference="0x0000000000400015", count=1000}, function(error, result +) print(vim.inspect(result)) end) + +lua require("dap").session():request("readMemory", {memoryReference="0x7fffffffcf78", count=1024}, function(error, result) print(vim.inspect(result)) end) + + + + + + + + + + + + + + + + + + + + + + + +When scrolling down far, you get this error + +``` +Rendering failed: .../bundle/nvim-dap-ui/lua/dapui/components/disassembly.lua:67: attempt to get length of field 'instructionB +ytes' (a nil value) +stack traceback: + ...sonal/.config/nvim/bundle/nvim-dap-ui/lua/dapui/util.lua:17: in function '__len' + .../bundle/nvim-dap-ui/lua/dapui/components/disassembly.lua:67: in function '_get_alignment_template' + .../bundle/nvim-dap-ui/lua/dapui/components/disassembly.lua:116: in function '_get_lines' + .../bundle/nvim-dap-ui/lua/dapui/components/disassembly.lua:476: in function 'render' + ...im/bundle/nvim-dap-ui/lua/dapui/elements/disassembly.lua:28: in function 'render' + ...im/bundle/nvim-dap-ui/lua/dapui/elements/disassembly.lua:16: in function <...im/bundle/nvim-dap-ui/lua/dapui/elemen +ts/disassembly.lua:15> + [C]: in function 'xpcall' + ...sonal/.config/nvim/bundle/nvim-dap-ui/lua/dapui/util.lua:16: in function <...sonal/.config/nvim/bundle/nvim-dap-ui/ +lua/dapui/util.lua:12> +``` + +Have a fallback in mind + + # nvim-dap-ui diff --git a/lua/dapui/elements/disassembly.lua b/lua/dapui/elements/disassembly.lua index 63ae3c63..0223651e 100644 --- a/lua/dapui/elements/disassembly.lua +++ b/lua/dapui/elements/disassembly.lua @@ -20,6 +20,9 @@ return function(client) filetype = "dapui_disassembly", })() + -- TODO: Check if this exists first, before setting it + vim.treesitter.start(buffer, "disassembly") + local disassembly = require("dapui.components.disassembly")(client, buffer, send_ready) ---@nodoc From 0c9861ca68bcbd141e8701fdb86d29335aedf00c Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Sun, 3 Dec 2023 19:03:24 -0800 Subject: [PATCH 18/21] Added error recovery to the Disassembly window --- README.md | 4 ++- lua/dapui/components/disassembly.lua | 43 ++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 714a049c..5ca4df96 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -- Need error recovery when the user goes too far up or down +local w = require('dap.ui.widgets'); w.sidebar(w.sessions, nil, '5 sp').open(); +- Use this to show how to make new pop-up widgets + - Consider a different action other than diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua index e6ae73ae..b2269a9e 100644 --- a/lua/dapui/components/disassembly.lua +++ b/lua/dapui/components/disassembly.lua @@ -29,6 +29,28 @@ local _VIRTUAL_SELECTION = vim.api.nvim_create_namespace("NvimDapUiDisassemblyVi --- A function that, when called, starts in `start` milliseconds, --- stops at `timeout`, and all the while calls `function`. + +--- Check if `instructions` has any invalid portions. +--- +--- This function typically returns `true` whenever `instruction_counter.count` +--- is too high. When that happens, the `instructions` has at least one element +--- that has incomplete contents. +--- +---@param instructions dapui.types.DisassembledInstruction[] +--- Each of the instructions to query for individual element lengths. +---@return boolean +--- If `instructions` is complete, return `true. Otherwise, return `false`. +--- +local function _is_valid(instructions) + for _, instruction in ipairs(instructions) do + if instruction.instructionBytes == nil then + return false + end + end + + return true +end + --- Figure out the spacing needed for every column in `instructions`. --- --- Each instruction address has potentially varying lengths of memory addresses, @@ -43,7 +65,7 @@ local _VIRTUAL_SELECTION = vim.api.nvim_create_namespace("NvimDapUiDisassemblyVi ---@param instructions dapui.types.DisassembledInstruction[] --- Each of the instructions to query for individual element lengths. ---@return string ---- # The recommended template that will provided aligned columns. +--- The recommended template that will provided aligned columns. --- local function _get_alignment_template(instructions) local address_max = 1 @@ -449,6 +471,12 @@ return function(client, buffer, send_ready) 20 -- A small enough number that won't risk triggering `_reset_cursor` 2+ times ) + ---@type _DiassemblyInstructionCounter + local previous_instruction_counter = { + count = nil, + offset = nil, + } + return { render = function(canvas) -- Reset any state that needs to be reset @@ -468,8 +496,17 @@ return function(client, buffer, send_ready) local instructions = _get_instructions(client, memory_reference, instruction_counter) - if instructions == nil then - return + if instructions == nil or not _is_valid(instructions) then + -- Important: we `- height` because `_is_valid` tends to only be true when + -- the count is too high. By backing off a bit, we ensure that the window + -- will still be in-bounds. + -- + instruction_counter.count = previous_instruction_counter.count - height + instruction_counter.offset = previous_instruction_counter.offset + instructions = _get_instructions(client, memory_reference, instruction_counter) + else + previous_instruction_counter.count = instruction_counter.count + previous_instruction_counter.offset = instruction_counter.offset end -- Set-up the canvas for writing From 754f00ee2d61e7e6a26f227db130d9e44220f785 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Sun, 3 Dec 2023 19:06:10 -0800 Subject: [PATCH 19/21] Removed old TODO notes --- README.md | 84 ------------------------------------------------------- 1 file changed, 84 deletions(-) diff --git a/README.md b/README.md index 5ca4df96..9d98e887 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,3 @@ -local w = require('dap.ui.widgets'); w.sidebar(w.sessions, nil, '5 sp').open(); -- Use this to show how to make new pop-up widgets - -- Consider a different action other than - - -- Make PR - - Copying the line is weird. Why? - - Ask about the weird coloring if it can be turned off - - Moving the cursor left/right in a line looks weird. Fix? - - -- New action for displaying memory under cursor - - Use treesitter to get the node under the cursor - - Read its address - - Do basically - lua require("my_custom.utilities.memory_test").dump_memory("0x0000000000400584") - - Make a pop-up, maybe? - - Make sure this window keeps a reference to that memory even when scopes change -- Disassembly HEX viewer - - https://github.com/microsoft/debug-adapter-protocol/issues/348 -- https://github.com/RaafatTurki/hex.nvim/issues/15 - - Could be a good idea to have a hex viewer - - /home/selecaoone/repositories/vimspector/python3/vimspector/code.py - - ---- Fix this bug - If error - stepping outside of the code window breaks things - Debug adapter reported a frame at line 8 column 1, but: Cursor position outside buffer. Ensure executable is up2date and if using a source mapping ensure it is correct - - -https://github.com/microsoft/debug-adapter-protocol/issues/200 -:lua require("dap").session():request("readMemory", {memoryReference="0x0000000000400015", count=1000}, function(error, result -) print(vim.inspect(result)) end) - -lua require("dap").session():request("readMemory", {memoryReference="0x0000000000400015", count=1000}, function(error, result -) print(vim.inspect(result)) end) - -lua require("dap").session():request("readMemory", {memoryReference="0x7fffffffcf78", count=1024}, function(error, result) print(vim.inspect(result)) end) - - - - - - - - - - - - - - - - - - - - - - - -When scrolling down far, you get this error - -``` -Rendering failed: .../bundle/nvim-dap-ui/lua/dapui/components/disassembly.lua:67: attempt to get length of field 'instructionB -ytes' (a nil value) -stack traceback: - ...sonal/.config/nvim/bundle/nvim-dap-ui/lua/dapui/util.lua:17: in function '__len' - .../bundle/nvim-dap-ui/lua/dapui/components/disassembly.lua:67: in function '_get_alignment_template' - .../bundle/nvim-dap-ui/lua/dapui/components/disassembly.lua:116: in function '_get_lines' - .../bundle/nvim-dap-ui/lua/dapui/components/disassembly.lua:476: in function 'render' - ...im/bundle/nvim-dap-ui/lua/dapui/elements/disassembly.lua:28: in function 'render' - ...im/bundle/nvim-dap-ui/lua/dapui/elements/disassembly.lua:16: in function <...im/bundle/nvim-dap-ui/lua/dapui/elemen -ts/disassembly.lua:15> - [C]: in function 'xpcall' - ...sonal/.config/nvim/bundle/nvim-dap-ui/lua/dapui/util.lua:16: in function <...sonal/.config/nvim/bundle/nvim-dap-ui/ -lua/dapui/util.lua:12> -``` - -Have a fallback in mind - - - - # nvim-dap-ui ## Introduction From 17d3aeb6a8e33bd8a7b1adec143017b3b73be0d9 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Sun, 3 Dec 2023 19:16:59 -0800 Subject: [PATCH 20/21] Completed more TODO notes --- doc/nvim-dap-ui.txt | 5 ++--- lua/dapui/components/disassembly.lua | 5 +++++ lua/dapui/config/init.lua | 10 +++++----- lua/dapui/elements/console.lua | 2 +- lua/dapui/elements/disassembly.lua | 10 ++++++++-- lua/dapui/elements/scopes.lua | 2 +- lua/dapui/elements/watches.lua | 2 +- 7 files changed, 23 insertions(+), 13 deletions(-) diff --git a/doc/nvim-dap-ui.txt b/doc/nvim-dap-ui.txt index d74e20ca..6617e88f 100644 --- a/doc/nvim-dap-ui.txt +++ b/doc/nvim-dap-ui.txt @@ -331,10 +331,9 @@ Alias~ `dapui.FloatingAction` → `"close"` ============================================================================== -dapui.elements.disassembly *dapui.elements.disassembly* +dapui.elements.disassembly *dapui.elements.disassembly* -Displays the Assembly code of the current frame, if supported by the client -adapter. +Displays instruction Assembly code, if supported by the client adapter. ============================================================================== diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua index b2269a9e..999807da 100644 --- a/lua/dapui/components/disassembly.lua +++ b/lua/dapui/components/disassembly.lua @@ -1,3 +1,8 @@ +--- The implementation for the "DAP Disassembly" canvas. +--- +--- @module 'dapui.components.disassembly' +--- + local config = require("dapui.config") local util = require("dapui.util") diff --git a/lua/dapui/config/init.lua b/lua/dapui/config/init.lua index 10a2c629..e8e95120 100644 --- a/lua/dapui/config/init.lua +++ b/lua/dapui/config/init.lua @@ -106,12 +106,12 @@ local default_config = { -- Provide IDs as strings or tables with "id" and "size" keys { id = "scopes", - size = 0.20, -- Can be float or integer > 1 + size = 0.25, -- Can be float or integer > 1 }, - { id = "breakpoints", size = 0.20 }, - { id = "disassembly", size = 0.20 }, - { id = "stacks", size = 0.20 }, - { id = "watches", size = 0.20 }, + { id = "breakpoints", size = 0.25 }, + { id = "disassembly", size = 0.25 }, + { id = "stacks", size = 0.25 }, + { id = "watches", size = 0.25 }, }, size = 40, position = "left", -- Can be "left" or "right" diff --git a/lua/dapui/elements/console.lua b/lua/dapui/elements/console.lua index 75bd3457..84af2da9 100644 --- a/lua/dapui/elements/console.lua +++ b/lua/dapui/elements/console.lua @@ -18,7 +18,7 @@ return function() if async.api.nvim_buf_is_valid(console_buf) then return console_buf end - console_buf = util.create_buffer("DAP Console dc", { filetype = "dapui_console" })() + console_buf = util.create_buffer("DAP Console", { filetype = "dapui_console" })() if vim.fn.has("nvim-0.7") == 1 then vim.keymap.set("n", "G", function() autoscroll = true diff --git a/lua/dapui/elements/disassembly.lua b/lua/dapui/elements/disassembly.lua index 0223651e..9aa00fe9 100644 --- a/lua/dapui/elements/disassembly.lua +++ b/lua/dapui/elements/disassembly.lua @@ -1,3 +1,8 @@ +--- Responsible for displaying the nvim-dap-ui Disassembly buffer. +--- +--- @module 'dapui.elements.disassembly' +--- + local config = require("dapui.config") local Canvas = require("dapui.render.canvas") local util = require("dapui.util") @@ -20,8 +25,9 @@ return function(client) filetype = "dapui_disassembly", })() - -- TODO: Check if this exists first, before setting it - vim.treesitter.start(buffer, "disassembly") + if vim.treesitter.language.get_lang("disassembly") ~= nil then + vim.treesitter.start(buffer, "disassembly") + end local disassembly = require("dapui.components.disassembly")(client, buffer, send_ready) diff --git a/lua/dapui/elements/scopes.lua b/lua/dapui/elements/scopes.lua index f29e38c8..c321ca34 100644 --- a/lua/dapui/elements/scopes.lua +++ b/lua/dapui/elements/scopes.lua @@ -30,7 +30,7 @@ return function(client) end ---@nodoc - dapui.elements.scopes.buffer = util.create_buffer("DAP Scopes ds", { + dapui.elements.scopes.buffer = util.create_buffer("DAP Scopes", { filetype = "dapui_scopes", }) diff --git a/lua/dapui/elements/watches.lua b/lua/dapui/elements/watches.lua index 39c83bda..e98e4251 100644 --- a/lua/dapui/elements/watches.lua +++ b/lua/dapui/elements/watches.lua @@ -71,7 +71,7 @@ return function(client) end ---@nodoc - dapui.elements.watches.buffer = util.create_buffer("DAP Watches dw", { + dapui.elements.watches.buffer = util.create_buffer("DAP Watches", { filetype = "dapui_watches", omnifunc = "v:lua.require'dap'.omnifunc", }) From 5ea49eaa2bff6a344ab1152cfdfc037a76238fd5 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Wed, 14 Feb 2024 23:46:13 -0800 Subject: [PATCH 21/21] Bug fix - A missing disassembly window no longer errors --- lua/dapui/components/disassembly.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lua/dapui/components/disassembly.lua b/lua/dapui/components/disassembly.lua index 999807da..e34f04ac 100644 --- a/lua/dapui/components/disassembly.lua +++ b/lua/dapui/components/disassembly.lua @@ -495,6 +495,11 @@ return function(client, buffer, send_ready) end local window = vim.fn.bufwinid(buffer) + + if window == -1 then + return + end + local height = vim.api.nvim_win_get_height(window) _initialize_counters(instruction_counter, height)