Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion autoload/notmuch.vim
Original file line number Diff line number Diff line change
@@ -1,17 +1,46 @@
let s:search_terms_list = [ "attachment:", "folder:", "id:", "mimetype:",
\ "property:", "subject:", "thread:", "date:", "from:", "lastmod:",
\ "path:", "query:", "tag:", "to:" ]
\ "path:", "query:", "tag:", "is:", "to:", "body:", "and ", "or ", "not " ]

function! notmuch#CompSearchTerms(ArgLead, CmdLine, CursorPos) abort
if match(a:ArgLead, "tag:") != -1
let l:tag_list = split(system('notmuch search --output=tags "*"'), '\n')
return "tag:" .. join(l:tag_list, "\ntag:")
endif
if match(a:ArgLead, "is:") != -1
let l:is_list = split(system('notmuch search --output=tags "*"'), '\n')
return "is:" .. join(l:is_list, "\nis:")
endif
if match(a:ArgLead, "mimetype:") != -1
let l:mimetype_list = ["application/", "audio/", "chemical/",
\ "font/", "image/", "inode/", "message/", "model/",
\ "multipart/", "text/", "video/"]
return "mimetype:" .. join(l:mimetype_list, "\nmimetype:")
endif
if match(a:ArgLead, "from:") != -1
let l:from_list = split(system('notmuch address "*"'), '\n')
return "from:" .. join(l:from_list, "\nfrom:")
endif
if match(a:ArgLead, "to:") != -1
let l:to_list = split(system('notmuch address "*"'), '\n')
return "to:" .. join(l:to_list, "\nto:")
endif
if match(a:ArgLead, "folder:") != -1
let l:folder_list = split(system('find ' .. g:notmuch_mailroot .. ' -type d -name cur -print0| sed -n -z "s|^' .. g:notmuch_mailroot .. '/*||p" | xargs -0 dirname | sort | uniq'), '\n')
return "folder:" .. join(l:folder_list, "\nfolder:")
endif
if match(a:ArgLead, "path:") != -1
let l:path_list = split(system('find ' .. g:notmuch_mailroot .. ' -type d -print0| sed -n -z "s|^' .. g:notmuch_mailroot .. '/*||p" | sort -z | uniq -z | tr "\0" "\n"'), '\n')
return "path:" .. join(l:path_list, "\npath:")
endif
return join(s:search_terms_list, "\n")
endfunction

function! notmuch#CompTags(ArgLead, CmdLine, CursorPos) abort
return system('notmuch search --output=tags "*"')
endfunction

function! notmuch#CompAddress(ArgLead, CmdLine, CursorPos) abort
return system('notmuch address "*"')
endfunction
" vim: tabstop=2:shiftwidth=2:expandtab
20 changes: 16 additions & 4 deletions ftplugin/notmuch-threads.vim
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,30 @@ let r = v:lua.require('notmuch.refresh')
let s = v:lua.require('notmuch.sync')
let tag = v:lua.require('notmuch.tag')

command -buffer -complete=custom,notmuch#CompTags -nargs=+ TagAdd :call tag.thread_add_tag("<args>")
command -buffer -complete=custom,notmuch#CompTags -nargs=+ TagRm :call tag.thread_rm_tag("<args>")
command -buffer -complete=custom,notmuch#CompTags -nargs=+ TagToggle :call tag.thread_toggle_tag("<args>")
command -buffer -range -complete=custom,notmuch#CompTags -nargs=+ TagAdd :call tag.thread_add_tag(<q-args>, <line1>, <line2>)
command -buffer -range -complete=custom,notmuch#CompTags -nargs=+ TagRm :call tag.thread_rm_tag(<q-args>, <line1>, <line2>)
command -buffer -range -complete=custom,notmuch#CompTags -nargs=+ TagToggle :call tag.thread_toggle_tag(<q-args>, <line1>, <line2>)
command -buffer -range DelThread :call tag.thread_add_tag("del", <line1>, <line2>) | :call tag.thread_rm_tag("inbox", <line1>, <line2>)

nmap <buffer> <silent> <CR> :call nm.show_thread()<CR>
nmap <buffer> <silent> r :call r.refresh_search_buffer()<CR>
nmap <buffer> <silent> q :bwipeout<CR>
nmap <buffer> <silent> % :call s.sync_maildir()<CR>
nmap <buffer> + :TagAdd
xmap <buffer> + :TagAdd
nmap <buffer> - :TagRm
xmap <buffer> - :TagRm
nmap <buffer> = :TagToggle
xmap <buffer> = :TagToggle
nmap <buffer> a :TagToggle inbox<CR>j
xmap <buffer> a :TagToggle inbox<CR>
nmap <buffer> A :TagRm inbox unread<CR>j
nmap <buffer> x :TagToggle unread<CR>j
xmap <buffer> A :TagRm inbox unread<CR>
nmap <buffer> x :TagToggle unread<CR>
xmap <buffer> x :TagToggle unread<CR>
nmap <buffer> f :TagToggle flagged<CR>j
xmap <buffer> f :TagToggle flagged<CR>
nmap <buffer> <silent> C :call v:lua.require('notmuch.send').compose()<CR>
nmap <buffer> dd :DelThread<CR>j
xmap <buffer> d :DelThread<CR>
nmap <buffer> <silent> D :lua require('notmuch.delete').purge_del()<CR>
7 changes: 6 additions & 1 deletion lua/notmuch/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ local C = {}
-- including keymaps. The defaults can be overridden with options `opts` passed
-- by the user in the `setup()` function.
C.defaults = function()
local name = vim.fn.system('notmuch config get user.name'):gsub('\n', '') or ''
local email = vim.fn.system('notmuch config get user.primary_email'):gsub('\n', '') or ''
local defaults = {
notmuch_db_path = os.getenv('HOME') .. '/Mail',
notmuch_db_path = vim.fn.system('notmuch config get database.path'):gsub('\n', ''),
from = name .. ' <' .. email .. '>',
maildir_sync_cmd = 'mbsync -a',
open_cmd = 'xdg-open',
logfile = nil,
sent_folder = nil,
keymaps = { -- This should capture all notmuch.nvim related keymappings
sendmail = '<C-g><C-g>',
},
Expand Down
31 changes: 31 additions & 0 deletions lua/notmuch/delete.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
local d = {}
local v = vim.api
local nm = require("notmuch")
local r = require("notmuch.refresh")

local confirm_purge = function()
-- remove keymap
vim.keymap.del("n", "DD", { buffer = true })
-- Confirm
local choice = v.nvim_call_function("confirm", {
"Purge deleted emails?",
"&Yes\n&No",
2, -- Default to no
})

if choice == 1 then
v.nvim_command("silent ! notmuch search --output=files --format=text0 tag:del and tag:/./ | xargs -0 rm")
v.nvim_command("silent ! notmuch new")
r.refresh_search_buffer()
end
end

d.purge_del = function()
nm.search_terms("tag:del and tag:/./")
-- Set keymap for purgin
vim.keymap.set("n", "DD", function()
confirm_purge()
end, { buffer = true })
end

return d
6 changes: 4 additions & 2 deletions lua/notmuch/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,11 @@ end
--
-- @param search string: search terms matching format from
-- `notmuch-search-terms(7)`
-- @param jumptothreadid string: jump to thread id after search
--
-- @usage
-- lua require('notmuch').search_terms('tag:inbox')
nm.search_terms = function(search)
nm.search_terms = function(search, jumptothreadid)
local num_threads_found = 0
if search == '' then
return nil
Expand All @@ -77,14 +78,15 @@ nm.search_terms = function(search)
v.nvim_buf_set_name(buf, search)
v.nvim_win_set_buf(0, buf)

local hint_text = "Hints: <Enter>: Open thread | q: Close | r: Refresh | %: Sync maildir | a: Archive | A: Archive and Read | +: Add tag | -: Remove tag | =: Toggle tag"
local hint_text = "Hints: <Enter>: Open thread | q: Close | r: Refresh | %: Sync maildir | a: Archive | A: Archive and Read | +/-/=: Add, remove, toggle tag | dd: Delete"
v.nvim_buf_set_lines(buf, 0, 2, false, { hint_text , "" })

-- Async notmuch search to make the UX non blocking
require('notmuch.async').run_notmuch_search(search, buf, function()
-- Completion logic
if vim.fn.getline(2) ~= '' then num_threads_found = vim.fn.line('$') - 1 end
print('Found ' .. num_threads_found .. ' threads')
vim.fn.search(jumptothreadid)
end)

-- Set cursor at head of buffer, declare filetype, and disable modifying
Expand Down
5 changes: 4 additions & 1 deletion lua/notmuch/refresh.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ local nm = require('notmuch')
-- -- Normally invoked by pressing `r` in the search results buffer
-- lua require('notmuch.refresh').refresh_search_buffer()
r.refresh_search_buffer = function()
local line = v.nvim_get_current_line()
local threadid = string.match(line, "%S+", 8)
local search = string.match(v.nvim_buf_get_name(0), '%a+:%C+')
v.nvim_command('bwipeout')
nm.search_terms(search)
nm.search_terms(search, threadid)
vim.fn.search(threadid)
end

-- Refreshes the thread view buffer
Expand Down
65 changes: 59 additions & 6 deletions lua/notmuch/send.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,37 @@ local v = vim.api

local config = require('notmuch.config')

-- Save a completed message in the sent folder
--
-- This inserts the email stored under `filename` in the notmuch library under
-- `folder`, and removes the tags `inbox` and `unread`, which are automatically
-- added by `notmuch insert`.
--
-- @param filename string: path to the email message you would like to send
--
-- @param folder string: folder name of location where to store the email
s.savemail = function(filename, folder)
os.execute("notmuch insert --folder=" .. folder .. " -inbox -unread < " .. filename)
end

-- Prompt confirmation for sending an email
--
-- This function utilizes vim's builtin `confirm()` to prompt the user and
-- confirm the action of sending an email. This is applicable for sending newly
-- composed mails or replies by passing the mail file path.
--
-- If the user specified a sent folder in the configuration
-- `config.options.sent_folder`, then this function will prepend two header
-- fields to the email before it is sent: `Date` and `Message-ID`. The former
-- is set to now, the latter to a random uuid @localhost. Then it sends the
-- email. On successful transmission, this function calls `savemail` to insert
-- the email in the notmuch library. If we would not add these header fields,
-- then the datetime will be unspecified, and the email will not be correctly
-- assigned to the thread it belongs to.
--
-- This function also parses the log message returned by `s.sendmail` to
-- determine if the message transmission was successful or not.
--
-- @param filename string: path to the email message you would like to send
--
-- @usage
Expand All @@ -25,24 +50,48 @@ local confirm_sendmail = function(filename)
})

if choice == 1 then
if config.options.sent_folder then
local date_line = "Date: " .. vim.fn.system("date -R"):gsub("\n", "")
v.nvim_buf_set_lines(0, 0, 0, false, { date_line })
local messageid_line = "Message-ID: " .. vim.fn.system('echo "<$(uuidgen)@localhost>"'):gsub("\n", "")
v.nvim_buf_set_lines(0, 0, 0, false, { messageid_line })
end
vim.cmd.write()
s.sendmail(filename)
local log_message = s.sendmail(filename)
local smtpstatus = log_message:match("smtpstatus=([^ ]*)")
local exitcode = log_message:match("exitcode=([^ ]*)")
if exitcode == "EX_OK" and smtpstatus == "250" then
vim.notify("📨 email sent successfully")
if config.options.sent_folder then
s.savemail(filename, config.options.sent_folder)
end
else
vim.notify("❌ failed to send email: " .. smtpstatus, vim.log.levels.ERROR)
end
end
end

-- Send a completed message
--
-- This function takes a file containing a completed message and send it to the
-- recipient(s) using `msmtp`. Typically you will invoke this function after
-- confirming from a reply or newly composed email message
-- confirming from a reply or newly composed email message. The invocation of
-- `msmtp` determines by itself the recipient and the sender.
--
-- If the configuration `config.options.logfile` is set, then it invokes
-- `msmtp` with logging capability to that file. Otherwise, it logs to
-- temporary file.
--
-- @param filename string: path to the email message you would like to send
--
-- @return string: The log message provided by `msmtp`
--
-- @usage
-- require('notmuch.send').sendmail('/tmp/my_new_email.eml')
s.sendmail = function(filename)
os.execute('msmtp -t <' .. filename)
print('Successfully sent email: ' .. filename)
local logfile = config.options.logfile or os.tmpname()
os.execute("msmtp -t --read-envelope-from --logfile=" .. logfile .. " <" .. filename)
return vim.fn.system("tail -1 " .. logfile):gsub("\n", "")
end

-- Reply to an email message
Expand Down Expand Up @@ -88,15 +137,19 @@ end
-- message headers and body. The mail content is stored in `/tmp/` so the user
-- can come back to it later if needed.
--
-- @param to string: recipient address (optionaal argument)
--
-- @usage
-- -- Typically you can run this with `:ComposeMail` or pressing `C`
-- require('notmuch.send').compose()
s.compose = function()
s.compose = function(to)
to = to or ''
local compose_filename = '/tmp/compose.eml'

-- TODO: Add ability to modify default body message and signature
local headers = {
'To: ',
'From: ' .. config.options.from,
'To: ' .. to,
'Cc: ',
'Subject: ',
'',
Expand Down
78 changes: 45 additions & 33 deletions lua/notmuch/tag.lua
Original file line number Diff line number Diff line change
Expand Up @@ -49,52 +49,64 @@ t.msg_toggle_tag = function(tags)
db.close()
end

t.thread_add_tag = function(tags)
t.thread_add_tag = function(tags, startlinenr, endlinenr)
startlinenr = startlinenr or v.nvim_win_get_cursor(0)[1]
endlinenr = endlinenr or startlinenr
local t = u.split(tags, '%S+')
local line = v.nvim_get_current_line()
local threadid = string.match(line, '%S+', 8)
local db = require'notmuch.cnotmuch'(config.options.notmuch_db_path, 1)
local query = db.create_query('thread:' .. threadid)
local thread = query.get_threads()[1]
for i,tag in pairs(t) do
thread:add_tag(tag)
for linenr = startlinenr, endlinenr do
local line = vim.fn.getline(linenr)
local threadid = string.match(line, "%S+", 8)
local db = require("notmuch.cnotmuch")(config.options.notmuch_db_path, 1)
local query = db.create_query("thread:" .. threadid)
local thread = query.get_threads()[1]
for i,tag in pairs(t) do
thread:add_tag(tag)
end
db.close()
end
db.close()
print('+(' .. tags .. ')')
end

t.thread_rm_tag = function(tags)
t.thread_rm_tag = function(tags, startlinenr, endlinenr)
startlinenr = startlinenr or v.nvim_win_get_cursor(0)[1]
endlinenr = endlinenr or startlinenr
local t = u.split(tags, '%S+')
local line = v.nvim_get_current_line()
local threadid = string.match(line, '%S+', 8)
local db = require'notmuch.cnotmuch'(config.options.notmuch_db_path, 1)
local query = db.create_query('thread:' .. threadid)
local thread = query.get_threads()[1]
for i,tag in pairs(t) do
thread:rm_tag(tag)
for linenr = startlinenr, endlinenr do
local line = vim.fn.getline(linenr)
local threadid = string.match(line, "%S+", 8)
local db = require("notmuch.cnotmuch")(config.options.notmuch_db_path, 1)
local query = db.create_query("thread:" .. threadid)
local thread = query.get_threads()[1]
for i,tag in pairs(t) do
thread:rm_tag(tag)
end
db.close()
end
db.close()
print('-(' .. tags .. ')')
end

t.thread_toggle_tag = function(tags)
t.thread_toggle_tag = function(tags, startlinenr, endlinenr)
startlinenr = startlinenr or v.nvim_win_get_cursor(0)[1]
endlinenr = endlinenr or startlinenr
local t = u.split(tags, '%S+')
local line = v.nvim_get_current_line()
local threadid = string.match(line, '%S+', 8)
local db = require'notmuch.cnotmuch'(config.options.notmuch_db_path, 1)
local query = db.create_query('thread:' .. threadid)
local thread = query.get_threads()[1]
local curr_tags = thread:get_tags()
for i,tag in pairs(t) do
if curr_tags[tag] == true then
thread:rm_tag(tag)
print('-' .. tag)
else
thread:add_tag(tag)
print('+' .. tag)
for linenr = startlinenr, endlinenr do
local line = vim.fn.getline(linenr)
local threadid = string.match(line, "%S+", 8)
local db = require("notmuch.cnotmuch")(config.options.notmuch_db_path, 1)
local query = db.create_query("thread:" .. threadid)
local thread = query.get_threads()[1]
local curr_tags = thread:get_tags()
for i,tag in pairs(t) do
if curr_tags[tag] == true then
thread:rm_tag(tag)
print("-" .. tag)
else
thread:add_tag(tag)
print("+" .. tag)
end
end
db.close()
end
db.close()
end

return t
Expand Down
5 changes: 3 additions & 2 deletions plugin/notmuch.vim
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
command -complete=custom,notmuch#CompSearchTerms -nargs=* NmSearch :call v:lua.require('notmuch').search_terms("<args>")
command ComposeMail :call v:lua.require('notmuch.send').compose()
let g:notmuch_mailroot = trim(system('notmuch config get database.mail_root'))
command -complete=custom,notmuch#CompSearchTerms -nargs=* NmSearch :call v:lua.require('notmuch').search_terms(<q-args>)
command -complete=custom,notmuch#CompAddress -nargs=* ComposeMail :call v:lua.require('notmuch.send').compose(<q-args>)

" vim: tabstop=2:shiftwidth=2:expandtab
Loading