Skip to content

Commit

Permalink
Merge pull request #20 from hat0uma/fix_duplicate_parsing
Browse files Browse the repository at this point in the history
Fix: Proper detachment of `nvim_buf_attach` on toggling
  • Loading branch information
hat0uma authored Jan 9, 2025
2 parents dcd9052 + 9c4bfb7 commit b5e57ee
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 114 deletions.
70 changes: 70 additions & 0 deletions lua/csvview/buf.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
--- Buffer utilities
local M = {}

--- Resolve bufnr
---@param bufnr integer| nil
---@return integer
function M.resolve_bufnr(bufnr)
if not bufnr or bufnr == 0 then
return vim.api.nvim_get_current_buf()
else
return bufnr
end
end

--- Watch buffer-update events
---@param bufnr integer
---@param callbacks vim.api.keyset.buf_attach
---@return fun() detach_bufevent
function M.attach(bufnr, callbacks)
local detached = false
local function wrap_buf_attach_handler(cb)
if not cb then
return nil
end

return function(...)
if detached then
return true -- detach
end

return cb(...)
end
end

local function attach_events()
vim.api.nvim_buf_attach(bufnr, false, {
on_lines = wrap_buf_attach_handler(callbacks.on_lines),
on_bytes = wrap_buf_attach_handler(callbacks.on_bytes),
on_changedtick = wrap_buf_attach_handler(callbacks.on_changedtick),
on_reload = wrap_buf_attach_handler(callbacks.on_reload),
on_detach = wrap_buf_attach_handler(callbacks.on_detach),
})
end

-- Attach to buffer
attach_events()

-- Re-register events on `:e`
local buf_event_auid = vim.api.nvim_create_autocmd({ "BufReadPost" }, {
callback = function()
attach_events()
if callbacks.on_reload then
callbacks.on_reload("reload", bufnr)
end
end,
buffer = bufnr,
})

-- detach
return function()
if detached then
return
end

vim.api.nvim_del_autocmd(buf_event_auid)
detached = true
end
end

return M
41 changes: 0 additions & 41 deletions lua/csvview/buffer_event.lua

This file was deleted.

31 changes: 18 additions & 13 deletions lua/csvview/util.lua → lua/csvview/errors.lua
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
--- Error handling utilities
local M = {}

--- @class CsvView.Error
--- @field err string error message
--- @field stacktrace? string error stacktrace
--- @field [string] any additional context data

--- Wrap error with stacktrace for `xpcall`
---@param err string | table
---@return table
---@param err string|CsvView.Error|nil
---@return CsvView.Error
function M.wrap_stacktrace(err)
if type(err) == "table" then
return err
return vim.tbl_deep_extend("keep", err, { stacktrace = debug.traceback("", 2) })
else
return { err = err, stacktrace = debug.traceback("", 2) }
end
end

--- Propagate error with context
---@param err any
---@param context table | nil
---@param err string|CsvView.Error|nil
---@param context table<string,any>| nil
function M.error_with_context(err, context)
if type(err) == "table" then
err = vim.tbl_deep_extend("keep", err, context or {})
elseif type(err) == "string" then
if type(err) == "string" then
err = vim.tbl_deep_extend("keep", { err = err }, context or {})
elseif type(err) == "table" then
err = vim.tbl_deep_extend("keep", err, context or {})
end
error(err, 0)
end
Expand All @@ -27,23 +33,22 @@ end
---@param tbl table
---@param key string
---@return any
function M.tbl_remove_key(tbl, key)
local function tbl_remove_key(tbl, key)
local value = tbl[key] ---@type any
tbl[key] = nil ---@type any
return value
end

--- Print error message
---@param header string
---@param err string | table
--- @type fun(header: string, err: string|CsvView.Error|nil)
M.print_structured_error = vim.schedule_wrap(function(header, err)
--- @type string
local msg

if type(err) == "table" then
-- extract error message and stacktrace
local stacktrace = M.tbl_remove_key(err, "stacktrace") or "No stacktrace available"
local err_msg = M.tbl_remove_key(err, "err") or "An unspecified error occurred"
local stacktrace = tbl_remove_key(err, "stacktrace") or "No stacktrace available"
local err_msg = tbl_remove_key(err, "err") or "An unspecified error occurred"

-- format error message
msg = string.format(
Expand Down
52 changes: 25 additions & 27 deletions lua/csvview/init.lua
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
local M = {}

local CsvView = require("csvview.view").View
local get_view = require("csvview.view").get
local attach_view = require("csvview.view").attach
local detach_view = require("csvview.view").detach
local setup_view = require("csvview.view").setup

local CsvViewMetrics = require("csvview.metrics")
local buffer_event = require("csvview.buffer_event")
local buf = require("csvview.buf")
local config = require("csvview.config")
local view = require("csvview.view")

--- check if csv table view is enabled
---@param bufnr integer
---@return boolean
function M.is_enabled(bufnr)
return view.get(bufnr) ~= nil
return get_view(bufnr) ~= nil
end

--- enable csv table view
Expand All @@ -24,37 +29,32 @@ function M.enable(bufnr, opts)
return
end

-- Calculate metrics and attach view.
local detach_bufevent_handle --- @type fun()
local metrics = CsvViewMetrics:new(bufnr, opts)
metrics:compute_buffer(function()
view.attach(bufnr, metrics, opts)
local view = CsvView:new(bufnr, metrics, opts, function()
detach_bufevent_handle()
metrics:clear()
end)

-- Register buffer events.
buffer_event.register(bufnr, {
-- Register buffer-update events.
detach_bufevent_handle = buf.attach(bufnr, {
on_lines = function(_, _, _, first, last, last_updated)
-- detach if disabled
if not M.is_enabled(bufnr) then
return true
end

-- Recalculate only the difference.
metrics:update(first, last, last_updated)
end,

on_reload = function()
-- detach if disabled
if not M.is_enabled(bufnr) then
return true
end

-- Recalculate all fields.
view.detach(bufnr)
view:clear()
metrics:clear()
view:lock()
metrics:compute_buffer(function()
view.attach(bufnr, metrics, opts)
view:unlock()
end)
end,
})

-- Calculate metrics and attach view.
metrics:compute_buffer(function()
attach_view(bufnr, view)
end)
end

--- disable csv table view
Expand All @@ -66,9 +66,7 @@ function M.disable(bufnr)
return
end

-- Unregister buffer events and detach view.
buffer_event.unregister(bufnr)
view.detach(bufnr)
detach_view(bufnr)
end

--- toggle csv table view
Expand All @@ -87,7 +85,7 @@ end
---@param opts CsvView.Options?
function M.setup(opts)
config.setup(opts)
view.setup()
setup_view()
end

return M
12 changes: 6 additions & 6 deletions lua/csvview/parser.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
local M = {}
local util = require("csvview.util")
local errors = require("csvview.errors")

---@class Csvview.Parser.Callbacks
---@field on_line fun(lnum:integer,is_comment:boolean,fields:string[]) the callback to be called for each line
Expand Down Expand Up @@ -145,9 +145,9 @@ local function iter(startlnum, endlnum, bufnr, opts, cb)
-- iterate lines
local parsed_num = 0
for i = startlnum, endlnum do
local ok, err = xpcall(parse_line, util.wrap_stacktrace, i)
local ok, err = xpcall(parse_line, errors.wrap_stacktrace, i)
if not ok then
util.error_with_context(err, { lnum = i })
errors.error_with_context(err, { lnum = i })
end

-- yield every chunksize
Expand Down Expand Up @@ -178,16 +178,16 @@ function M.iter_lines_async(bufnr, startlnum, endlnum, cb, opts)

-- create coroutine to iterate lines
local co = coroutine.create(function() ---@async
local ok, err = xpcall(iter, util.wrap_stacktrace, startlnum, endlnum, bufnr, opts, cb)
local ok, err = xpcall(iter, errors.wrap_stacktrace, startlnum, endlnum, bufnr, opts, cb)
if not ok then
util.error_with_context(err, { startlnum = startlnum, endlnum = endlnum })
errors.error_with_context(err, { startlnum = startlnum, endlnum = endlnum })
end
end)

local function resume_co()
local ok, err = coroutine.resume(co)
if not ok then
util.print_structured_error("CsvView Error parsing buffer", err)
errors.print_structured_error("CsvView Error parsing buffer", err)
elseif coroutine.status(co) ~= "dead" then
vim.schedule(resume_co)
end
Expand Down
Loading

0 comments on commit b5e57ee

Please sign in to comment.