diff --git a/lua/neolog/actions.lua b/lua/neolog/actions.lua index c90172d..6b91a11 100644 --- a/lua/neolog/actions.lua +++ b/lua/neolog/actions.lua @@ -1,6 +1,7 @@ ---@class NeologActions --- @field log_templates { [string]: NeologLogTemplates } -local M = {} +--- @field batches { [string]: TSNode[] } +local M = { log_templates = {}, batches = {} } local utils = require("neolog.utils") @@ -259,34 +260,18 @@ local function pick_best_node(nodes, selection_range) return best_node or nodes[#nodes] end ----@param log_template string ---@param lang string ----@param position LogPosition ----@return LogStatementInsert[] -local function build_capture_log_statements(log_template, lang, position) +---@return {log_container: TSNode, logable_range: logable_range?, log_targets: TSNode[]}[] +local function capture_log_targets(lang) local selection_range = utils.get_selection_range() - local log_containers = query_log_target_container(lang, selection_range) - ---@type LogStatementInsert[] - local to_insert = {} + local result = {} - for _, container in ipairs(log_containers) do - local log_targets = find_log_target(container.container, lang) - local logable_range = container.logable_range - - local insert_row - - if logable_range then - insert_row = logable_range[1] - else - if position == "above" then - insert_row = container.container:start() - else - insert_row = container.container:end_() + 1 - end - end + for _, log_container in ipairs(log_containers) do + local log_targets = find_log_target(log_container.container, lang) + -- Filter targets that intersect with the given range log_targets = utils.array_filter(log_targets, function(node) return utils.ranges_intersect(selection_range, utils.get_ts_node_range(node)) end) @@ -298,7 +283,33 @@ local function build_capture_log_statements(log_template, lang, position) return pick_best_node(group, selection_range) end) - -- Filter targets that intersect with the given range + table.insert(result, { + log_container = log_container.container, + logable_range = log_container.logable_range, + log_targets = log_targets, + }) + end + + return result +end + +---@param log_template string +---@param lang string +---@param position LogPosition +---@return LogStatementInsert[] +local function build_capture_log_statements(log_template, lang, position) + local to_insert = {} + + for _, entry in ipairs(capture_log_targets(lang)) do + local log_targets = entry.log_targets + local log_container = entry.log_container + local logable_range = entry.logable_range + local insert_row = logable_range and logable_range[1] + or ({ + above = log_container:start(), + below = log_container:end_() + 1, + })[position] + for _, log_target in ipairs(log_targets) do local content, insert_cursor_offset = build_log_statement(log_template, { identifier = function() @@ -306,7 +317,7 @@ local function build_capture_log_statements(log_template, lang, position) return vim.treesitter.get_node_text(log_target, bufnr) end, line_number = function() - return log_target:start() + 1 + return tostring(log_target:start() + 1) end, }) @@ -340,42 +351,53 @@ local function build_non_capture_log_statement(log_template, position) } end ----@class LogStatementInsert ----@field content string[] The log statement content ----@field row number The (0-indexed) row number to insert ----@field insert_cursor_offset number? The offset of the %insert_cursor placeholder if any ----@field log_target TSNode? The log target node - ---- @alias LogPosition "above" | "below" - ---- Insert log statement for the current identifier at the cursor ---- @class InsertLogOptions ---- @field template string? Which template to use. Defaults to `default` ---- @field position LogPosition -function M.insert_log(opts) - opts = vim.tbl_deep_extend("force", { template = "default" }, opts or {}) - +---@param template_set string +---@return string?, string? +local function get_lang_log_template(template_set) local lang = get_lang(vim.bo.filetype) if not lang then vim.notify("Cannot determine language for current buffer", vim.log.levels.ERROR) return end - local log_template_set = M.log_templates[opts.template] + local log_template_set = M.log_templates[template_set] if not log_template_set then - vim.notify(string.format("Log template '%s' is not found", opts.template), vim.log.levels.ERROR) + vim.notify(string.format("Log template '%s' is not found", template_set), vim.log.levels.ERROR) return end local log_template_lang = log_template_set[lang] if not log_template_lang then vim.notify( - string.format("Log template '%s' does not have '%s' language template", opts.template, lang), + string.format("Log template '%s' does not have '%s' language template", template_set, lang), vim.log.levels.ERROR ) return end + return log_template_lang, lang +end + +---@class LogStatementInsert +---@field content string[] The log statement content +---@field row number The (0-indexed) row number to insert +---@field insert_cursor_offset number? The offset of the %insert_cursor placeholder if any +---@field log_target TSNode? The log target node + +--- @alias LogPosition "above" | "below" + +--- Insert log statement for the current identifier at the cursor +--- @class InsertLogOptions +--- @field template string? Which template to use. Defaults to `default` +--- @field position LogPosition +--- @param opts InsertLogOptions +function M.insert_log(opts) + opts = vim.tbl_deep_extend("force", { template = "default" }, opts or {}) + local log_template_lang, lang = get_lang_log_template(opts.template) + if not log_template_lang or not lang then + return + end + -- There are two kinds of log statements: -- 1. Capture log statements: log statements that contain %identifier placeholder -- We need to capture the log target in the selection range and replace it @@ -389,6 +411,69 @@ function M.insert_log(opts) after_insert_log_statements(to_insert) end +---Add log target to the log batch +--- @class AddLogToBatchOptions +--- @field batch_name string? Which batch to add to. Defaults to `default` +function M.add_log_target_to_batch(batch_name) + batch_name = batch_name or "default" + + -- local log_template_lang, lang = get_lang_log_template(opts.template) + -- if not log_template_lang or not lang then + -- return + -- end + -- + -- if not log_template_lang:find("%%identifier") then + -- vim.notify("Batch log statements must include %identifier placeholder", vim.log.levels.ERROR) + -- return + -- end + + local lang = get_lang(vim.bo.filetype) + if not lang then + vim.notify("Cannot determine language for current buffer", vim.log.levels.ERROR) + return + end + + ---@type TSNode[] + local to_add = {} + + for _, entry in ipairs(capture_log_targets(lang)) do + for _, log_target in ipairs(entry.log_targets) do + table.insert(to_add, log_target) + end + end + + to_add = utils.array_sort_with_index(to_add, function(a, b) + local result = utils.compare_ts_node_start(a[1], b[1]) + return result == "equal" and a[2] < b[2] or result == "before" + end) + + ---@type TSNode[] + local batch = M.batches[batch_name] + if not batch then + batch = {} + M.batches[batch_name] = batch + end + + vim.list_extend(batch, to_add) +end + +---@param batch_name string? +function M.get_batch_size(batch_name) + batch_name = batch_name or "default" + local batch = M.batches[batch_name] + if not batch then + return 0 + end + + return #batch +end + +---@param batch_name string? +function M.clear_batch(batch_name) + batch_name = batch_name or "default" + M.batches[batch_name] = nil +end + -- Register the custom predicate ---@param templates { [string]: NeologLogTemplates } function M.setup(templates) diff --git a/lua/neolog/utils.lua b/lua/neolog/utils.lua index fccf8dc..534cd06 100644 --- a/lua/neolog/utils.lua +++ b/lua/neolog/utils.lua @@ -1,15 +1,5 @@ local M = {} -function M.array_includes(array, value) - for _, v in ipairs(array) do - if v == value then - return true - end - end - - return false -end - function M.array_find(array, predicate) for i, v in ipairs(array) do if predicate(v, i) then diff --git a/tests/neolog/actions/neolog_actions_spec.lua b/tests/neolog/actions/neolog_actions_spec.lua index 53024c5..3450aad 100644 --- a/tests/neolog/actions/neolog_actions_spec.lua +++ b/tests/neolog/actions/neolog_actions_spec.lua @@ -3,7 +3,7 @@ local helper = require("tests.neolog.helper") local actions = require("neolog.actions") local assert = require("luassert") -describe("neolog.actions", function() +describe("neolog.actions single log", function() before_each(function() neolog.setup() end) @@ -212,3 +212,48 @@ describe("neolog.actions", function() end) end) end) + +describe("neolog.actions batch log", function() + before_each(function() + neolog.setup() + end) + + it("supports adding log targets to the batch, getting batch size, and clearing batch", function() + local input = [[ + // Comment + const fo|o = "foo" + const bar = "bar" + const baz = "baz" + ]] + + -- Default batch is `default` + helper.assert_scenario({ + input = input, + filetype = "javascript", + action = function() + vim.cmd("normal! V2j") + actions.add_log_target_to_batch() + end, + expected = function() + assert.are.same(3, actions.get_batch_size()) + end, + }) + + helper.assert_scenario({ + input = input, + filetype = "javascript", + action = function() + vim.cmd("normal! V2j") + actions.add_log_target_to_batch("testing") + end, + expected = function() + assert.are.same(3, actions.get_batch_size("testing")) + end, + }) + + actions.clear_batch() + assert.are.same(0, actions.get_batch_size()) + actions.clear_batch("testing") + assert.are.same(0, actions.get_batch_size("testing")) + end) +end)