if !exists("g:vmail_flagged_color") let g:vmail_flagged_color = "ctermfg=green guifg=green guibg=grey" endif let s:mailbox = $VMAIL_MAILBOX let s:query = $VMAIL_QUERY let s:browser_command = $VMAIL_BROWSER let s:append_file = '' let s:drb_uri = $DRB_URI let s:client_script = "vmail_client " . shellescape(s:drb_uri) . " " let s:set_window_width_command = s:client_script . "window_width= " let s:list_mailboxes_command = s:client_script . "list_mailboxes " let s:show_message_command = s:client_script . "show_message " let s:update_command = s:client_script . "update" let s:fetch_headers_command = s:client_script . "fetch_headers " let s:select_mailbox_command = s:client_script . "select_mailbox " let s:search_command = s:client_script . "search " let s:more_messages_command = s:client_script . "more_messages " let s:flag_command = s:client_script . "flag " let s:append_to_file_command = s:client_script . "append_to_file " let s:move_to_command = s:client_script . "move_to " let s:copy_to_command = s:client_script . "copy_to " let s:new_message_template_command = s:client_script . "new_message_template " let s:reply_template_command = s:client_script . "reply_template " let s:forward_template_command = s:client_script . "forward_template " let s:deliver_command = s:client_script . "deliver " let s:save_attachments_command = s:client_script . "save_attachments " let s:open_html_part_command = s:client_script . "open_html_part " let s:show_help_command = s:client_script . "show_help" let s:message_bufname = "current_message.txt" function! VmailStatusLine() return "%<%f\ " . s:mailbox . " " . s:query . "%r%=%-14.(%l,%c%V%)\ %P" endfunction function! s:create_list_window() "setlocal bufhidden=delete "setlocal buftype=nofile setlocal nomodifiable setlocal noswapfile "setlocal nomodifiable setlocal nowrap setlocal nonumber setlocal foldcolumn=0 setlocal nospell " setlocal nobuflisted setlocal textwidth=0 setlocal noreadonly " hi CursorLine cterm=NONE ctermbg=darkred ctermfg=white guibg=darkred guifg=white setlocal cursorline " we need the bufnr to find the window later let s:listbufnr = bufnr('%') let s:listbufname = bufname('%') setlocal statusline=%!VmailStatusLine() call s:message_list_window_mappings() autocmd BufNewFile,BufRead *.txt setlocal modifiable endfunction " the message display buffer window function! s:create_message_window() exec "split " . s:message_bufname setlocal modifiable setlocal buftype=nofile let s:message_window_bufnr = bufnr('%') call s:message_window_mappings() close endfunction function! s:show_message(stay_in_message_list) let line = getline(line(".")) if match(line, '^> Load') != -1 setlocal modifiable delete call s:more_messages() return endif let s:uid = shellescape(matchstr(line, '\S\+$')) if s:uid == "" return end " mark as read let newline = substitute(line, '^\V*+', '* ', '') let newline = substitute(newline, '^\V+ ', ' ', '') setlocal modifiable call setline(line('.'), newline) setlocal nomodifiable write " this just clears the command line and prevents the screen from " moving up when the next echo statement executes: " call feedkeys(":\") " redraw let command = s:show_message_command . s:uid echom "Loading message ". s:uid .". Please wait..." redrawstatus let res = system(command) call s:focus_message_window() set modifiable 1,$delete put =res " critical: don't call execute 'normal \' " call feedkeys("") 1delete normal 1Gjk set nomodifiable if a:stay_in_message_list call s:focus_list_window() end redraw endfunction " from message window function! s:show_next_message() let fullscreen = (bufwinnr(s:listbufnr) == -1) " we're in full screen message mode if fullscreen 3split exec 'b'. s:listbufnr else call s:focus_list_window() end normal j call s:show_message(1) normal zz wincmd p redraw endfunction function! s:show_previous_message() let fullscreen = (bufwinnr(s:listbufnr) == -1) " we're in full screen message mode if fullscreen 3split exec 'b'. s:listbufnr else call s:focus_list_window() end normal k if line('.') != line('$') call s:show_message(1) endif normal zz wincmd p redraw endfunction " from message list window function! s:show_next_message_in_list() if line('.') != line('$') normal j call s:show_message(1) endif endfunction function! s:show_previous_message_in_list() if line('.') != 1 normal k call s:show_message(1) endif endfunction " invoked from withint message window function! s:show_raw() let command = s:show_message_command . s:uid . ' raw' echo command setlocal modifiable 1,$delete let res = system(command) put =res 1delete normal 1G setlocal nomodifiable endfunction function! s:focus_list_window() if bufwinnr(s:listbufnr) == winnr() && exists("s:syntax_coloring_set") return end let winnr = bufwinnr(s:listbufnr) if winnr == -1 " create window split exec "buffer" . s:listbufnr else exec winnr . "wincmd w" endif " set up syntax highlighting if has("syntax") syn clear " colorize whole line syn match VmailBufferFlagged /^*.*/hs=s exec "hi def VmailBufferFlagged " . g:vmail_flagged_color let s:syntax_coloring_set = 1 endif " " call feedkeys("\") " prevents screen artifacts when user presses return too fast " turn this off though, because it causes an annoying flash endfunction function! s:focus_message_window() let winnr = bufwinnr(s:message_window_bufnr) if winnr == -1 " create window exec "rightbelow split " . s:message_bufname else exec winnr . "wincmd w" endif endfunction func! s:close_message_window() if winnr('$') > 1 close! else call s:focus_list_window() wincmd p close! normal z. endif endfunc " gets new messages since last update function! s:update() let command = s:update_command echo "Checking for new messages. Please wait..." let res = system(command) let lines = split(res, '\n') if len(lines) > 0 setlocal modifiable call append(0, lines) setlocal nomodifiable write! let num = len(lines) call cursor(num, 0) normal z. redraw echom "You have " . num . " new message" . (num == 1 ? '' : 's') . "!" else redraw echom "No new messages" endif endfunction function! s:toggle_star() range let uid_set = s:collect_uids(a:firstline, a:lastline) let nummsgs = len(uid_set) let flag_symbol = "^*" " check if starred already let action = " +FLAGS" if (match(getline(a:firstline), flag_symbol) != -1) let action = " -FLAGS" endif let command = s:flag_command . shellescape(join(uid_set, ',')) . action . " Flagged" if nummsgs == 1 echom "Toggling flag on message" else echom "Toggling flags on " . nummsgs . " messages" endif " toggle * on lines let res = system(command) setlocal modifiable let lnum = a:firstline while lnum <= a:lastline let line = getline(lnum) if action == " +FLAGS" let newline = substitute(line, '^ ', '*', '') let newline = substitute(newline, '^+ ', '*+', '') else let newline = substitute(line, '^*+', '+ ', '') let newline = substitute(newline, '^* ', ' ', '') endif call setline(lnum, newline) let lnum += 1 endwhile setlocal nomodifiable write redraw if nummsgs == 1 echom "Toggled flag on message" else echom "Toggled flags on " . nummsgs . " messages" endif endfunction " flag can be Deleted or spam func! s:delete_messages(flag) range let uid_set = s:collect_uids(a:firstline, a:lastline) let nummsgs = len(uid_set) let command = s:flag_command . shellescape(join(uid_set, ',')) . " +FLAGS " . a:flag if nummsgs == 1 echom "Deleting message" else echom "Deleting " . nummsgs . " messages" endif let res = system(command) setlocal modifiable exec "silent " . a:firstline . "," . a:lastline . "delete" setlocal nomodifiable write redraw echo nummsgs . " message" . (nummsgs == 1 ? '' : 's') . " marked " . a:flag endfunc func! s:archive_messages() range let uid_set = s:collect_uids(a:firstline, a:lastline) let nummsgs = len(uid_set) let command = s:move_to_command . shellescape(join(uid_set, ',')) . ' ' . "all" echo "Archiving message" . (nummsgs == 1 ? '' : 's') let res = system(command) setlocal modifiable exec "silent " . a:firstline . "," . a:lastline . "delete" setlocal nomodifiable write redraw echo nummsgs . " message" . (nummsgs == 1 ? '' : 's') . " archived" endfunc " -------------------------------------------------------------------------------- " append text bodies of a set of messages to a file func! s:append_messages_to_file() range let uid_set = s:collect_uids(a:firstline, a:lastline) let nummsgs = len(uid_set) let append_file = input("print messages to file: ", s:append_file) if append_file == '' echom "Canceled" return endif let s:append_file = append_file let command = s:append_to_file_command . shellescape(join(uid_set, ',')) . ' ' . s:append_file echo "Appending " . nummsgs . " message" . (nummsgs == 1 ? '' : 's') . " to " . s:append_file . ". Please wait..." let res = system(command) echo res redraw endfunc " -------------------------------------------------------------------------------- " move to another mailbox function! s:move_to_mailbox(copy) range let s:copy_to_mailbox = a:copy let uid_set = s:collect_uids(a:firstline, a:lastline) let s:nummsgs = len(uid_set) let s:uid_set = shellescape(join(uid_set, ',')) " now prompt use to select mailbox if !exists("s:mailboxes") call s:get_mailbox_list() endif leftabove split MailboxSelect setlocal buftype=nofile setlocal noswapfile setlocal modifiable resize 1 let prompt = "select mailbox to " . (a:copy ? 'copy' : 'move') . " to: " call setline(1, prompt) normal $ inoremap :call complete_move_to_mailbox() inoremap :q setlocal completefunc=CompleteMoveMailbox " c-p clears the line let s:firstline = a:firstline let s:lastline = a:lastline call feedkeys("a\\\", 't') " save these in script scope to delete the lines when move completes endfunction function! s:complete_move_to_mailbox() let mailbox = get(split(getline(line('.')), ": "), 1) close if s:copy_to_mailbox let command = s:copy_to_command . s:uid_set . ' ' . shellescape(mailbox) else let command = s:move_to_command . s:uid_set . ' ' . shellescape(mailbox) endif redraw echo "Moving uids ". s:uid_set . " to mailbox " . mailbox let res = system(command) setlocal modifiable if !s:copy_to_mailbox exec "silent " . s:firstline . "," . s:lastline . "delete" end setlocal nomodifiable write redraw echo s:nummsgs . " message" . (s:nummsgs == 1 ? '' : 's') . ' ' . (s:copy_to_mailbox ? 'copied' : 'moved') . ' to ' . mailbox endfunction function! CompleteMoveMailbox(findstart, base) if !exists("s:mailboxes") call s:get_mailbox_list() endif if a:findstart " locate the start of the word let line = getline('.') let start = col('.') - 1 while start > 0 && line[start - 1] =~ '\a' let start -= 1 endwhile return start else " find months matching with "a:base" let res = [] for m in s:mailboxes if m == s:mailbox continue endif if m =~ '^' . a:base call add(res, m) endif endfor return res endif endfun " -------------------------------------------------------------------------------- function! s:get_mailbox_list() let command = s:list_mailboxes_command redraw echo command let res = system(command) let s:mailboxes = split(res, "\n", '') endfunction " ------------------------------------------------------------------------------- " select mailbox function! s:mailbox_window() call s:get_mailbox_list() leftabove split MailboxSelect setlocal buftype=nofile setlocal noswapfile setlocal modifiable resize 1 inoremap :call select_mailbox() inoremap :q setlocal completefunc=CompleteMailbox " c-p clears the line call setline(1, "select mailbox to switch to: ") normal $ call feedkeys("a\\\", 't') endfunction function! CompleteMailbox(findstart, base) if !exists("s:mailboxes") call s:get_mailbox_list() endif if a:findstart " locate the start of the word let line = getline('.') let start = col('.') - 1 while start > 0 && line[start - 1] =~ '\a' let start -= 1 endwhile return start else " find mailboxes matching with "a:base" let res = [] for m in s:mailboxes if m =~ '^' . a:base call add(res, m) endif endfor return res endif endfun function! s:select_mailbox() let mailbox = get(split(getline(line('.')), ": "), 1) close call s:focus_message_window() close " check if mailbox is a real mailbox if (index(s:mailboxes, mailbox) == -1) return endif let s:mailbox = mailbox let s:query = "all" let command = s:select_mailbox_command . shellescape(s:mailbox) redraw echom "Selecting mailbox: ". s:mailbox . ". Please wait..." call system(command) redraw " reset window width now call system(s:set_window_width_command . winwidth(1)) " now get latest 100 messages call s:focus_list_window() setlocal modifiable let command = s:search_command . shellescape("all") echo "Loading messages..." let res = system(command) silent 1,$delete silent! put! =res execute "normal Gdd\" setlocal nomodifiable write normal gg redraw echom "Current mailbox: ". s:mailbox endfunction func! s:search_query() if !exists("s:query") let s:query = "" endif let s:query = input("search query: ", s:query) call s:do_search() endfunc function! s:do_search() " empty query if match(s:query, '^\s*$') != -1 return endif " close message window if open call s:focus_message_window() close let command = s:search_command . shellescape(s:query) redraw call s:focus_list_window() setlocal modifiable echo "Running query on " . s:mailbox . ": " . s:query . ". Please wait..." let res = system(command) silent! 1,$delete silent! put! =res execute "silent normal Gdd\" setlocal nomodifiable write normal gg endfunction function! s:more_messages() let command = s:more_messages_command echo "Fetching more messages. Please wait..." let res = system(command) setlocal modifiable let lines = split(res, "\n") call append(line('$'), lines) " execute "normal Gdd\" setlocal nomodifiable normal j redraw echo "Done" endfunction " -------------------------------------------------------------------------------- " compose reply, compose, forward, save draft function! s:compose_reply(all) let command = s:reply_template_command if a:all let command = command . ' 1' endif call s:open_compose_window(command) " cursor after headers normal 1G} endfunction function! s:compose_message() let command = s:new_message_template_command call s:open_compose_window(command) " position cursor after to: " call search("^to:") " normal A endfunction function! s:compose_forward() let command = s:forward_template_command call s:open_compose_window(command) " call search("^to:") " normal A endfunction func! s:open_compose_window(command) redraw echo a:command let res = system(a:command) let previous_winnr = winnr() only split compose_message.txt setlocal modifiable wincmd p close! silent! 1,$delete silent! put! =res redraw "call feedkeys("\") call s:compose_window_mappings() setlocal completefunc=CompleteContact normal 1G endfunc func! s:turn_into_compose_window() call s:compose_window_mappings() setlocal completefunc=CompleteContact endfunc " contacts.txt file should be generated. " grep works well, does partial matches function! CompleteContact(findstart, base) if !exists("s:mailboxes") call s:get_mailbox_list() endif if a:findstart " locate the start of the word let line = getline('.') let start = col('.') - 1 while start > 0 && line[start - 1] =~ '\a' let start -= 1 endwhile return start else " find contacts " model regex: match at beginning of line, or inside < > wrapping " email addr " '\(^ho\|h func! s:open_html_part() let command = s:open_html_part_command " the command saves the html part to a local file let outfile = system(command) " todo: allow user to change open in browser command? exec "!" . s:browser_command . ' ' . outfile endfunc func! s:save_attachments() if !exists("s:savedir") let s:savedir = getcwd() . "/attachments" end let s:savedir = input("save attachments to directory: ", s:savedir, "dir") let command = s:save_attachments_command . s:savedir let res = system(command) echo res endfunc func! s:attach_file(file) normal gg normal } let attach = "attach: " . a:file put =attach endfunc " -------------------------------------------------------------------------------- func! s:toggle_maximize_window() if winnr('$') > 1 only " normal z. elseif bufwinnr(s:listbufnr) == winnr() call s:show_message(1) else " we're in the message window call s:focus_list_window() wincmd p endif endfunc " maybe not DRY enough, but fix that later " also, come up with a more precise regex pattern for matching hyperlinks func! s:open_href(all) range let pattern = 'https\?:[^ >)\]]\+' let n = 0 " range version if a:firstline < a:lastline let lnum = a:firstline while lnum <= a:lastline let href = matchstr(getline(lnum), pattern) if href != "" let command = s:browser_command ." ".shellescape(href)." &" call system(command) let n += 1 endif let lnum += 1 endwhile echom 'opened '.n.' links' return end let line = search(pattern, 'cw') if line && a:all while line let href = matchstr(getline(line('.')), pattern) let command = s:browser_command ." ".shellescape(href)." &" call system(command) let n += 1 let line = search('https\?:', 'W') endwhile echom 'opened '.n.' links' else let href = matchstr(getline(line('.')), pattern) let command = s:browser_command ." ".shellescape(href)." &" call system(command) echom 'opened '.href endif endfunc " linenum < 5, turn off line wrapping func! s:toggle_textwidth() if line('.') > 4 && &textwidth == 0 setlocal textwidth=72 elseif line('.') <= 4 && &textwidth != 0 setlocal textwidth=0 endif "echo &textwidth endfunc " -------------------------------------------------------------------------------- " HELP func! s:show_help() let command = s:browser_command . ' ' . shellescape('http://danielchoi.com/software/vmail.html') call system(command) "let helpfile = system(s:show_help_command) "exec "split " . helpfile endfunc " -------------------------------------------------------------------------------- " CONVENIENCE FUNCS function! s:collect_uids(startline, endline) let uid_set = [] let lnum = a:startline while lnum <= a:endline let uid = matchstr(getline(lnum), '\S\+$') call add(uid_set, uid) let lnum += 1 endwhile return uid_set endfunc " -------------------------------------------------------------------------------- " MAPPINGS func! s:message_window_mappings() noremap =:call focus_list_window() noremap r :call compose_reply(0) noremap a :call compose_reply(1) noremap R :call show_raw() noremap R :call show_raw() noremap f :call compose_forward() noremap :call show_next_message() noremap :call show_previous_message() nmap j nmap k noremap c :call compose_message() noremap h :call open_html_part() noremap q :call close_message_window() nnoremap # :close:call focus_list_window():call delete_messages("Deleted") nnoremap * :call focus_list_window():call toggle_star() noremap ! :close:call focus_list_window():call delete_messages("spam") noremap e :call focus_list_window():call archive_messages() " alt mappings for lazy hands nmap 8 * nmap 3 # nmap 1 ! nnoremap b :call focus_list_window():call move_to_mailbox(0) nnoremap B :call focus_list_window():call move_to_mailbox(1) nnoremap u :call focus_list_window():call update() nnoremap u :call focus_list_window():call update() nnoremap m :call focus_list_window():call mailbox_window() nnoremap A :call save_attachments() nnoremap :call toggle_maximize_window() noremap vp :call focus_list_window():call append_messages_to_file() nnoremap s :call focus_list_window():call search_query() endfunc func! s:message_list_window_mappings() noremap :call show_message(0) noremap :call show_message(0) nnoremap l :call show_message(1) noremap q :qal! noremap * :call toggle_star() noremap # :call delete_messages("Deleted") noremap ! :call delete_messages("spam") noremap e :call archive_messages() " alt mappings for lazy hands noremap 8 :call toggle_star() noremap 3 :call delete_messages("Deleted") noremap 1 :call delete_messages("spam") " nmap 8 * " nmap 3 # " nmap 1 ! "open a link browser (os x) " autocmd CursorMoved :redraw! noremap vp :call append_messages_to_file() nnoremap u :call update() noremap u :call update() noremap s :call search_query() noremap m :call mailbox_window() noremap b :call move_to_mailbox(0) noremap B :call move_to_mailbox(1) noremap c :call compose_message() noremap r :call show_message(0):call compose_reply(0) noremap a :call show_message(0):call compose_reply(1) noremap f :call show_message(0):call compose_forward() noremap :call show_next_message_in_list() noremap :call show_previous_message_in_list() nnoremap :call toggle_maximize_window() autocmd CursorMoved :redraw endfunc func! s:compose_window_mappings() noremap q :call close_and_focus_list_window() setlocal ai " setlocal textwidth=72 autocmd CursorMoved call toggle_textwidth() command! -bar -nargs=1 -complete=file VMAttach call s:attach_file() endfunc func! s:global_mappings() " NOTE send_message is a global mapping, so user can load a saved " message from a file and send it nnoremap vs :call send_message() noremap o :call open_href(0) noremap O :call open_href(1) noremap ? :call show_help() noremap qq :qal! endfunc "TODO see if using LocalLeader and maplocalleader makes more sense let mapleader = "," call s:global_mappings() call s:create_list_window() call s:create_message_window() call s:focus_list_window() " to go list window " send window width call system(s:set_window_width_command . winwidth(1)) "autocmd VimResized call system(s:set_window_width_command . winwidth(1)) autocmd bufreadpost *.txt call turn_into_compose_window() normal G call system(s:select_mailbox_command . shellescape(s:mailbox)) call s:do_search()