summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lua/inbox/init.lua2
-rw-r--r--lua/inbox/renderers.lua47
-rw-r--r--lua/inbox/renderers/headers.lua39
-rw-r--r--lua/inbox/renderers/nvim.lua19
-rw-r--r--lua/inbox/renderers/w3m.lua29
-rw-r--r--lua/inbox/types.lua12
-rw-r--r--lua/inbox/view.lua246
-rw-r--r--selene.toml1
8 files changed, 252 insertions, 143 deletions
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<inbox.ContentType, string|inbox.Renderer>|nil
+---@field custom_renderers table<string, inbox.Renderer>|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", "<C-h>", require("inbox").toggle_headers, { buffer = bufnr })
+ vim.keymap.set("n", "<C-h>", 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", "<C-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"]