X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=vim%2Fplugin%2Fnotmuch.vim;h=a9754f2b4b2e94de332931806260323ff674bfaa;hp=341f3370dad8d821062a0bb3aa87c50b17cbaf50;hb=2805a243b5564bb99196923f96118ace4c55e870;hpb=8bafbac75c53631d0db351e20fb5f5bb0fcfb48e diff --git a/vim/plugin/notmuch.vim b/vim/plugin/notmuch.vim index 341f3370..a9754f2b 100644 --- a/vim/plugin/notmuch.vim +++ b/vim/plugin/notmuch.vim @@ -33,17 +33,17 @@ let s:notmuch_defaults = { \ 'g:notmuch_show_fold_bodies': 0 , \ 'g:notmuch_show_fold_headers': 1 , \ - \ 'g:notmuch_show_message_begin_regexp': '^ message{' , - \ 'g:notmuch_show_message_end_regexp': '^ message}' , - \ 'g:notmuch_show_header_begin_regexp': '^ header{' , - \ 'g:notmuch_show_header_end_regexp': '^ header}' , - \ 'g:notmuch_show_body_begin_regexp': '^ body{' , - \ 'g:notmuch_show_body_end_regexp': '^ body}' , - \ 'g:notmuch_show_attachment_begin_regexp': '^ attachment{' , - \ 'g:notmuch_show_attachment_end_regexp': '^ attachment}' , - \ 'g:notmuch_show_part_begin_regexp': '^ part{' , - \ 'g:notmuch_show_part_end_regexp': '^ part}' , - \ 'g:notmuch_show_marker_regexp': '^ \\(message\\|header\\|body\\|attachment\\|part\\)[{}].*$', + \ 'g:notmuch_show_message_begin_regexp': ' message{' , + \ 'g:notmuch_show_message_end_regexp': ' message}' , + \ 'g:notmuch_show_header_begin_regexp': ' header{' , + \ 'g:notmuch_show_header_end_regexp': ' header}' , + \ 'g:notmuch_show_body_begin_regexp': ' body{' , + \ 'g:notmuch_show_body_end_regexp': ' body}' , + \ 'g:notmuch_show_attachment_begin_regexp': ' attachment{' , + \ 'g:notmuch_show_attachment_end_regexp': ' attachment}' , + \ 'g:notmuch_show_part_begin_regexp': ' part{' , + \ 'g:notmuch_show_part_end_regexp': ' part}' , + \ 'g:notmuch_show_marker_regexp': ' \\(message\\|header\\|body\\|attachment\\|part\\)[{}].*$', \ \ 'g:notmuch_show_message_parse_regexp': '\(id:[^ ]*\) depth:\([0-9]*\) match:\([0-9]*\) filename:\(.*\)$', \ 'g:notmuch_show_tags_regexp': '(\([^)]*\))$' , @@ -53,6 +53,7 @@ let s:notmuch_defaults = { \ \ 'g:notmuch_show_citation_regexp': '^\s*>' , \ + \ 'g:notmuch_compose_insert_mode_start': 1 , \ 'g:notmuch_compose_header_help': 1 , \ 'g:notmuch_compose_temp_file_dir': '~/.notmuch/compose/' , \ } @@ -104,6 +105,7 @@ let s:notmuch_compose_headers_defaults = [ " --- --- bindings for folders mode {{{2 let g:notmuch_folders_maps = { + \ 'm': ':call NM_new_mail()', \ 's': ':call NM_search_prompt()', \ 'q': ':call NM_kill_this_buffer()', \ '=': ':call NM_folders_refresh_view()', @@ -112,7 +114,8 @@ let g:notmuch_folders_maps = { " --- --- bindings for search screen {{{2 let g:notmuch_search_maps = { - \ '': ':call NM_search_show_thread()', + \ '': ':call NM_search_show_thread(0)', + \ '': ':call NM_search_show_thread(1)', \ '': ':call NM_search_expand('''')', \ 'a': ':call NM_search_archive_thread()', \ 'f': ':call NM_search_filter()', @@ -126,7 +129,7 @@ let g:notmuch_search_maps = { \ '+': ':call NM_search_add_tags([])', \ '-': ':call NM_search_remove_tags([])', \ '=': ':call NM_search_refresh_view()', - \ '?': ':echo NM_search_thread_id()', + \ '?': ':echo NM_search_thread_id() . '' @ '' . join(NM_get_search_words())', \ } " --- --- bindings for show screen {{{2 @@ -135,11 +138,12 @@ let g:notmuch_show_maps = { \ '': ':call NM_show_next(1, 0)', \ '': ':call NM_search_expand('''')', \ 'q': ':call NM_kill_this_buffer()', + \ 's': ':call NM_search_prompt()', \ \ 'b': ':call NM_show_fold_toggle(''b'', ''bdy'', !g:notmuch_show_fold_bodies)', \ 'c': ':call NM_show_fold_toggle(''c'', ''cit'', !g:notmuch_show_fold_citations)', \ 'h': ':call NM_show_fold_toggle(''h'', ''hdr'', !g:notmuch_show_fold_headers)', - \ 's': ':call NM_show_fold_toggle(''s'', ''sig'', !g:notmuch_show_fold_signatures)', + \ 'i': ':call NM_show_fold_toggle(''s'', ''sig'', !g:notmuch_show_fold_signatures)', \ \ 'a': ':call NM_show_archive_thread()', \ 'A': ':call NM_show_mark_read_then_archive_thread()', @@ -156,7 +160,7 @@ let g:notmuch_show_maps = { \ \ 'r': ':call NM_show_reply()', \ 'm': ':call NM_new_mail()', - \ '?': ':echo NM_show_message_id() . '' @ '' . join(NM_show_search_words())', + \ '?': ':echo NM_show_message_id() . '' @ '' . join(NM_get_search_words())', \ } " --- --- bindings for compose screen {{{2 @@ -174,7 +178,7 @@ let g:notmuch_compose_imaps = { function! s:NM_cmd_folders(words) if len(a:words) - echoe 'Not exapecting any arguments for folders command.' + throw 'Not expecting any arguments for folders command.' endif let cmd = ['count'] let disp = [] @@ -187,7 +191,7 @@ function! s:NM_cmd_folders(words) call add(searches, search) endfor - call NM_newBuffer('folders', join(disp, "\n")) + call NM_newBuffer('', 'folders', join(disp, "\n")) let b:nm_searches = searches let b:nm_timestamp = reltime() @@ -235,7 +239,7 @@ function! s:NM_cmd_search(words) let disp = copy(lines) call map(disp, 's:NM_cmd_search_fmtline(v:val)') - call NM_newBuffer('search', join(disp, "\n")) + call NM_newBuffer('', 'search', join(disp, "\n")) let b:nm_raw_lines = lines let b:nm_search_words = a:words @@ -263,17 +267,15 @@ endfunction " --- --- search screen action functions {{{2 -function! s:NM_search_show_thread() - let id = NM_search_thread_id() - if id != '' - let words = [id] - if exists('b:nm_search_words') - let words = ['('] + b:nm_search_words + [')', 'and', id] - endif - if len(words) - call NM_cmd_show(words) - endif +function! s:NM_search_show_thread(everything) + let words = [ NM_search_thread_id() ] + if !a:everything && exists('b:nm_search_words') + call extend(words, ['AND', '(']) + call extend(words, b:nm_search_words) + call add(words, ')') endif + call NM_cmd_show(words) + let b:nm_show_everything = a:everything endfunction function! s:NM_search_prompt() @@ -322,23 +324,13 @@ endfunction function! s:NM_search_filter_helper(prompt, prefix, joiner) " TODO: input() can support completion - let text = input(a:prompt) + let text = substitute(input(a:prompt), '\v(^\s*|\s*$|\n)', '', 'g') if !strlen(text) return endif - let tags = split(text) - if strlen(a:prefix) - call map(tags, 'a:prefix . v:val') - endif - if strlen(a:joiner) - let idx = len(tags) - 1 - while idx > 0 - call insert(tags, a:joiner, idx) - let idx = idx - 1 - endwhile - endif - let tags = b:nm_search_words + ['and', '('] + tags + [')'] + let tags = b:nm_search_words + ['AND'] + \ + NM_combine_tags(a:prefix, split(text), a:joiner, '()') let prev_bufnr = bufnr('%') setlocal bufhidden=hide @@ -355,7 +347,14 @@ function! s:NM_search_toggle_order() endfunction function! s:NM_search_reply_to_thread() - echo 'not implemented' + let cmd = ['reply'] + call add(cmd, NM_search_thread_id()) + call add(cmd, 'AND') + call extend(cmd, NM_get_search_words()) + + let data = NM_run(cmd) + let lines = split(data, "\n") + call NM_newComposeBuffer(lines, 0) endfunction function! s:NM_search_add_tags(tags) @@ -380,14 +379,12 @@ endfunction function! s:NM_search_thread_id() if !exists('b:nm_raw_lines') - echoe 'no b:nm_raw_lines' - return '' + throw 'Eeek! no b:nm_raw_lines' endif let mnum = line('.') - 1 if len(b:nm_raw_lines) <= mnum return '' endif - echo 'len=' . string(len(b:nm_raw_lines)) . ' mnum=' . string(mnum) let info = b:nm_raw_lines[mnum] let what = split(info, '\s\+')[0] return what @@ -412,15 +409,15 @@ endfunction function! s:NM_cmd_show(words) let prev_bufnr = bufnr('%') - let data = s:NM_run(['show'] + a:words) + let data = s:NM_run(['show', '--entire-thread'] + a:words) let lines = split(data, "\n") let info = s:NM_cmd_show_parse(lines) setlocal bufhidden=hide - call NM_newBuffer('show', join(info['disp'], "\n")) + call NM_newBuffer('', 'show', join(info['disp'], "\n")) setlocal bufhidden=delete - let b:nm_words = a:words + let b:nm_search_words = a:words let b:nm_raw_info = info let b:nm_prev_bufnr = prev_bufnr @@ -434,6 +431,7 @@ function! s:NM_cmd_show(words) endfunction function! s:NM_show_previous(can_change_thread, find_matching) + let everything = exists('b:nm_show_everything') ? b:nm_show_everything : 0 let info = b:nm_raw_info let lnum = line('.') for msg in reverse(copy(info['msgs'])) @@ -454,7 +452,7 @@ function! s:NM_show_previous(can_change_thread, find_matching) call NM_kill_this_buffer() if line('.') > 1 norm k - call NM_search_show_thread() + call NM_search_show_thread(everything) norm G call NM_show_previous(0, a:find_matching) else @@ -483,10 +481,11 @@ function! s:NM_show_next(can_change_thread, find_matching) endfunction function! s:NM_show_next_thread() + let everything = exists('b:nm_show_everything') ? b:nm_show_everything : 0 call NM_kill_this_buffer() if line('.') != line('$') norm j - call NM_search_show_thread() + call NM_search_show_thread(everything) else echo 'No more messages.' endif @@ -509,7 +508,14 @@ function! s:NM_show_previous_message() endfunction function! s:NM_show_reply() - echo 'not implemented' + let cmd = ['reply'] + call add(cmd, NM_show_message_id()) + call add(cmd, 'AND') + call extend(cmd, NM_get_search_words()) + + let data = NM_run(cmd) + let lines = split(data, "\n") + call NM_newComposeBuffer(lines, 0) endfunction function! s:NM_show_view_all_mime_parts() @@ -539,7 +545,7 @@ function! s:NM_show_advance_marking_read_and_archiving() let msg_top = NM_show_get_message_for_line(vis_top) if !has_key(msg_top,'id') - echo "No top visible message." + throw "No top visible message." endif " if the top message is the last message, just expunge the entire thread and move on @@ -547,14 +553,12 @@ function! s:NM_show_advance_marking_read_and_archiving() let ids = [] for msg in b:nm_raw_info['msgs'] if has_key(msg,'match') && msg['match'] != '0' - if len(ids) - call add(ids, 'OR') - endif call add(ids, msg['id']) endif endfor - - let filter = ['('] + advance_tags + [')', 'AND', '('] + ids + [')'] + let filter = NM_combine_tags('tag:', advance_tags, 'OR', '()') + \ + ['AND'] + \ + NM_combine_tags('', ids, 'OR', '()') call NM_add_remove_tags(filter, '-', advance_tags) call NM_show_next(1, 1) return @@ -562,7 +566,7 @@ function! s:NM_show_advance_marking_read_and_archiving() let msg_bot = NM_show_get_message_for_line(vis_bot) if !has_key(msg_bot,'id') - echo "No bottom visible message." + throw "No bottom visible message." endif " if entire message fits on the screen, read/archive it, move to the next one @@ -573,7 +577,8 @@ function! s:NM_show_advance_marking_read_and_archiving() if has_key(msg_top,'match') && msg_top['match'] != '0' redraw " do this last to hide the latency - let filter = ['('] + advance_tags + [')', 'AND', msg_top['id']] + let filter = NM_combine_tags('tag:', advance_tags, 'OR', '()') + \ + ['AND', msg_top['id']] call NM_add_remove_tags(filter, '-', advance_tags) endif return @@ -620,8 +625,7 @@ endfunction function! s:NM_show_message_id() if !exists('b:nm_raw_info') - echoe 'no b:nm_raw_info' - return '' + throw 'Eeek! no b:nm_raw_info' endif let msg = NM_show_get_message_for_line(line('.')) if has_key(msg,'id') @@ -630,14 +634,6 @@ function! s:NM_show_message_id() return '' endfunction -function! s:NM_show_search_words() - if !exists('b:nm_words') - echoe 'no b:nm_words' - return [] - endif - return b:nm_words -endfunction - function! s:NM_show_fold_toggle(key, type, fold) let info = b:nm_raw_info let act = 'open' @@ -645,9 +641,19 @@ function! s:NM_show_fold_toggle(key, type, fold) let act = 'close' endif for fld in info['folds'] - if fld[0] == a:type - exec printf('%dfold%s', fld[1], act) + if fld[0] != a:type + continue endif + "let idx = fld[3] + "let msg = info['msgs'][idx] + "if has_key(msg,'match') && msg['match'] == '0' + " continue + "endif + let cls = foldclosed(fld[1]) + if cls != -1 && cls != fld[1] + continue + endif + exec printf('%dfold%s', fld[1], act) endfor exec printf('nnoremap %s :call NM_show_fold_toggle(''%s'', ''%s'', %d)', a:key, a:key, a:type, !a:fold) endfunction @@ -877,18 +883,12 @@ endfunction " --- implement compose screen {{{1 function! s:NM_cmd_compose(words, body_lines) - let lines = !g:notmuch_compose_header_help ? [] : [ - \ 'Notmuch-Help: Type in your message here; to help you use these bindings:', - \ 'Notmuch-Help: ,a - attach a file', - \ 'Notmuch-Help: ,s - send the message (Notmuch-Help lines will be removed)', - \ 'Notmuch-Help: ,q - abort the message', - \ 'Notmuch-Help: - skip through header lines', - \ ] + let lines = [] let start_on_line = 0 let hdrs = { } for word in a:words - let m = matchlist(word, '^\([^:]\+\):\s*\(.*\)\s*$') + let m = matchlist(word, '^\(\w[^:]*\):\s*\(.*\)\s*$') if !len(m) throw 'Eeek! bad parameter ''' . string(word) . '''' endif @@ -921,7 +921,7 @@ function! s:NM_cmd_compose(words, body_lines) endif endfor - call extend(lines, [ '', '' ]) + call add(lines, '') if !start_on_line let start_on_line = len(lines) + 1 endif @@ -929,26 +929,110 @@ function! s:NM_cmd_compose(words, body_lines) if len(a:body_lines) call extend(lines, a:body_lines) else - call add(lines, '') + call extend(lines, [ '', '' ]) endif - call extend(lines, g:notmuch_signature) - - let prev_bufnr = bufnr('%') - setlocal bufhidden=hide - call NM_newFileBuffer(g:notmuch_compose_temp_file_dir, '%s.mail', - \ 'compose', lines) - setlocal bufhidden=hide - - call NM_set_map('n', g:notmuch_compose_nmaps) - call NM_set_map('i', g:notmuch_compose_imaps) - call cursor(start_on_line, strlen(start_on_line) + 1) - startinsert! - echo 'Type your message, use to jump to next header and then body.' + call NM_newComposeBuffer(lines, start_on_line) endfunction function! s:NM_compose_send() - echo 'not implemented' + call NM_assert_buffer_type('compose') + let fname = expand('%') + let lnum = 1 + let line = getline(lnum) + let hdrs = {} + let lst_hdr = '' + while match(line, '^$') == -1 + if match(line, '^Notmuch-Help:') != -1 + " skip it + elseif strlen(lst_hdr) && match(line, '^\s') != -1 + let hdrs[lst_hdr][-1] = hdrs[lst_hdr][-1] . substitute(line, '^\s*', ' ', '') + else + let m = matchlist(line, '^\(\w[^:]*\):\s*\(.*\)\s*$') + if !len(m) + cursor(lnum, 0) + throw printf('Eeek! invalid header on line %d', lnum) + endif + let key = substitute(m[1], '\<\w', '\U&', 'g') + if strlen(m[2]) + if !has_key(hdrs, key) + let hdrs[key] = [] + endif + call add(hdrs[key], m[2]) + endif + let lst_hdr = key + endif + let lnum = lnum + 1 + let line = getline(lnum) + endwhile + let body_starts = lnum + + "[-a header] [-b bcc-addr] [-c cc-addr] [-s subject] to-addr + let cmd = ['mail'] + let tos = [] + for [key, vals] in items(hdrs) + if key == 'To' + call extend(tos, vals) + elseif key == 'Bcc' + for adr in vals + call add(cmd, '-b') + call add(cmd, adr) + endfor + elseif key == 'Cc' + for adr in vals + call add(cmd, '-c') + call add(cmd, adr) + endfor + elseif key == 'Subject' + for txt in vals + call add(cmd, '-s') + call add(cmd, txt) + endfor + else + for val in vals + call add(cmd, '-a') + call add(cmd, key . ': ' . val) + endfor + endif + endfor + call extend(cmd, tos) + + " TODO: make sure we have at least one target + " TODO: ask about empty jubject, etc + + exec printf('0,%dd', body_starts) + write + + call map(cmd, 's:NM_shell_escape(v:val)') + let cmdtxt = join(cmd) . '< ' . fname + let out = system(cmdtxt) + let err = v:shell_error + if err + undo + write + call NM_newBuffer('new', 'error', + \ "While running...\n" . + \ ' ' . cmdtxt . "\n" . + \ "\n" . + \ "Failed with...\n" . + \ substitute(out, '^', ' ', 'g')) + echohl Error + echo 'Eeek! unable to send mail' + echohl None + return + endif + + if !exists('b:nm_prev_bufnr') + bdelete + else + let prev_bufnr = b:nm_prev_bufnr + bdelete + if prev_bufnr == bufnr('%') + exec printf("buffer %d", prev_bufnr) + endif + endif + call delete(fname) + echo 'Mail sent successfully.' endfunction function! s:NM_compose_attach() @@ -958,7 +1042,6 @@ endfunction function! s:NM_compose_next_entry_area() let lnum = line('.') let hdr_end = NM_compose_find_line_match(1,'^$',1) - echo 'header end = ' . string(hdr_end) if lnum < hdr_end let lnum = lnum + 1 let line = getline(lnum) @@ -976,7 +1059,6 @@ function! s:NM_compose_next_entry_area() call cursor(lnum+1, strlen(getline(lnum+1)) + 1) return '' endif - echo 'mode=' . mode() if mode() == 'i' if !getbufvar(bufnr('.'), '&et') return "\t" @@ -1014,8 +1096,12 @@ endfunction " --- notmuch helper functions {{{1 -function! s:NM_newBuffer(type, content) - enew +function! s:NM_newBuffer(how, type, content) + if strlen(a:how) + exec a:how + else + enew + endif setlocal buftype=nofile readonly modifiable scrolloff=0 sidescrolloff=0 silent put=a:content keepjumps 0d @@ -1041,6 +1127,57 @@ function! s:NM_newFileBuffer(fdir, fname, type, lines) let b:nm_type = a:type endfunction +function! s:NM_newComposeBuffer(lines, start_on_line) + let lines = a:lines + let start_on_line = a:start_on_line + let real_hdr_start = 1 + if g:notmuch_compose_header_help + let help_lines = [ + \ 'Notmuch-Help: Type in your message here; to help you use these bindings:', + \ 'Notmuch-Help: ,a - attach a file', + \ 'Notmuch-Help: ,s - send the message (Notmuch-Help lines will be removed)', + \ 'Notmuch-Help: ,q - abort the message', + \ 'Notmuch-Help: - skip through header lines', + \ ] + call extend(lines, help_lines, 0) + let real_hdr_start = len(help_lines) + if start_on_line > 0 + let start_on_line = start_on_line + len(help_lines) + endif + endif + call extend(lines, g:notmuch_signature) + + + let prev_bufnr = bufnr('%') + setlocal bufhidden=hide + call NM_newFileBuffer(g:notmuch_compose_temp_file_dir, '%s.mail', + \ 'compose', lines) + setlocal bufhidden=hide + let b:nm_prev_bufnr = prev_bufnr + + call NM_set_map('n', g:notmuch_compose_nmaps) + call NM_set_map('i', g:notmuch_compose_imaps) + + if start_on_line > 0 && start_on_line <= len(lines) + call cursor(start_on_line, strlen(getline(start_on_line)) + 1) + else + call cursor(real_hdr_start, strlen(getline(real_hdr_start)) + 1) + call NM_compose_next_entry_area() + endif + + if g:notmuch_compose_insert_mode_start + startinsert! + endif + echo 'Type your message, use to jump to next header and then body.' +endfunction + +function! s:NM_assert_buffer_type(type) + if !exists('b:nm_type') || b:nm_type != a:type + throw printf('Eeek! expected type %s, but got %s.', a:type, + \ exists(b:nm_type) ? b:nm_type : 'something else') + endif +endfunction + function! s:NM_mktemp(dir, name) let time_stamp = strftime('%Y%m%d-%H%M%S') let file_name = substitute(a:dir,'/*$','/','') . printf(a:name, time_stamp) @@ -1121,15 +1258,47 @@ endfunction " --- external mail handling helpers {{{1 function! s:NM_new_mail() - echo 'not implemented' + call NM_cmd_compose([], []) +endfunction + +" --- tag manipulation helpers {{{1 + +" used to combine an array of words with prefixes and separators +" example: +" NM_combine_tags('tag:', ['one', 'two', 'three'], 'OR', '()') +" -> ['(', 'tag:one', 'OR', 'tag:two', 'OR', 'tag:three', ')'] +function! s:NM_combine_tags(word_prefix, words, separator, brackets) + let res = [] + for word in a:words + if len(res) && strlen(a:separator) + call add(res, a:separator) + endif + call add(res, a:word_prefix . word) + endfor + if len(res) > 1 && strlen(a:brackets) + if strlen(a:brackets) != 2 + throw 'Eeek! brackets arg to NM_combine_tags must be 2 chars' + endif + call insert(res, a:brackets[0]) + call add(res, a:brackets[1]) + endif + return res endfunction " --- other helpers {{{1 +function! s:NM_get_search_words() + if !exists('b:nm_search_words') + throw 'Eeek! no b:nm_search_words' + endif + return b:nm_search_words +endfunction + function! s:NM_kill_this_buffer() if exists('b:nm_prev_bufnr') - setlocal bufhidden=delete - exec printf(":buffer %d", b:nm_prev_bufnr) + let prev_bufnr = b:nm_prev_bufnr + bdelete! + exec printf("buffer %d", prev_bufnr) else echo "This is the last buffer; use :q to quit." endif @@ -1147,7 +1316,7 @@ endfunction function! s:NM_add_remove_tags(filter, prefix, tags) let filter = len(a:filter) ? a:filter : [NM_search_thread_id()] if !len(filter) - echoe 'Eeek! I couldn''t find the thead id!' + throw 'Eeek! I couldn''t find the thead id!' endif call map(a:tags, 'a:prefix . v:val') let args = ['tag'] @@ -1166,7 +1335,7 @@ function! s:NM_add_remove_tags_on_screen(online, prefix, tags) endfor else for tagname in a:tags - exec printf('silent! %ss/(\([^)]*\)\([^)]*\))$/(\1 %s)/', string(a:online), tagname) + exec printf('silent! %ss/(\([^)]*\))$/(\1 %s)/', string(a:online), tagname) endfor endif setlocal nomodifiable @@ -1222,7 +1391,7 @@ function! s:NM_set_map(type, maps) exec printf('%snoremap %s %s', a:type, key, code) endfor " --- this is a hack for development :) - nnoremap ,nmr :source ~/.vim/plugin/notmuch.vim:call NotMuch('') + nnoremap ,nmr :runtime! plugin/notmuch.vim endfunction " --- command handler {{{1