From 803f43266acf932bb02b169c091d28474250ad08 Mon Sep 17 00:00:00 2001 From: Willians Faria Date: Tue, 20 Aug 2024 00:31:31 -0300 Subject: [PATCH] feat: adding a command runner for ledger reports --- README.md | 15 +++++++++ lua/ledger/commands.lua | 69 ++++++++++++++++++++++++++++++++++---- lua/ledger/config.lua | 28 ++++++++++++++-- lua/ledger/diagnostics.lua | 32 +++++++++--------- lua/ledger/init.lua | 2 +- 5 files changed, 121 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 0e576b5..75d71f1 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ to your liking. new_posting_today = { "td" }, new_commodity = { "cm" }, }, + reports = {} }, diagnostics = { lsp_diagnostics = true, @@ -92,6 +93,19 @@ to your liking. - The `snippets` section defines keymaps or triggers for snippets. If you're using native as your snippet engine, this will set the actual keymap in Neovim. You can disable a snippet by either removing it from the list or removing its triggers. + - The `reports` section defines keymaps for running ledger reports, it will define + a keymap that runs the given ledger command string. Techinically, anything that + outputs to stdout could be used here. + + Define your own reports by filling in the reports keymaps like this: + ```lua + keymaps = { + reports = { + { key = "bal", name = "Balance", command = "ledger --strict -f main.ledger bal" }, + { key = "bud", name = "Budget", command = "ledger --strict -f main.ledger budget" }, + } + } + ``` - The `diagnostics` field lets you customize how diagnostics work in Ledger: - `lsp_diagnostics` sets diagnostics using vim.diagnostic.set, so it works like an LSP diagnostic, populating your workspace diagnostics. @@ -105,6 +119,7 @@ to your liking. - Autocompletion for account names, powered by nvim-cmp - Snippets for common actions (creating postings, accounts, commodities, etc.) - Diagnostics for undeclared commodities and accounts +- Running user defined reports without leaving neovim - And a few other things that I still have to code ## Related projects diff --git a/lua/ledger/commands.lua b/lua/ledger/commands.lua index 646216a..5913c31 100644 --- a/lua/ledger/commands.lua +++ b/lua/ledger/commands.lua @@ -1,15 +1,72 @@ +local files = require("ledger.files") + local M = {} --- @class LedgerCommands --- @field augroup integer +--- @field output_augroup integer local LedgerCommands = {} LedgerCommands.__index = LedgerCommands ---- @type LedgerCommands -local instance - -function LedgerCommands:create_augroup() +function LedgerCommands:create_augroups() self.augroup = vim.api.nvim_create_augroup("Ledger", {}) + self.output_augroup = vim.api.nvim_create_augroup("LedgerOutput", {}) +end + +--- @param buf integer +function LedgerCommands:set_up_output_buffer(buf) + vim.api.nvim_buf_create_user_command(buf, "LedgerOutputClose", function() + vim.api.nvim_buf_delete(buf, { force = true, unload = true }) + end, {}) + + vim.api.nvim_buf_set_keymap(buf, "n", "q", ":LedgerOutputClose", {}) + + vim.api.nvim_create_autocmd("BufLeave", { + group = self.output_augroup, + buffer = buf, + callback = function() + vim.cmd("LedgerOutputClose") + end, + }) +end + +--- @param command string +function LedgerCommands:run_report(command) + local cwd = files.cwd() + local command_args = vim.split(command, " ") + + vim.cmd("split") + vim.cmd("enew") + + local buf = vim.api.nvim_get_current_buf() + + vim.bo[buf].buftype = "nofile" + vim.bo[buf].swapfile = false + vim.bo[buf].bufhidden = "wipe" + self:set_up_output_buffer(buf) + + local ok, result = pcall(vim.system, command_args, { cwd = cwd }) + + if not ok then + local message = { + "Something went wrong. Failed to run report command:", + command, + "", + "With stdout message:", + result, + } + vim.api.nvim_buf_set_lines(buf, 0, -1, false, message) + return + end + + local result_output = result:wait().stdout + if not result_output then + error("ledger command should always return output") + return + end + vim.api.nvim_buf_set_lines(buf, 0, -1, false, vim.split(result_output, "\n")) + + vim.api.nvim_set_option_value("modifiable", false, { buf = buf }) end function LedgerCommands:setup_autocommands() @@ -52,8 +109,8 @@ function LedgerCommands:setup_autocommands() end function M.setup() - instance = setmetatable({}, LedgerCommands) - return instance + local self = setmetatable({}, LedgerCommands) + return self end return M diff --git a/lua/ledger/config.lua b/lua/ledger/config.lua index 44fb19d..9ab7615 100644 --- a/lua/ledger/config.lua +++ b/lua/ledger/config.lua @@ -6,8 +6,18 @@ local M = {} --- @field new_posting_today string[] --- @field new_commodity string[] +--- @class ledger.ReportKeymap +--- @field name string +--- @field key string +--- @field command string + --- @class ledger.Keymaps --- @field snippets ledger.SnippetKeymaps +--- @field reports ledger.ReportKeymap[] + +--- @class ledger.PartialKeymaps +--- @field snippets? ledger.SnippetKeymaps +--- @field reports? ledger.ReportKeymap[] --- @class ledger.CompletionSource --- @field enabled boolean @@ -27,7 +37,7 @@ local M = {} --- @field extensions string[]? --- @field completion ledger.Completion? --- @field snippets ledger.Snippet? ---- @field keymaps ledger.Keymaps? +--- @field keymaps ledger.PartialKeymaps? --- @field diagnostics ledger.Diagnostics? --- @class ledger.Diagnostics @@ -44,6 +54,9 @@ local M = {} local LedgerConfig = {} LedgerConfig.__index = LedgerConfig +--- @type ledger.Config +local instance = nil + --- @class ledger.Snippet local LedgerConfigSnippets = {} LedgerConfigSnippets.__index = LedgerConfigSnippets @@ -130,14 +143,22 @@ local function get_default_config() new_posting_today = { "td" }, new_commodity = { "cm" }, }, + reports = {}, }, } return default_config end ---- @type ledger.Config -local instance = nil +--- set keymaps for running reports +function LedgerConfig:set_keymaps() + local commands = require("ledger.commands").setup() + for _, keymap in pairs(self.keymaps.reports) do + vim.keymap.set("n", keymap.key, function() + commands:run_report(keymap.command) + end) + end +end --- Config is a singleton, allowing us to call `get` as many times as we --- want and always getting the same instance, so we don't have to pass @@ -150,6 +171,7 @@ function M.setup(overrides) local default = get_default_config() local with_overrides = vim.tbl_deep_extend("force", default, overrides or {}) instance = setmetatable(with_overrides, LedgerConfig) + instance:set_keymaps() end return instance end diff --git a/lua/ledger/diagnostics.lua b/lua/ledger/diagnostics.lua index 6d100e7..4bece5a 100644 --- a/lua/ledger/diagnostics.lua +++ b/lua/ledger/diagnostics.lua @@ -57,24 +57,26 @@ function M.get_missing_commodities() for filename, postings in pairs(context.postings) do for _, posting in pairs(postings) do - local commodity_name = posting.commodity.text - local has_commodity = false - - for _, accounts in pairs(context.commodities) do - if vim.tbl_contains(accounts, commodity_name) then - has_commodity = true + if posting.commodity then + local commodity_name = posting.commodity.text + local has_commodity = false + + for _, accounts in pairs(context.commodities) do + if vim.tbl_contains(accounts, commodity_name) then + has_commodity = true + end end - end - if not has_commodity then - if not missing_accounts[filename] then - missing_accounts[filename] = {} + if not has_commodity then + if not missing_accounts[filename] then + missing_accounts[filename] = {} + end + table.insert(missing_accounts[filename], { + filename = filename, + text = commodity_name, + range = posting.account.range, + }) end - table.insert(missing_accounts[filename], { - filename = filename, - text = commodity_name, - range = posting.account.range, - }) end end end diff --git a/lua/ledger/init.lua b/lua/ledger/init.lua index 0bb3b83..e9c1d77 100644 --- a/lua/ledger/init.lua +++ b/lua/ledger/init.lua @@ -33,7 +33,7 @@ function M.setup(overrides) end local commands = require("ledger.commands").setup() - commands:create_augroup() + commands:create_augroups() commands:setup_autocommands() local context = require("ledger.context").new(files.cwd())