127 lines
3.5 KiB
Lua
127 lines
3.5 KiB
Lua
-- Helper function to extract clean email address
|
|
local function extract_email(address_string)
|
|
-- Match email in angle brackets: "Name <email@example.com>"
|
|
local email = address_string:match("<([^>]+)>")
|
|
if email then
|
|
return email
|
|
end
|
|
|
|
-- Match standalone email: "email@example.com"
|
|
email = address_string:match("([%w%._%+-]+@[%w%._%+-]+)")
|
|
if email then
|
|
return email
|
|
end
|
|
|
|
-- Fallback: return as-is if no pattern matches
|
|
return address_string
|
|
end
|
|
|
|
-- Option 2: Omnifunc completion
|
|
function _G.mail_complete(findstart, base)
|
|
if findstart == 1 then
|
|
-- Find start of word
|
|
local line = vim.api.nvim_get_current_line()
|
|
local col = vim.fn.col(".")
|
|
local line_to_cursor = line:sub(1, col - 1)
|
|
|
|
-- Only on header lines
|
|
if not line_to_cursor:match("^%s*[ToCcBcFrom]+:%s*") then
|
|
return -1
|
|
end
|
|
|
|
-- Find start of current address
|
|
local start = line_to_cursor:reverse():find("[%s,]")
|
|
if start then
|
|
return col - start
|
|
else
|
|
return line_to_cursor:find(":") or 0
|
|
end
|
|
else
|
|
-- Get completions
|
|
local handle = io.popen(string.format('abook --mutt-query "%s" 2>/dev/null | tail -n +2', base))
|
|
if not handle then
|
|
return {}
|
|
end
|
|
|
|
local result = handle:read("*a")
|
|
handle:close()
|
|
|
|
local matches = {}
|
|
for address in result:gmatch("[^\r\n]+") do
|
|
if address ~= "" then
|
|
-- Extract clean email only
|
|
local clean_email = extract_email(address)
|
|
table.insert(matches, clean_email)
|
|
end
|
|
end
|
|
|
|
return matches
|
|
end
|
|
end
|
|
|
|
-- Set omnifunc
|
|
vim.bo.omnifunc = "v:lua.mail_complete"
|
|
|
|
-- Map Tab to trigger completion on header lines
|
|
vim.keymap.set("i", "<Tab>", function()
|
|
local line = vim.api.nvim_get_current_line()
|
|
if line:match("^%s*[ToCcBcFrom]+:%s*") then
|
|
return "<C-x><C-o>"
|
|
else
|
|
return "<Tab>"
|
|
end
|
|
end, { expr = true, buffer = true })
|
|
|
|
-- Option 3: Interactive fzf picker
|
|
local function pick_email()
|
|
-- Get all addresses
|
|
local handle = io.popen('abook --mutt-query "" 2>/dev/null | tail -n +2')
|
|
if not handle then
|
|
vim.notify("Could not query abook", vim.log.levels.ERROR)
|
|
return
|
|
end
|
|
|
|
local addresses = {}
|
|
local display_addresses = {}
|
|
|
|
for line in handle:lines() do
|
|
if line ~= "" then
|
|
local clean_email = extract_email(line)
|
|
table.insert(addresses, clean_email)
|
|
-- Show full format in picker, but insert clean email
|
|
table.insert(display_addresses, line .. " → " .. clean_email)
|
|
end
|
|
end
|
|
handle:close()
|
|
|
|
if #addresses == 0 then
|
|
vim.notify("No addresses in abook", vim.log.levels.WARN)
|
|
return
|
|
end
|
|
|
|
-- Use vim.ui.select (works with fzf-lua if installed)
|
|
vim.ui.select(display_addresses, {
|
|
prompt = "Select recipient:",
|
|
format_item = function(item)
|
|
return item
|
|
end,
|
|
}, function(choice, idx)
|
|
if choice and idx then
|
|
local email = addresses[idx]
|
|
-- Insert at cursor
|
|
local row, col = unpack(vim.api.nvim_win_get_cursor(0))
|
|
local line = vim.api.nvim_get_current_line()
|
|
local new_line = line:sub(1, col) .. email .. line:sub(col + 1)
|
|
vim.api.nvim_set_current_line(new_line)
|
|
-- Move cursor after inserted text
|
|
vim.api.nvim_win_set_cursor(0, { row, col + #email })
|
|
end
|
|
end)
|
|
end
|
|
|
|
-- Map Ctrl+f to pick email (Option 3)
|
|
vim.keymap.set("i", "<C-f>", pick_email, { buffer = true, desc = "Pick email address with fzf" })
|
|
|
|
-- Optional: Map Ctrl+a for Tab completion manually (in case Tab doesn't work)
|
|
vim.keymap.set("i", "<C-a>", "<C-x><C-o>", { buffer = true, desc = "Trigger address completion" })
|