From c3980bbd432ce50aaf351d5af3ab9e63bfda99a9 Mon Sep 17 00:00:00 2001 From: Toby Vincent Date: Thu, 23 May 2024 12:20:51 -0500 Subject: wip --- lua/inbox/init.lua | 2 + lua/inbox/renderers.lua | 47 ++++++++ lua/inbox/renderers/headers.lua | 39 +++++++ lua/inbox/renderers/nvim.lua | 19 ++++ lua/inbox/renderers/w3m.lua | 29 +++++ lua/inbox/types.lua | 12 +- lua/inbox/view.lua | 246 +++++++++++++++++----------------------- selene.toml | 1 + 8 files changed, 252 insertions(+), 143 deletions(-) create mode 100644 lua/inbox/renderers.lua create mode 100644 lua/inbox/renderers/headers.lua create mode 100644 lua/inbox/renderers/nvim.lua create mode 100644 lua/inbox/renderers/w3m.lua create mode 100644 selene.toml diff --git a/lua/inbox/init.lua b/lua/inbox/init.lua index bce9020..ad6c00d 100644 --- a/lua/inbox/init.lua +++ b/lua/inbox/init.lua @@ -1,5 +1,6 @@ local config = require("inbox.config") local indexers = require("inbox.indexers") +local renderers = require("inbox.renderers") local utils = require("inbox.utils") local view = require("inbox.view") @@ -126,6 +127,7 @@ end function M.setup(opts) config.setup(opts) indexers.setup(config) + renderers.setup(config) for sign, value in pairs(config.flags) do local name = utils.sign_name(sign) diff --git a/lua/inbox/renderers.lua b/lua/inbox/renderers.lua new file mode 100644 index 0000000..ef34382 --- /dev/null +++ b/lua/inbox/renderers.lua @@ -0,0 +1,47 @@ +local utils = require("inbox.utils") + +local M = { + _renderers = setmetatable({}, { + __index = function(_, k) + if k == "headers" then + return require("inbox.renderers.headers") + else + return require("inbox.renderers.nvim") + end + end, + }), +} + +---@param content_type inbox.ContentType +---@return inbox.Renderer +function M.get_renderer(content_type) + return M._renderers[content_type] +end + +---@param opts inbox.Config +function M.setup(opts) + if opts.renderers == nil then + return + end + + for content_type, renderer in pairs(opts.renderers) do + if type(renderer) == "string" then + local is_ok, result = pcall(require, string.format("inbox.renderers.%s", renderer)) + if is_ok then + renderer = result + else + utils.error("Renderer not found", { renderer = renderer }) + end + end + + if + type(renderer.available) == "nil" + or (type(renderer.available) == "function" and renderer.available()) + or (type(renderer.available) == "boolean" and renderer.available) + then + M._renderers[content_type] = renderer + end + end +end + +return M diff --git a/lua/inbox/renderers/headers.lua b/lua/inbox/renderers/headers.lua new file mode 100644 index 0000000..991dd9f --- /dev/null +++ b/lua/inbox/renderers/headers.lua @@ -0,0 +1,39 @@ +local M = {} + +function M.render(bufnr) + local entry = vim.b[bufnr].inbox.entry + + local lines = {} + + local headers = {} + if vim.b[bufnr].show_all_headers then + headers = entry.headers + else + for _, name in pairs(vim.b[bufnr].inbox.headers) do + if entry.headers[name] ~= nil then + headers[name] = entry.headers[name] + end + end + end + + for name, value in pairs(headers) do + local i, line = next(value) + table.insert(lines, ("%s: %s"):format(name, line)) + i, line = next(value, i) + while i ~= nil do + table.insert(lines, line) + i, line = next(value, i) + end + end + + table.insert(lines, "") + + vim.bo[bufnr].filetype = "mail" + vim.bo[bufnr].syntax = "mail" + vim.bo[bufnr].modifiable = true + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines) + vim.bo[bufnr].modifiable = false + vim.bo[bufnr].modified = false +end + +return M diff --git a/lua/inbox/renderers/nvim.lua b/lua/inbox/renderers/nvim.lua new file mode 100644 index 0000000..223ab4e --- /dev/null +++ b/lua/inbox/renderers/nvim.lua @@ -0,0 +1,19 @@ +---@type inbox.Renderer +local M = {} + +function M.render(bufnr) + local entry = vim.b[bufnr].inbox.entry + local content_type = vim.b[bufnr].inbox.content_type + local part = entry.parts[content_type] + + local lines = vim.split(part.content, "\n") + + vim.bo[bufnr].filetype = "mail" + vim.bo[bufnr].syntax = "mail" + vim.bo[bufnr].modifiable = true + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines) + vim.bo[bufnr].modifiable = false + vim.bo[bufnr].modified = false +end + +return M diff --git a/lua/inbox/renderers/w3m.lua b/lua/inbox/renderers/w3m.lua new file mode 100644 index 0000000..bd7e33b --- /dev/null +++ b/lua/inbox/renderers/w3m.lua @@ -0,0 +1,29 @@ +---@type inbox.Renderer +local M = {} + +function M.render(bufnr) + local entry = vim.b[bufnr].inbox.entry + local content_type = vim.b[bufnr].inbox.content_type + vim.api.nvim_buf_call(bufnr, function() + local job_id = vim.fn.termopen({ + "socksify", + "w3m", + "-I", + "UTF-8", + "-T", + "text/html", + "-cols", + tostring(vim.bo[bufnr].textwidth), + "-dump", + "-o", + "display_image=false", + "-o", + "display_link_number=true", + }) + + local part = entry.parts[content_type] + vim.fn.chansend(job_id, part.content) + end) +end + +return M diff --git a/lua/inbox/types.lua b/lua/inbox/types.lua index 3913699..1366c80 100644 --- a/lua/inbox/types.lua +++ b/lua/inbox/types.lua @@ -1,4 +1,4 @@ ----@class inbox.Config: inbox.Indexer.Config +---@class inbox.Config: inbox.Indexer.Config, inbox.Renderer.Config ---@field default_maildir string|nil ---@field buf_options table|nil ---@field win_options table|nil @@ -9,7 +9,7 @@ ---@class inbox.Indexer ---@field setup? fun(opts: table) ----@field available? fun(): boolean +---@field available? boolean | fun(): boolean ---@field index? fun(maildir: string, cb: fun(ids: string[], entries: inbox.Summary[], signs: (string | integer)[][]), opts?: table) ---@field get_entry? fun(id: string): inbox.Entry|nil ---@field get_parts? fun(id: string): inbox.ContentType[] @@ -18,6 +18,14 @@ ---@field indexer string|nil ---@field indexer_config table|nil +---@class inbox.Renderer +---@field render? fun(bufnr: integer): integer +---@field available? boolean | fun(): boolean + +---@class inbox.Renderer.Config +---@field renderers table|nil +---@field custom_renderers table|nil + ---@class inbox.PartFilter ---@field command string ---@field args string[] diff --git a/lua/inbox/view.lua b/lua/inbox/view.lua index e204e70..8fdb893 100644 --- a/lua/inbox/view.lua +++ b/lua/inbox/view.lua @@ -1,5 +1,6 @@ local config = require("inbox.config") local indexers = require("inbox.indexers") +local renderers = require("inbox.renderers") local utils = require("inbox.utils") local M = {} @@ -21,39 +22,6 @@ function M.create_buffer(path, buf_options) return bufnr end -function M.render_buffer(bufnr) - local maildir, id = utils.parse_scheme(bufnr) - - if maildir == nil then - vim.notify(("Buffer is not a valid maildir buffer: %s"):format(bufnr), vim.log.levels.ERROR) - elseif id ~= nil and id ~= "" then - M.render_entry(bufnr, id) - else - M.render_inbox(bufnr, maildir) - end -end - ----@param bufnr integer ----@param start integer ----@param end_ integer ----@param strict_indexing boolean ----@param lines string[]? ----@param escape_ascii boolean? -function M.buf_set_lines(bufnr, start, end_, strict_indexing, lines, escape_ascii) - local buf_writer - if escape_ascii and pcall(require, "baleia") then - local baleia = require("baleia").setup() - buf_writer = baleia.buf_set_lines - else - buf_writer = vim.api.nvim_buf_set_lines - end - - vim.bo[bufnr].modifiable = true - buf_writer(bufnr, start, end_, strict_indexing, lines or {}) - vim.bo[bufnr].modifiable = false - vim.bo[bufnr].modified = false -end - ---@param maildir string ---@return integer bufnr function M.initialize_inbox(maildir) @@ -105,13 +73,30 @@ end ---@param content_type string ---@return integer? bufnr function M.initialize_entry(maildir, id, content_type) - local bufname = ("%s:%s"):format(maildir, id) - local bufnr = M.create_buffer(bufname, { - syntax = "mail", - filetype = "mail", - }) + local bufnr = vim.api.nvim_create_buf(true, false) + + local bufname = ("maildir://%s:%s"):format(maildir, id) + vim.api.nvim_buf_set_name(bufnr, bufname) - vim.api.nvim_clear_autocmds({ buffer = bufnr, group = require("inbox").augroup }) + vim.b[bufnr].inbox = { + id = id, + content_type = content_type, + } + + M.render_entry(bufnr, id, content_type) + + vim.api.nvim_clear_autocmds({ buffer = bufnr, group = "Inbox" }) + vim.api.nvim_create_autocmd({ "BufModifiedSet", "BufWinEnter" }, { + group = "Inbox", + buffer = bufnr, + callback = function() + for _, winid in pairs(vim.fn.win_findbuf(bufnr)) do + local textoff = vim.fn.getwininfo(winid)[1].textoff + local winbar = string.format("%sPart: %s", string.rep(" ", textoff), vim.b[bufnr].inbox.content_type) + vim.api.nvim_set_option_value("winbar", winbar, { scope = "local", win = winid }) + end + end, + }) vim.api.nvim_create_autocmd("BufDelete", { group = "Inbox", @@ -125,147 +110,126 @@ function M.initialize_entry(maildir, id, content_type) vim.api.nvim_buf_delete(bufnr, {}) end, { buffer = bufnr }) - vim.keymap.set("n", "", require("inbox").toggle_headers, { buffer = bufnr }) + vim.keymap.set("n", "", function() + if not vim.b[bufnr].inbox.header_bufnr then + vim.b[bufnr].inbox.header_bufnr = M.initialize_headers(id) + end + local bufinfo = vim.fn.getbufinfo(vim.b[bufnr].inbox.header_bufnr) + if not bufinfo or bufinfo[1].hidden ~= 1 then + vim.cmd.split() + vim.cmd.wincmd("k") + vim.api.nvim_set_current_buf(vim.b[bufnr].inbox.header_bufnr) + end + end, { buffer = bufnr }) + vim.keymap.set("n", "", function() local k - if vim.b[bufnr].inbox_content_type then - k = next(vim.b[bufnr].inbox_parts, vim.b[bufnr].inbox_content_type) + if vim.b[bufnr].inbox.content_type then + k = next(vim.b[bufnr].inbox.parts, vim.b[bufnr].inbox.content_type) else - k = next(vim.b[bufnr].inbox_parts) + k = next(vim.b[bufnr].inbox.parts) end M.render_entry(bufnr, id, k) end, { buffer = bufnr }) - vim.b[bufnr].inbox_id = id - vim.b[bufnr].header_filter = config.headers + return bufnr +end - M.render_headers(bufnr) - M.render_entry(bufnr, id, content_type) +function M.render_entry(bufnr, id, content_type) + local entry = indexers.get_indexer().get_entry(id) - vim.api.nvim_create_autocmd({ "BufModifiedSet", "BufWinEnter" }, { + if entry == nil then + utils.error("Failed to get entry", { id = id }) + return + end + + if content_type == nil then + content_type = next(entry.parts) + end + + if entry.parts[content_type] == nil then + utils.error("Failed to find message part for entry", { id = id, ["content-type"] = content_type }) + return + end + + vim.b[bufnr].inbox = vim.tbl_deep_extend("force", vim.b[bufnr].inbox, { + entry = entry, + content_type = content_type, + }) + vim.print(vim.b[bufnr].inbox) + + local renderer = renderers.get_renderer(content_type) + renderer.render(bufnr) +end + +function M.initialize_headers(entry_bufnr, id) + local bufnr = vim.api.nvim_create_buf(true, false) + + vim.bo[bufnr].filetype = "mail" + vim.bo[bufnr].syntax = "mail" + + vim.b[bufnr].inbox = { + id = id, + headers = config.headers, + } + + vim.api.nvim_clear_autocmds({ buffer = bufnr, group = "Inbox" }) + vim.api.nvim_create_autocmd("BufDelete", { group = "Inbox", buffer = bufnr, callback = function() - for _, winid in pairs(vim.fn.win_findbuf(bufnr)) do - local winbar = string.format( - "%sPart: %s", - string.rep(" ", vim.fn.getwininfo(winid)[1].textoff), - vim.b[bufnr].inbox_content_type - ) - - vim.api.nvim_set_option_value("winbar", winbar, { - scope = "local", - win = winid, - }) - end + vim.b[entry_bufnr].inbox.header_bufnr = nil end, }) + vim.keymap.set("n", "q", function() + vim.api.nvim_buf_delete(bufnr, {}) + end, { buffer = bufnr }) + + M.render_headers(bufnr, id) + return bufnr end -function M.render_headers(bufnr) - local entry = indexers.get_indexer().get_entry(vim.b[bufnr].inbox_id) +---@return integer|nil bufnr +function M.render_headers(bufnr, id) + local entry = indexers.get_indexer().get_entry(id) if entry == nil then return end - local lines = {} - local headers = {} - if vim.b[bufnr].show_all_headers then + if vim.b[bufnr].inbox.show_all_headers then headers = entry.headers else - for _, name in pairs(config.headers) do + for _, name in pairs(vim.b[bufnr].inbox.headers) do if entry.headers[name] ~= nil then headers[name] = entry.headers[name] end end end - for name, value in pairs(headers) do - local i, line = next(value) - table.insert(lines, ("%s: %s"):format(name, line)) - i, line = next(value, i) + local lines = {} + for header_key, header_lines in pairs(headers) do + local i, header_line = next(header_lines) + table.insert(lines, ("%s: %s"):format(header_key, header_line)) + + i, header_line = next(header_lines, i) while i ~= nil do - table.insert(lines, line) - i, line = next(value, i) + table.insert(lines, header_line) + i, header_line = next(header_lines, i) end end table.insert(lines, "") - local cursor_pos = vim.fn.getpos(".") - - M.buf_set_lines(bufnr, 0, vim.b[bufnr].header_lines or 0, true, {}) - M.buf_set_lines(bufnr, 0, 0, true, lines) - - vim.b[bufnr].header_lines = #lines - - vim.fn.setpos(".", cursor_pos) -end - -function M.render_entry(bufnr, id, content_type) - local Job = require("plenary.job") - local entry = indexers.get_indexer().get_entry(id) - - if entry == nil then - utils.error("Failed to get entry", { id = id }) - return - end - - if content_type == nil then - content_type = next(entry.parts) - end - - local part = entry.parts[content_type] - - if part == nil then - utils.error("Failed to find message part for entry", { id = id, ["content-type"] = content_type }) - return - end - - vim.b[bufnr].inbox_parts = entry.parts - vim.b[bufnr].inbox_content_type = content_type - - if vim.tbl_isempty(config.filters[content_type] or {}) then - local lines = vim.split(part.content, "\n") - M.buf_set_lines(bufnr, vim.b[bufnr].header_lines or 0, -1, true, lines) - else - local job - - for i, filter in pairs(config.filters[content_type] or {}) do - for key, value in pairs(filter) do - if type(value) == "function" then - filter[key] = value(bufnr) - else - filter[key] = value - end - end - - local opts = { - command = filter.command, - args = filter.args, - } - - if job == nil then - opts.writer = part.content - else - opts.writer = job - end - - if i == #config.filters[content_type] then - opts.on_exit = vim.schedule_wrap(function(self) - M.buf_set_lines(bufnr, vim.b[bufnr].header_lines or 0, -1, true, self:result(), true) - end) - end - - job = Job:new(opts) - end + vim.bo[bufnr].modifiable = true + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines) + vim.bo[bufnr].modifiable = false + vim.bo[bufnr].modified = false - job:start() - end + return bufnr end return M diff --git a/selene.toml b/selene.toml new file mode 100644 index 0000000..d19f919 --- /dev/null +++ b/selene.toml @@ -0,0 +1 @@ +std = ["vim"] -- cgit v1.2.3-70-g09d2