Skip to content

Commit

Permalink
feat(core): add clearing log statements action (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
Goose97 authored Nov 25, 2024
1 parent a5ce3b4 commit a2faec8
Show file tree
Hide file tree
Showing 8 changed files with 336 additions and 71 deletions.
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,6 @@ See [RECIPES](https://github.com/Goose97/timber.nvim/blob/main/doc/RECIPES.md) g

## Usage

`timber.nvim` has two core operations: inserting log statements and capturing log results.

### Insert log statements

There are two kinds of log statements:
Expand Down Expand Up @@ -109,6 +107,22 @@ The content of the log statement is specified via templates. See [`:h timber.nvi
print("LOG 1 foo", foo)
```

### Clear log statements

Clear all log statements in the current buffer:

```lua
require("timber.actions").clear_log_statements({ global = false })
```

or from all buffers:

```lua
require("timber.actions").clear_log_statements({ global = true })
```

Be aware of [potential limitations](https://github.com/Goose97/timber.nvim/blob/main/doc/timber.nvim.txt).

### Capture log results

`timber.nvim` can monitor multiple sources and capture the log results. For example, a common use case is to capture the log results from a test runner or from a log file.
Expand Down
25 changes: 23 additions & 2 deletions doc/timber.nvim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ Users can invoke actions via the API. Action APIs are defined in the
:h timber.actions.insert_log
:h timber.actions.insert_batch_log
:h timber.actions.add_log_targets_to_batch
:h timber.actions.clear_log_statements


actions.insert_log({opts}) *timber.actions.insert_log()*
Expand Down Expand Up @@ -224,18 +225,38 @@ actions.add_log_targets_to_batch({opts}) *timber.actions.add_log_target
|timber.actions.insert_batch_log|


actions.get_batch_size({opts}) *timber.actions.get_batch_size()*
actions.get_batch_size() *timber.actions.get_batch_size()*

Get the number of log targets in the batch.

Return: ~
- integer


actions.clear_batch({opts}) *timber.actions.clear_batch()*
actions.clear_batch() *timber.actions.clear_batch()*

Clear all log targets in the batch.


actions.clear_log_statements({opts}) *timber.actions.clear_log_statements()*

Clear all log statements in the current buffer or all buffers.

This API has some limitations:
- It might not work well with code formatters. The code
formatter can break the log statements into multiple lines and we
have no mechanism to keep track of them.
- With `global = true`, we will clear all buffers, but not all files. Which
means files that you haven't loaded won't be cleared.

Parameters: ~
{opts} (table) options to pass to the action

Options: ~
{global} (boolean) whether to clear all buffers (true)
or just the current one (false).
Default: false

==============================================================================
3. Watchers *timber.nvim-watchers*

Expand Down
3 changes: 1 addition & 2 deletions lua/timber.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ local M = {}
---@param config Timber.InitConfig?
M.setup = function(config)
config_mod.setup(config)

actions.setup()
highlight.setup()

buffers.setup()

if config_mod.config.log_watcher.enabled then
watcher.setup(config_mod.config.log_watcher.sources)
end
Expand Down
36 changes: 27 additions & 9 deletions lua/timber/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -231,15 +231,13 @@ local function after_insert_log_statements(log_statements, insert_cursor_pos, or

-- Add the log placeholder to the buffer manager
for _, log_statement in ipairs(log_statements) do
if log_statement.placeholder_id then
buffers.new_log_placeholder({
id = log_statement.placeholder_id,
bufnr = vim.api.nvim_get_current_buf(),
-- TODO: support multi line log statements
line = log_statement.inserted_rows[1],
entries = {},
})
end
buffers.new_log_placeholder({
id = log_statement.placeholder_id,
bufnr = vim.api.nvim_get_current_buf(),
-- TODO: support multi line log statements
line = log_statement.inserted_rows[1],
entries = {},
})
end
end

Expand Down Expand Up @@ -722,6 +720,26 @@ function M.clear_batch()
M.batch = {}
end

---@class Timber.Actions.ClearLogStatementsOptions
---@field global? boolean Whether to clear all buffers, or just the current buffer. Defaults to `false`
---@param opts Timber.Actions.ClearLogStatementsOptions?
function M.clear_log_statements(opts)
opts = vim.tbl_deep_extend("force", { global = false }, opts or {})
local lines_per_bufnr = buffers.get_log_statement_lines(not opts.global and vim.api.nvim_get_current_buf() or nil)

for bufnr, lines in pairs(lines_per_bufnr) do
-- Sort lines in descending order to avoid line number shifting
table.sort(lines, function(a, b)
return a > b
end)

-- Delete each line
for _, line_num in ipairs(lines) do
vim.api.nvim_buf_set_lines(bufnr, line_num, line_num + 1, false, {})
end
end
end

function M.setup()
treesitter.setup()
end
Expand Down
141 changes: 109 additions & 32 deletions lua/timber/buffers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,82 @@ local utils = require("timber.utils")
---@field timestamp integer

---@class Timber.Buffers.LogPlaceholder
---@field id Timber.Watcher.LogPlaceholderId
---@field id Timber.Watcher.LogPlaceholderId? The log placeholder id. It is only set when the log template has %watcher_marker_start
---@field bufnr number
---@field line number 0-indexed line number. The line number is only correct when the placeholder is newly created. Overtime, after updates, the real line number will be shifted.
---@field extmark_id? integer
---@field entries Timber.Buffers.LogPlaceholderEntries[]

---@alias Timber.Buffers.LogPlaceholderRegistry table<Timber.Watcher.LogPlaceholderId, Timber.Buffers.LogPlaceholder>
---@class Timber.Buffers.LogPlaceholderRegistry
---@field placeholders Timber.Buffers.LogPlaceholder[] List of log placeholders
local LogPlaceholderRegistry = {}
function LogPlaceholderRegistry.new()
local o = {
placeholders = {},
}

setmetatable(o, LogPlaceholderRegistry)
LogPlaceholderRegistry.__index = LogPlaceholderRegistry
return o
end

---@class Timber.Buffers
---@field log_placeholders Timber.Buffers.LogPlaceholderRegistry
---@field seen_buffers integer[] Buffers has been opened and processed
---@field attached_buffers integer[] Buffers currently being attached to
---@field pending_log_entries Timber.Watcher.LogEntry[] Log entries that didn't have a corresponding placeholder. They will be processed once the placeholder is created
---@field placeholder_render_timer any Timer to keep updating the placeholder snippet
local M = { log_placeholders = {}, seen_buffers = {}, attached_buffers = {}, pending_log_entries = {} }
local M = {
log_placeholders = LogPlaceholderRegistry.new(),
seen_buffers = {},
attached_buffers = {},
pending_log_entries = {},
}

---Find a log placeholder by id
---@param id Timber.Watcher.LogPlaceholderId
---@return Timber.Buffers.LogPlaceholder?
function LogPlaceholderRegistry:get(id)
for _, placeholder in ipairs(self.placeholders) do
if placeholder.id == id then
return placeholder
end
end
end

---Add a log placeholder to the registry. Noop if the placeholder already exists
---@param placeholder Timber.Buffers.LogPlaceholder
function LogPlaceholderRegistry:add(placeholder)
local existing = placeholder.id and self:get(placeholder.id) or nil
if not existing then
table.insert(self.placeholders, placeholder)
end
end

---Remove a log placeholder
---@param id Timber.Watcher.LogPlaceholderId
function LogPlaceholderRegistry:remove(id)
self.placeholders = utils.array_filter(self.placeholders, function(placeholder)
return placeholder.id ~= id
end)
end

---Remove a log placeholder by extmark id. The extmark id is unique to a buffer so we need to pass in bufnr as well
---@param extmark_id integer
---@param bufnr integer
function LogPlaceholderRegistry:remove_by_extmark_id(extmark_id, bufnr)
self.placeholders = utils.array_filter(self.placeholders, function(placeholder)
return placeholder.extmark_id ~= extmark_id or placeholder.bufnr ~= bufnr
end)
end

---Return all log placeholders in a buffer
---@param bufnr integer
function LogPlaceholderRegistry:buffer_placeholders(bufnr)
return utils.array_filter(self.placeholders, function(placeholder)
return placeholder.bufnr == bufnr
end)
end

---@param line string
---@return string? placeholder_id
Expand All @@ -36,7 +97,7 @@ end
local function process_line(content, bufnr, line)
local placeholder_id = parse_log_placeholder(content)

if placeholder_id and M.log_placeholders[placeholder_id] == nil then
if placeholder_id and not M.log_placeholders:get(placeholder_id) then
vim.schedule(function()
M.new_log_placeholder({ id = placeholder_id, bufnr = bufnr, line = line, entries = {} })
end)
Expand Down Expand Up @@ -149,26 +210,6 @@ local function detach_buffer(bufnr)
end
end

---@param extmark_id integer
---@param bufnr integer
local function delete_placeholder_by_extmark_id(extmark_id, bufnr)
local buf_remain_placeholders = 0

for placeholder_id, placeholder in pairs(M.log_placeholders) do
if placeholder.bufnr == bufnr then
if placeholder.extmark_id == extmark_id then
M.log_placeholders[placeholder_id] = nil
else
buf_remain_placeholders = buf_remain_placeholders + 1
end
end
end

if buf_remain_placeholders == 0 then
detach_buffer(bufnr)
end
end

local function on_lines(_, bufnr, _, first_line, last_line, new_last_line, _)
-- local index = utils.array_find_index(M.attached_buffers, function(v)
-- return v == bufnr
Expand Down Expand Up @@ -208,15 +249,20 @@ local function on_lines(_, bufnr, _, first_line, last_line, new_last_line, _)
return true
end

return mark[1] ~= M.log_placeholders[line_placeholder].extmark_id
return mark[1] ~= M.log_placeholders:get(line_placeholder).extmark_id
end)

for _, mark in ipairs(marks) do
local mark_id = mark[1]

vim.schedule(function()
vim.api.nvim_buf_del_extmark(bufnr, M.log_placeholder_ns, mark_id)
delete_placeholder_by_extmark_id(mark_id, bufnr)
M.log_placeholders:remove_by_extmark_id(mark_id, bufnr)

local remaining = M.log_placeholders:buffer_placeholders(bufnr)
if #remaining == 0 then
detach_buffer(bufnr)
end
end)
end
end
Expand All @@ -240,7 +286,7 @@ end
---Callback for log entry received
--- @param entry Timber.Watcher.LogEntry
function M.receive_log_entry(entry)
local log_placeholder = M.log_placeholders[entry.log_placeholder_id]
local log_placeholder = M.log_placeholders:get(entry.log_placeholder_id)

if log_placeholder then
table.insert(
Expand All @@ -259,15 +305,15 @@ end

---@param log_placeholder Timber.Buffers.LogPlaceholder
function M.new_log_placeholder(log_placeholder)
if M.log_placeholders[log_placeholder.id] then
if log_placeholder.id and M.log_placeholders:get(log_placeholder.id) then
return
end

local extmark_id =
vim.api.nvim_buf_set_extmark(log_placeholder.bufnr, M.log_placeholder_ns, log_placeholder.line, 0, {})

log_placeholder.extmark_id = extmark_id
M.log_placeholders[log_placeholder.id] = log_placeholder
M.log_placeholders:add(log_placeholder)
attach_buffer(log_placeholder.bufnr)

-- Check the pending log entries and process ones targeting this placeholder
Expand Down Expand Up @@ -380,7 +426,7 @@ function M.open_float(opts)
return
end

local placeholder = M.log_placeholders[placeholder_id]
local placeholder = M.log_placeholders:get(placeholder_id)
if placeholder then
show_placeholder_full_content(placeholder, { silent = opts.silent })
else
Expand All @@ -389,8 +435,39 @@ function M.open_float(opts)
end

local function update_placeholders_snippet()
for _, log_placeholder in pairs(M.log_placeholders) do
render_placeholder_snippet(log_placeholder)
for _, i in pairs(M.log_placeholders.placeholders) do
render_placeholder_snippet(i)
end
end

---Get all log statement line numbers in the current buffer or all buffers
---@param bufnr number? Buffer number. If not provided, return results for all buffers
---@return table<integer, integer[]> lines_per_bufnr 0-indexed line numbers grouped by buffers
function M.get_log_statement_lines(bufnr)
local grouped = utils.array_group_by(M.log_placeholders.placeholders, function(placeholder)
return placeholder.bufnr
end)

local to_lines = function(items)
local lines = {}
for _, placeholder in ipairs(items) do
local pos =
vim.api.nvim_buf_get_extmark_by_id(placeholder.bufnr, M.log_placeholder_ns, placeholder.extmark_id, {})
table.insert(lines, pos[1])
end

return lines
end

if bufnr then
return grouped[bufnr] and { [bufnr] = to_lines(grouped[bufnr]) } or {}
else
local result = {}
for _bufnr, _placeholders in pairs(grouped) do
result[_bufnr] = to_lines(_placeholders)
end

return result
end
end

Expand Down
Loading

0 comments on commit a2faec8

Please sign in to comment.