" Vimball Archiver by Charles E. Campbell, Jr., Ph.D. UseVimball finish autoload/tmboxbrowser.vim [[[1 481 " tmboxbrowser.vim -- Browse mbox files " @Author: Thomas Link (mailto:samul AT web de?subject=[vim]) " @Website: http://www.vim.org/account/profile.php?user_id=4037 " @License: GPL (see http://www.gnu.org/licenses/gpl.txt) " @Created: 2007-04-21. " @Last Change: 2007-05-23. " @Revision: 0.1.641 " " TODO: if &cp || exists("loaded_tmboxbrowser_autoload") finish endif if !exists('loaded_tlib') echoerr "tlib is required" finish endif let loaded_tmboxbrowser_autoload = 1 let s:scratch_name = '__MBOX_Browser__' fun! s:SNR() return matchstr(expand(''), '\d\+_\zeSNR$') endf function! tmboxbrowser#TMBoxSelect(bang) if empty(g:tmboxbrowser_path) echoerr 'tmbox: Please set g:tmboxbrowser_path first' return endif let mboxes = split(globpath(g:tmboxbrowser_path, '**'), '\n') call filter(mboxes, 'v:val !~ ''\.\(sbd\|msf\|dat\|html\)$''') let mbox = tlib#InputList('s', 'Select mbox', mboxes) if !empty(mbox) " TLogVAR mbox " exec 'edit '. tlib#ExArg(mbox) call tmboxbrowser#TMBoxBrowse(a:bang, mbox) endif endf fun! tmboxbrowser#TMBoxBrowse(...) exec tlib#Args(['bang', 'file']) let markread = empty(bang) " TLogVAR bang " TLogVAR file " TLogDBG bufnr('%') " TLogDBG bufname('%') " TLogDBG "exists('s:tmboxbrowser_bufnr')=". exists('b:tmboxbrowser_bufnr') if !exists('b:tmboxbrowser_bufnr') || !empty(file) || !markread if !empty(file) " TLogDBG 'edit '. file exec 'edit '. tlib#ExArg(file) endif " TAssert bufname('%') != s:scratch_name let b:tmboxbrowser_bufnr = bufnr('%') " TLogVAR b:tmboxbrowser_bufnr let pos = getpos('.') let b:tmboxbrowser_index = {} let b:tmboxbrowser_ids = [] let mbox = expand('%:p') let b:tmboxbrowser_data = tlib#GetCacheName('tmbox', mbox, 1) " g:tmboxbrowser_datadir . " \ substitute(tlib#RelativeFilename(mbox, g:tmboxbrowser_datadir), '\W', '_', 'g') call s:SetIgnore(markread) silent keepjumps g /^From /call s:CollectHeader(b:tmboxbrowser_index, b:tmboxbrowser_ids) if !empty(b:tmboxbrowser_ids) let b:tmboxbrowser_index[b:tmboxbrowser_ids[-1]].bodyend = line('$') endif for id in b:tmboxbrowser_ids let mail = b:tmboxbrowser_index[id] let mail.top = mail.bodystart - mail.headstart + 1 " let mail.top = mail.bodystart - mail.headstart " TLogVAR mail.top let mail.contents = getline(mail.headstart, mail.bodyend) endfor call setpos('.', pos) endif let mlist = s:GetList(b:tmboxbrowser_bufnr) if !empty(mlist) call s:SetInputListParams(1) let mailid = tlib#InputList('m', 'Select Mail', mlist, [ \ {'key': 24, 'agent': s:SNR() .'AgentToggleMarkRead', 'key_name': '', 'help': 'Toggle mark read'}, \ {'key': 20, 'agent': s:SNR() .'AgentToggleHideRead', 'key_name': '', 'help': 'Toggle show read'}, \ {'pick_last_item': 0}, \ {'show_empty': 1}, \ {'filter_format': s:SNR() .'DisplayFormat(%s)'}, \ {'display_format': s:SNR() .'DisplayFormat(%s)'}, \ ]) " \ {'return_agent': s:SNR() .'AgentReturnValue'}, if !empty(mailid) " call TLogDBG(s:MBoxVar(b:tmboxbrowser_bufnr, 'b:tmboxbrowser_markread')) if s:MBoxVar(b:tmboxbrowser_bufnr, 'b:tmboxbrowser_markread') call s:MBoxVar(b:tmboxbrowser_bufnr, 's:MarkRead('. string(mailid) .')') endif " TLogVAR mailidx call s:ViewMail(mailid[0], winnr()) endif endif endf fun! s:MarkRead(ids) for id in a:ids " TLogVAR id let idi = index(b:tmboxbrowser_ignore, id) " TLogVAR idi if idi == -1 call add(b:tmboxbrowser_ignore, id) else call remove(b:tmboxbrowser_ignore, idi) endif endfor " if !isdirectory(g:tmboxbrowser_datadir) " call mkdir(g:tmboxbrowser_datadir, 'p') " endif call writefile(b:tmboxbrowser_ignore, b:tmboxbrowser_data) call s:SetInputListParams(0) endf fun! s:SetInputListParams(setbuffer) " TLogDBG bufname('%') if a:setbuffer let s:tmboxbrowser_bufnr = b:tmboxbrowser_bufnr endif let s:mails_index = s:MBoxVar(b:tmboxbrowser_bufnr, 'b:tmboxbrowser_index') let s:mails_ignore = s:MBoxVar(b:tmboxbrowser_bufnr, 'b:tmboxbrowser_ignore') endf fun! s:SetIgnore(markread) " TLogVAR a:markread " TAssert bufnr('%') == b:tmboxbrowser_bufnr let b:tmboxbrowser_markread = a:markread if filereadable(b:tmboxbrowser_data) let b:tmboxbrowser_ignore = readfile(b:tmboxbrowser_data) else let b:tmboxbrowser_ignore = [] endif endf fun! s:MBoxVar(bufnr, name) let bn = bufnr('%') if a:bufnr != bn exec 'buffer! '. a:bufnr endif let rv = eval(a:name) if a:bufnr != bn exec 'buffer! '. bn endif return rv endf function! s:AgentToggleHideRead(world, selected) call s:MBoxVar(s:tmboxbrowser_bufnr, 's:SetIgnore(!b:tmboxbrowser_markread)') let a:world.base = s:GetList(s:tmboxbrowser_bufnr) let a:world.state = 'reset' call a:world.ResetSelected() return a:world endf function! s:AgentToggleMarkRead(world, selected) call s:MBoxVar(s:tmboxbrowser_bufnr, 's:MarkRead('. string(a:selected) .')') let a:world.base = s:GetList(s:tmboxbrowser_bufnr) let a:world.state = 'reset' call a:world.ResetSelected() return a:world endf fun! s:DisplayFormat(id) let mail = s:mails_index[a:id] let sep = index(s:mails_ignore, a:id) == -1 ? ':' : '*' return printf('%-20s %s %s', mail.from, sep, mail.subject) endf fun! s:GetList(bufnr) " TLogVAR a:bufnr let mails = s:MBoxVar(a:bufnr, 'copy(b:tmboxbrowser_ids)') let sort = s:MBoxVar(a:bufnr, "tlib#GetValue('tmboxbrowser_sort', 'bg')") if !empty(sort) if sort == '-' call reverse(mails) endif end if s:MBoxVar(a:bufnr, 'b:tmboxbrowser_markread') let ignore = s:MBoxVar(a:bufnr, 'b:tmboxbrowser_ignore') if !empty(ignore) call filter(mails, 'index(ignore, v:val) == -1') endif endif return mails endf fun! s:ViewMail(id, winnr) let bufnr = b:tmboxbrowser_bufnr " TLogVAR bufnr let bidx = s:MBoxVar(b:tmboxbrowser_bufnr, 'b:tmboxbrowser_index') " TAssert IsDictionary(bidx) let mail = bidx[a:id] " TLogVAR mail.top call tlib#UseScratch({'scratch': s:scratch_name, 'scratch_split': 0}) " TAssert IsEqual(fnamemodify(bufname('%'), ':t'), '__MBOX_Browser__') let b:tmboxbrowser_bufnr = bufnr " TLogVAR b:tmboxbrowser_bufnr if &term =~ 'gui' noremap :call tmboxbrowser#TMBoxBrowse() endif noremap q :bdelete " exec 'noremap i :buffer '. bufnr .'| call tmboxbrowser#TMBoxBrowse()' " noremap i :call MBoxVar(b:tmboxbrowser_bufnr, 'tmboxbrowser#TMBoxBrowse()') noremap i :call tmboxbrowser#TMBoxBrowse() noremap ? :call ShowHelp() noremap $ :echo MBoxVar(b:tmboxbrowser_bufnr, 'string(b:tmboxbrowser_mail)') exec 'noremap p :call ViewMail('. string(a:id) .', '. a:winnr .')' map map set ft=mail " let charset = matchstr(mail.contenttype, 'charset=\zs\S\+') " if charset =~? 'utf-\?8' " setlocal enc=utf8 " endif let contents = copy(mail.contents) " TLogDBG "len0(contents)=". len(contents) let type = matchstr(mail.contenttype, '.\{-}\ze;') " TLogVAR type while type =~ 'multipart' let boundary = matchstr(mail.contenttype, 'boundary=\zs\("[^"]\+"\|[^[:space:];]\+\)') let boundary = substitute(boundary, '^"\(.*\)"$', '\1', '') norm! ggdG call append(0, contents[1:-1]) let parts = s:CollectMultipart(boundary) let part_names = map(copy(parts), 'v:val["contenttype"]') let parts_index = tlib#InputList('si', 'Select part', part_names, [], 0) if parts_index > 0 " let mail = extend(parts[parts_index - 1], mail) let mail = extend(parts[parts_index - 1], mail, 'keep') " TLogVAR mail.top " TLogVAR mail.encoding " TLogVAR parts[parts_index - 1].encoding let type = matchstr(mail.contenttype, '.\{-}\ze;') " TLogVAR type let contents = copy(mail.contents) else let @/ = '^--'. boundary break endif endwh let cenc = tolower(substitute(mail.encoding, '\W', '_', 'g')) " TLogVAR mail.encoding " TLogVAR cenc if exists('*s:Convert_'. cenc) " TLogDBG "len1(contents)=". len(contents) " TLogVAR mail.top " let contents[mail.top : -1] = s:Convert_{cenc}(contents[mail.top : -1]) let contents_body = contents[mail.top : -1] call remove(contents, mail.top, -1) let contents += s:Convert_{cenc}(contents_body) " TLogVAR contents endif " TLogVAR type let cleantype = substitute(type, '\W', '_', 'g') if exists('g:tmboxbrowser_convert_'. cleantype) && !empty(g:tmboxbrowser_convert_{cleantype}) norm! ggdG call append(0, contents[mail.top : -1]) silent exec '%'. g:tmboxbrowser_convert_{cleantype} call remove(contents, mail.top, -1) let contents += getline(1, line('$')) elseif mail.encoding == 'base64' && !empty(g:tmboxbrowser_decode_base64) let filename = matchstr(mail.contenttype, 'name="\zs.\{-}\ze"') if !exists('loaded_viki') || VikiIsSpecialFile(filename) let cwd = getcwd() call s:EnsureAttachmentsDir() silent exec 'lcd '. escape(g:tmboxbrowser_attachments_dir, '#% \') try " echom filename .' -- '. g:tmboxbrowser_attachments_dir if !filereadable(filename) || !empty(input('File exists. Overwrite? (y/n) ', 'y')) let image = contents[mail.top : -1] let fn64 = filename .'.base64' call writefile(image, fn64) silent exec printf(g:tmboxbrowser_decode_base64, tlib#ExArg(fn64), tlib#ExArg(filename)) call delete(fn64) if exists('*VikiOpenSpecialFile') call VikiOpenSpecialFile(filename) else silent exec '!'. g:netrw_browsex_viewer .' '. tlib#ExArg(filename) endif endif finally silent exec 'lcd '. escape(cwd, '#% \') endtry endif endif norm! ggdG call append(0, contents[1:-1]) setlocal nomodifiable exec (mail.top - 1) norm! zt if exists(':VikiMinorMode') let b:vikiDisableType = 'c' VikiMinorMode endif endf fun! s:EnsureAttachmentsDir() if !isdirectory(g:tmboxbrowser_attachments_dir) call mkdir(g:tmboxbrowser_attachments_dir, 'p') endif endf fun! s:ShowHelp() if &term =~ 'gui' echo ' ... Index' endif echo 'i ... Index' echo 'p ... Select parts or re-view mail' echo 'q ... Quit' echo '? ... Help' endf " fun! s:AgentReturnValue(world, return_value) " if !empty(a:return_value) " let bidx = a:world.table[a:world.prefidx - 1] " return bidx " endif " endf fun! s:CollectMultipart(boundary) let prx = '^--'. a:boundary .'$' " TLogVAR prx let erx = '^--'. a:boundary .'--$' " TLogVAR erx norm! G let end = search(erx, 'Wbc') - 1 " TLogVAR end let parts = [] while search(prx, 'bW') let headstart = line('.') " TLogVAR headstart let headend = search('\n\zs\n', 'Wn') " TLogVAR headend " let part = getline(headstart, end - 1) let part = getline(headstart, end) let ctype = s:ExtractHeader(headstart, headend, 'Content-Type') " TLogVAR ctype let ctenc = s:ExtractHeader(headstart, headend, 'Content-Transfer-Encoding') " TLogVAR ctenc let top = headend - headstart + 1 " let top = headend - headstart call insert(parts, {'contenttype': ctype, 'encoding': ctenc, 'top': top, \ 'contents': part}) let end = headstart - 1 exec end endwh return parts endf fun! s:CollectHeader(acc, ids) let date = matchstr(getline('.'), '^From -\s*\zs.*') let headstart = line('.') let headend = search('\n\zs\n') if !empty(a:acc) let a:acc[a:ids[-1]].bodyend = headstart - 1 endif let from = s:ExtractHeader(headstart, headend, 'From') let fromlist = matchlist(from, '^\(.\{-}\)\?\s*\(<.\{-}>\)') if !empty(fromlist) let from = !empty(fromlist[1]) ? fromlist[1] : fromlist[2] endif let subject = s:ExtractHeader(headstart, headend, 'Subject') let encoding = s:ExtractHeader(headstart, headend, 'Content-Transfer-Encoding') " TLogVAR encoding let contenttype = s:ExtractHeader(headstart, headend, 'Content-Type') let idheaders = ['Message-ID', 'X-UIDL'] let messageid = '' while empty(messageid) && !empty(idheaders) let [header; idheaders] = idheaders let messageid = s:ExtractHeader(headstart, headend, header) endwh if empty(messageid) let messageid = join([from, subject, date]) endif exec headstart norm! } if has_key(a:acc, messageid) echohl Error echom 'TMBOX: Dupplicate Message-ID: '. messageid echohl NONE endif let a:acc[messageid] = { \ 'date': date, 'from': from, 'subject': subject, 'encoding': encoding, \ 'contenttype': contenttype, 'messageid': messageid, \ 'headstart': headstart, 'bodystart': headend + 1, \ } call add(a:ids, messageid) endf fun! s:ExtractHeader(headstart, headend, name) exec a:headstart let rx = '\c^'. a:name .':' call search(rx, 'W', a:headend) let info = [matchstr(getline('.'), rx .'\s\+\zs.*')] while line('.') < a:headend && getline(line('.') + 1) =~ '^\s\+\S' norm! j call add(info, matchstr(getline('.'), '^\s\+\zs.\+')) endwh " TLogVAR info return join(info) endf fun! s:Convert_quoted_printable(body) let qplc = 0 let acc = [] for i in range(0, len(a:body) - 1) " TLogVAR a:body[i] let line0 = a:body[i] let line = substitute(line0, '=\([0-F][0-F]\)', '\=nr2char("0x". submatch(1))', 'g') " TLogVAR line let qplc0 = qplc " TLogVAR qplc0 if line[-1:-1] == '=' let line = line[0:-2] let qplc = 1 else let qplc = 0 endif " TLogVAR qplc if qplc0 let acc[-1] .= substitute(line, '^\s\+', '', '') " TLogVAR acc[-1] else call add(acc, line) " TLogVAR line endif endfor return acc endf fun! tmboxbrowser#Decode_base64(infile, outfile) silent exec printf(g:tmboxbrowser_decode_base64, a:infile, a:outfile) endf fun! tmboxbrowser#Convert_image_jpeg(line1, line2) if !empty(g:tmboxbrowser_decode_base64) " call s:EnsureAttachmentsDir() let tempfile = tempname() let tempdir = fnamemodify(tempfile, ':h') let tempname = fnamemodify(tempfile, ':t') let cwd = getcwd() silent exec 'lcd '. escape(tempdir, '#% \') try let image = contents[mail.top : -1] call writefile(image, tempname) call tmboxbrowser#Decode_base64(tempname, tempname .'.jpg') silent exec printf(g:tmboxbrowser_decode_jpeg, tempname .'.jpg', tempname .'.txt') exec 'norm! '. a:line1 .'Gd'. a:line2 .'G' call append(a:line1, readfile(tempname .'.txt')) finally if filereadable(tempname .'.jpg') call delete(tempname .'.jpg') endif if filereadable(tempname .'.txt') call delete(tempname .'.txt') endif if filereadable(tempname) call delete(tempname) endif silent exec 'lcd '. escape(cwd, '#% \') echom tempfile endtry endif endf finish CHANGES: 0.1 Initial release plugin/tmboxbrowser.vim [[[1 59 " tmboxbrowser.vim " @Author: Thomas Link (mailto:samul AT web de?subject=[vim]) " @Website: http://www.vim.org/account/profile.php?user_id=4037 " @License: GPL (see http://www.gnu.org/licenses/gpl.txt) " @Created: 2007-04-21. " @Last Change: 2007-05-23. " @Revision: 0.1.66 " " TODO: " - handle multipart documents if &cp || exists("loaded_tmboxbrowser") finish endif if !exists('loaded_tlib') || loaded_tlib < 6 echoerr 'tlib >= 0.6 is required' finish endif let loaded_tmboxbrowser = 1 if !exists('g:tmboxbrowser_attachments_dir') let g:tmboxbrowser_attachments_dir = resolve(fnamemodify(tempname(), ':p:h')) endif if !exists('g:tmboxbrowser_decode_base64') let g:tmboxbrowser_decode_base64 = '!base64 -i -d %s > %s' endif if !exists('g:tmboxbrowser_decode_jpeg') " let g:tmboxbrowser_decode_jpeg = '!jave image2ascii %s algorithm=edge_detection > %s' let g:tmboxbrowser_decode_jpeg = '!java -jar jave5.jar image2ascii %s algorithm=edge_detection > %s' endif if !exists('g:tmboxbrowser_convert_text_html') let g:tmboxbrowser_convert_text_html = '!w3m -dump -T text/html' endif " if !exists('g:tmboxbrowser_datadir') " let g:tmboxbrowser_datadir = fnamemodify(split(&runtimepath, ',')[0], ':p') .'cache_tmbox/' " endif if !exists('g:tmboxbrowser_sort') " let g:tmboxbrowser_sort = '' let g:tmboxbrowser_sort = '-' endif if !exists('g:tmboxbrowser_path') let g:tmboxbrowser_path = '' endif command! -bang -bar -nargs=? -complete=file TMBoxBrowser call tmboxbrowser#TMBoxBrowse("", ) command! -bang -bar TMBoxSelect call tmboxbrowser#TMBoxSelect("") finish CHANGES 0.1 Initial release doc/tmboxbrowser.txt [[[1 81 *tmboxbrowser.txt* A mbox browser Thomas Link (samul AT web de) Features~ - Show an index of e-mails contained in a mbox file - Mark e-mails as read and don't show them again in the index - View e-mails - display single parts of a multipart message - decode quoted printables - filter html mails through an external program; in the default setup, this requires "w3m" (|g:tmboxbrowser_convert_html|) - view base64 encoded attachments; this also requires "base64" (|g:tmboxbrowser_convert_base64|) to be installed - if viki (vimscript #861) is installed, use |VikiOpenSpecialFile()| - otherwise the file will be opened with |g:netrw_browsex_viewer| Usage~ 0. After installation check the values of: - |g:tmboxbrowser_attachments_dir| - |g:tmboxbrowser_datadir| - |g:tmboxbrowser_path| 1. Open an mbox file 2. Type :TMboxBrowser *:TMboxBrowser* :TMBoxBrowser[!] [FILE] Open a index list with the e-mails in the currently opened mbox. With [!], read items are displayed too. In this mode, no e-mails are marked as read. If [FILE] is given, the display the file is (newly) edited. I.e. if you open a mbox, mark all items as read and then want to browse the file again, you'll have to type> :TMBoxBrowser! % Alternatively, press in the list view to show/hide read mails. *:TMBoxSelect* :TMBoxSelect[!] Show a list of mbox files (as defined in |g:tmboxbrowser_path|). Open a mbox & call |:TMBoxBrowser|. *g:tmboxbrowser_convert_html* g:tmboxbrowser_convert_html (Default: '!w3m -dump -T text/html') The ex command used to convert html mails. *g:tmboxbrowser_convert_base64* g:tmboxbrowser_convert_base64 (Default: '!base64 -i -d %s > %s') The ex command (as format string for |printf|) used to convert base64 encoded messages. *g:tmboxbrowser_attachments_dir* g:tmboxbrowser_attachments_dir (Default: fnamemodify(tempname(), ':p:h')) Where to save decoded attachments. *g:tmboxbrowser_datadir* g:tmboxbrowser_datadir (Default: split(&runtimepath, ',')[0] .'/tmbox') Where to save auxiliary data. *g:tmboxbrowser_path* g:tmboxbrowser_path (Default: "") The (comma-separated) path to your mboxes. Key maps~ These are effective in the __MBOX_Browser__ window: i ... Display the index list (in gvim, you can also use ) p ... Select parts or re-view mail q ... Quit the browser ? ... Help ... Page down ... Page up Requirements~ tlib (vimscript#1863) is required.