aboutsummaryrefslogtreecommitdiff
path: root/vim/plugin/notmuch-vimpy.vim
diff options
context:
space:
mode:
Diffstat (limited to 'vim/plugin/notmuch-vimpy.vim')
-rw-r--r--vim/plugin/notmuch-vimpy.vim863
1 files changed, 863 insertions, 0 deletions
diff --git a/vim/plugin/notmuch-vimpy.vim b/vim/plugin/notmuch-vimpy.vim
new file mode 100644
index 0000000..8d80a0e
--- /dev/null
+++ b/vim/plugin/notmuch-vimpy.vim
@@ -0,0 +1,863 @@
+" nm-vimpy.vim plugin --- run notmuch within vim
+"
+" This file is part of Notmuch.
+"
+" Notmuch is free software: you can redistribute it and/or modify it
+" under the terms of the GNU General Public License as published by
+" the Free Software Foundation, either version 3 of the License, or
+" (at your option) any later version.
+"
+" Notmuch is distributed in the hope that it will be useful, but
+" WITHOUT ANY WARRANTY; without even the implied warranty of
+" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+" General Public License for more details.
+"
+" You should have received a copy of the GNU General Public License
+" along with Notmuch. If not, see <http://www.gnu.org/licenses/>.
+"
+" Based on the notmuch.vim plugin by:
+" Authors: Bart Trojanowski <bart@jukie.net>
+" Contributors: Felipe Contreras <felipe.contreras@gmail.com>,
+" Peter Hartman <peterjohnhartman@gmail.com>
+"
+" Mostly rewritten using python by Anton Khirnov <anton@khirnov.net>
+"
+
+
+" --- initialization
+if exists('s:nm_vimpy_loaded') || &cp
+ finish
+endif
+
+" init the python layer
+let s:python_path = expand('<sfile>:p:h')
+python import sys
+exec "python sys.path += [r'" . s:python_path . "']"
+python import vim, nm_vim
+
+command! NMVimpy call NMVimpy()
+
+" --- configuration defaults
+
+let s:nm_vimpy_defaults = {
+ \ 'g:notmuch_cmd': 'notmuch' ,
+ \
+ \ 'g:notmuch_search_newest_first': 1 ,
+ \
+ \ 'g:notmuch_compose_insert_mode_start': 1 ,
+ \ 'g:notmuch_compose_header_help': 1 ,
+ \ 'g:notmuch_compose_temp_file_dir': '~/.notmuch/compose/' ,
+ \ 'g:nm_vimpy_fcc_maildir': 'sent' ,
+ \ 'g:nm_vimpy_user_agent': 'notmuch-vimpy' ,
+ \ }
+
+" defaults for g:nm_vimpy_folders
+" override with: let g:nm_vimpy_folders = [ ... ]
+let s:nm_vimpy_folders_defaults = [
+ \ [ 'new', 'tag:inbox and tag:unread' ],
+ \ [ 'inbox', 'tag:inbox' ],
+ \ [ 'unread', 'tag:unread' ],
+ \ ]
+
+let s:nm_vimpy_show_headers_defaults = [
+ \ 'From',
+ \ 'To',
+ \ 'Cc',
+ \ 'Subject',
+ \ 'Date',
+ \ 'Reply-To',
+ \ 'Message-Id',
+ \]
+
+" defaults for g:nm_vimpy_compose_headers
+" override with: let g:nm_vimpy_compose_headers = [ ... ]
+let s:nm_vimpy_compose_headers_defaults = [
+ \ 'From',
+ \ 'To',
+ \ 'Cc',
+ \ 'Bcc',
+ \ 'Subject'
+ \ ]
+
+" --- keyboard mapping definitions
+
+" --- --- bindings for folders mode {{{2
+
+let g:nm_vimpy_folders_maps = {
+ \ 'm': ':call <SID>NM_new_mail()<CR>',
+ \ 's': ':call <SID>NM_search_prompt(0)<CR>',
+ \ 'q': ':call <SID>NM_kill_this_buffer()<CR>',
+ \ '=': ':call <SID>NM_folders_refresh_view()<CR>',
+ \ '<Enter>': ':call <SID>NM_folders_show_search('''')<CR>',
+ \ '<Space>': ':call <SID>NM_folders_show_search(''tag:unread'')<CR>',
+ \ 'tt': ':call <SID>NM_folders_from_tags()<CR>',
+ \ }
+
+" --- --- bindings for search screen {{{2
+let g:nm_vimpy_search_maps = {
+ \ '<Enter>': ':call <SID>NM_search_show_thread()<CR>',
+ \ '<Space>': ':call <SID>NM_search_show_thread_unread()<CR>',
+ \ '<C-]>': ':call <SID>NM_search_expand(''<cword>'')<CR>',
+ \ 'a': ':call <SID>NM_search_archive_thread()<CR>',
+ \ 'A': ':call <SID>NM_search_mark_read_then_archive_thread()<CR>',
+ \ 'D': ':call <SID>NM_search_delete_thread()<CR>',
+ \ 'f': ':call <SID>NM_search_filter()<CR>',
+ \ 'm': ':call <SID>NM_new_mail()<CR>',
+ \ 'o': ':call <SID>NM_search_toggle_order()<CR>',
+ \ 'r': ':call <SID>NM_search_reply_to_thread()<CR>',
+ \ 's': ':call <SID>NM_search_prompt(0)<CR>',
+ \ ',s': ':call <SID>NM_search_prompt(1)<CR>',
+ \ 'q': ':call <SID>NM_kill_this_buffer()<CR>',
+ \ '+': ':call <SID>NM_search_add_tags([])<CR>',
+ \ '-': ':call <SID>NM_search_remove_tags([])<CR>',
+ \ '=': ':call <SID>NM_search_refresh_view()<CR>',
+ \ }
+
+" --- --- bindings for show screen {{{2
+let g:nm_vimpy_show_maps = {
+ \ '<C-P>': ':call <SID>NM_jump_message(-1)<CR>',
+ \ '<C-N>': ':call <SID>NM_jump_message(+1)<CR>',
+ \ '<C-]>': ':call <SID>NM_search_expand(''<cword>'')<CR>',
+ \ 'q': ':call <SID>NM_kill_this_buffer()<CR>',
+ \ 's': ':call <SID>NM_search_prompt(0)<CR>',
+ \
+ \
+ \ 'a': ':call <SID>NM_show_archive_thread()<CR>',
+ \ 'A': ':call <SID>NM_show_mark_read_then_archive_thread()<CR>',
+ \ 'N': ':call <SID>NM_show_mark_read_then_next_open_message()<CR>',
+ \ 'v': ':call <SID>NM_show_view_all_mime_parts()<CR>',
+ \ '+': ':call <SID>NM_show_add_tag()<CR>',
+ \ '-': ':call <SID>NM_show_remove_tag()<CR>',
+ \ '<Space>': ':call <SID>NM_show_advance()<CR>',
+ \ '\|': ':call <SID>NM_show_pipe_message()<CR>',
+ \
+ \ '<Enter>': ':call <SID>NM_show_view_attachment()<CR>',
+ \ 'S': ':call <SID>NM_show_save_attachment()<CR>',
+ \
+ \ 'r': ':call <SID>NM_show_reply()<CR>',
+ \ 'R': ':call <SID>NM_show_view_raw_message()<CR>',
+ \ 'm': ':call <SID>NM_new_mail()<CR>',
+ \ }
+
+" --- --- bindings for compose screen {{{2
+let g:nm_vimpy_compose_nmaps = {
+ \ ',s': ':call <SID>NM_compose_send()<CR>',
+ \ ',a': ':call <SID>NM_compose_attach()<CR>',
+ \ ',q': ':call <SID>NM_kill_this_buffer()<CR>',
+ \ '<Tab>': ':call <SID>NM_compose_next_entry_area()<CR>',
+ \ }
+
+let g:nm_vimpy_raw_message_nmaps = {
+ \ 'q': ':call <SID>NM_kill_this_buffer()<CR>',
+ \ }
+
+" --- implement folders screen {{{1
+
+" Create the folders buffer.
+" Takes a list of [ folder name, query string]
+function! s:NM_cmd_folders(folders)
+ call <SID>NM_create_buffer('folders')
+ silent 0put!='<Enter>:view <Space>:view unread s:search =:refresh tt:all tags m:new mail'
+ python nm_vim.SavedSearches(vim.eval("a:folders"))
+ call <SID>NM_finalize_menu_buffer()
+ call <SID>NM_set_map('n', g:nm_vimpy_folders_maps)
+endfunction
+
+" Show a folder for each existing tag.
+function! s:NM_folders_from_tags()
+ let folders = []
+ python nm_vim.vim_get_tags()
+ for tag in split(taglist, '\n')
+ call add(folders, [tag, 'tag:' . tag ])
+ endfor
+
+ call <SID>NM_cmd_folders(folders)
+endfunction
+
+" --- --- folders screen action functions {{{2
+
+" Refresh the folders screen
+function! s:NM_folders_refresh_view()
+ let lno = line('.')
+ setlocal modifiable
+ silent norm 3GdG
+ python nm_vim.get_current_buffer().refresh()
+ setlocal nomodifiable
+ exec printf('norm %dG', lno)
+endfunction
+
+" Show contents of the folder corresponding to current line AND query
+function! s:NM_folders_show_search(query)
+ exec printf('python nm_vim.vim_get_object(%d, 0)', line('.'))
+ if exists('obj')
+ if len(a:query)
+ let querystr = '(' . obj['id'] . ') and ' . a:query
+ else
+ let querystr = obj['id']
+ endif
+
+ call <SID>NM_cmd_search(querystr, 0)
+ endif
+endfunction
+
+" Create the search buffer corresponding to querystr.
+" If relative is 1, the search is relative to current buffer
+function! s:NM_cmd_search(querystr, relative)
+ let cur_buf = bufnr('%')
+ call <SID>NM_create_buffer('search')
+ silent 0put!=printf(' Query results: %s', a:querystr)
+ if a:relative
+ python nm_vim.Search(querystr = vim.eval("a:querystr"), parent = nm_vim.nm_buffers[vim.eval('cur_buf')])
+ else
+ python nm_vim.Search(querystr = vim.eval("a:querystr"))
+ endif
+ call <SID>NM_finalize_menu_buffer()
+ call <SID>NM_set_map('n', g:nm_vimpy_search_maps)
+endfunction
+
+" --- --- search screen action functions {{{2
+
+" Show the thread corresponding to current line
+function! s:NM_search_show_thread()
+ let querystr = <SID>NM_search_thread_id()
+ if len(querystr)
+ call <SID>NM_cmd_show(querystr)
+ endif
+endfunction
+
+" Same as NM_search_show_thread, except jump to first unread
+function! s:NM_search_show_thread_unread()
+ call <SID>NM_search_show_thread()
+ python nm_vim.vim_get_message_for_tag('unread')
+ if start > 0
+ exec printf('norm %dGzt', start)
+ silent! norm zo
+ endif
+endfunction
+
+" Search according to input from user.
+" If edit is 1, current query string is inserted to prompt for editing.
+function! s:NM_search_prompt(edit)
+ if a:edit
+ python nm_vim.vim_get_id()
+ else
+ let buf_id = ''
+ endif
+ let querystr = input('Search: ', buf_id, 'custom,NM_search_type_completion')
+ if len(querystr)
+ call <SID>NM_cmd_search(querystr, 0)
+ endif
+endfunction
+
+" Filter current search, i.e. search for
+" (current querystr) AND (user input)
+function! s:NM_search_filter()
+ let querystr = input('Filter: ', '', 'custom,NM_search_type_completion')
+ if len(querystr)
+ call <SID>NM_cmd_search(querystr, 1)
+ endif
+endfunction
+
+""""""""""""""""""""""'' TODO
+function! s:NM_search_archive_thread()
+ call <SID>NM_tag([], ['-inbox'])
+ norm j
+endfunction
+
+function! s:NM_search_mark_read_then_archive_thread()
+ call <SID>NM_tag([], ['-unread', '-inbox'])
+ norm j
+endfunction
+
+function! s:NM_search_delete_thread()
+ call <SID>NM_tag([], ['+junk','-inbox','-unread'])
+ norm j
+endfunction
+
+"""""""""""""""""""""""""""""""""""""""""""""""""""""
+
+" XXX This function is broken
+function! s:NM_search_toggle_order()
+ let g:notmuch_search_newest_first = !g:notmuch_search_newest_first
+ " FIXME: maybe this would be better done w/o reading re-reading the lines
+ " reversing the b:nm_raw_lines and the buffer lines would be better
+ call <SID>NM_search_refresh_view()
+endfunction
+
+"XXX this function is broken
+function! s:NM_search_reply_to_thread()
+ python vim.command('let querystr = "%s"'%nm_vim.get_current_buffer().id)
+ let cmd = ['reply']
+ call add(cmd, <SID>NM_search_thread_id())
+ call add(cmd, 'AND')
+ call extend(cmd, [querystr])
+
+ let data = <SID>NM_run(cmd)
+ let lines = split(data, "\n")
+ call <SID>NM_newComposeBuffer(lines, 0)
+endfunction
+
+function! s:NM_search_add_tags(tags)
+ call <SID>NM_search_add_remove_tags('Add Tag(s): ', '+', a:tags)
+endfunction
+
+function! s:NM_search_remove_tags(tags)
+ call <SID>NM_search_add_remove_tags('Remove Tag(s): ', '-', a:tags)
+endfunction
+
+function! s:NM_search_refresh_view()
+ let lno = line('.')
+ setlocal modifiable
+ norm 3ggdG
+ python nm_vim.get_current_buffer().refresh()
+ setlocal nomodifiable
+ " FIXME: should find the line of the thread we were on if possible
+ exec printf('norm %dG', lno)
+endfunction
+
+" --- --- search screen helper functions {{{2
+
+function! s:NM_search_thread_id()
+ exec printf('python nm_vim.vim_get_object(%d, 0)', line('.'))
+ if exists('obj')
+ return 'thread:' . obj['id']
+ endif
+ return ''
+endfunction
+
+function! s:NM_search_add_remove_tags(prompt, prefix, intags)
+ if type(a:intags) != type([]) || len(a:intags) == 0
+ let text = input(a:prompt, '', 'custom,NM_tag_name_completion')
+ if !strlen(text)
+ return
+ endif
+ let tags = split(text, ' ')
+ else
+ let tags = a:intags
+ endif
+ call map(tags, 'a:prefix . v:val')
+ call <SID>NM_tag([], tags)
+endfunction
+
+" --- implement show screen {{{1
+
+function! s:NM_cmd_show(querystr)
+ "TODO: folding, syntax
+ call <SID>NM_create_buffer('show')
+ python nm_vim.ShowThread(vim.eval('a:querystr'))
+
+ call <SID>NM_set_map('n', g:nm_vimpy_show_maps)
+ setlocal fillchars=
+ setlocal foldtext=NM_show_foldtext()
+ setlocal foldcolumn=6
+ setlocal foldmethod=syntax
+ setlocal nomodifiable
+ setlocal nowrap
+ call <SID>NM_jump_message(1)
+endfunction
+
+function! s:NM_jump_message(offset)
+ "TODO implement can_change_thread and find_matching, nicer positioning
+ exec printf('python nm_vim.vim_get_object(%d, %d)', line('.'), a:offset)
+ if exists('obj')
+ silent! norm zc
+ exec printf('norm %dGzt', obj['start'])
+ silent! norm zo
+ endif
+endfunction
+
+function! s:NM_show_next_thread()
+ call <SID>NM_kill_this_buffer()
+ if line('.') != line('$')
+ norm j
+ call <SID>NM_search_show_thread()
+ else
+ echo 'No more messages.'
+ endif
+endfunction
+
+function! s:NM_show_archive_thread()
+ call <SID>NM_tag('', ['-inbox'])
+ call <SID>NM_show_next_thread()
+endfunction
+
+function! s:NM_show_mark_read_then_archive_thread()
+ call <SID>NM_tag('', ['-unread', '-inbox'])
+ call <SID>NM_show_next_thread()
+endfunction
+
+function! s:NM_show_mark_read_then_next_open_message()
+ echo 'not implemented'
+endfunction
+
+function! s:NM_show_previous_message()
+ echo 'not implemented'
+endfunction
+
+"XXX pythonise
+function! s:NM_show_reply()
+ let cmd = ['reply']
+ call add(cmd, 'id:' . <SID>NM_show_message_id())
+
+ let data = <SID>NM_run(cmd)
+ let lines = split(data, "\n")
+ call <SID>NM_newComposeBuffer(lines, 0)
+endfunction
+
+function! s:NM_show_view_all_mime_parts()
+ echo 'not implemented'
+endfunction
+
+"Show the raw message for current line in a new buffer
+function! s:NM_show_view_raw_message()
+ exec printf('python nm_vim.vim_get_object(%d, 0)', line('.'))
+ if !exists('obj')
+ return
+ endif
+
+ call <SID>NM_create_buffer('rawmessage')
+ python nm_vim.RawMessage(vim.eval("obj['id']"))
+ call <SID>NM_set_map('n', g:nm_vimpy_raw_message_nmaps)
+endfunction
+
+function! s:NM_show_add_tag()
+ echo 'not implemented'
+endfunction
+
+function! s:NM_show_remove_tag()
+ echo 'not implemented'
+endfunction
+
+function! s:NM_show_advance()
+ let advance_tags = ['-unread']
+
+ exec printf('python nm_vim.vim_get_object(%d, 0)', line('.'))
+ if !exists('obj')
+ return
+ endif
+
+ call <SID>NM_tag(['id:' . obj['id']], advance_tags)
+ if obj['end'] == line('$')
+ call <SID>NM_kill_this_buffer()
+ else
+ call <SID>NM_jump_message(1)
+ endif
+endfunction
+
+function! s:NM_show_pipe_message()
+ echo 'not implemented'
+endfunction
+
+function! s:NM_show_view_attachment()
+ exec printf('python nm_vim.vim_view_attachment(%d)', line('.'))
+endfunction
+
+function! s:NM_show_save_attachment()
+ let filename = input('Where to save the attachment: ', './', 'file')
+ if len(filename)
+ try
+ python nm_vim.vim_save_attachment(int(vim.eval('line(".")')), vim.eval('filename'))
+ echo 'Attachment saved successfully.'
+ endtry
+ endif
+endfunction
+
+" --- --- show screen helper functions {{{2
+
+function! s:NM_show_message_id()
+ exec printf('python nm_vim.vim_get_object(%d, 0)', line('.'))
+ if exists('obj')
+ return obj['id']
+ else
+ return ''
+endfunction
+
+" --- implement compose screen {{{1
+
+function! s:NM_cmd_compose(words, body_lines)
+ let lines = []
+ let start_on_line = 0
+
+ let hdrs = { }
+
+ if !has_key(hdrs, 'From') || !len(hdrs['From'])
+ let me = <SID>NM_compose_get_user_email()
+ let hdrs['From'] = [ me ]
+ endif
+
+ for key in g:nm_vimpy_compose_headers
+ let text = has_key(hdrs, key) ? join(hdrs[key], ', ') : ''
+ call add(lines, key . ': ' . text)
+ if !start_on_line && !strlen(text)
+ let start_on_line = len(lines)
+ endif
+ endfor
+
+ for [key,val] in items(hdrs)
+ if match(g:nm_vimpy_compose_headers, key) == -1
+ let line = key . ': ' . join(val, ', ')
+ call add(lines, line)
+ endif
+ endfor
+
+ call add(lines, '')
+ if !start_on_line
+ let start_on_line = len(lines) + 1
+ endif
+
+ call extend(lines, [ '', '' ])
+
+ call <SID>NM_newComposeBuffer(lines, start_on_line)
+endfunction
+
+function! s:NM_compose_send()
+ let fname = expand('%')
+
+ try
+ python nm_vim.get_current_buffer().send()
+ call <SID>NM_kill_this_buffer()
+
+ call delete(fname)
+ echo 'Mail sent successfully.'
+ endtry
+endfunction
+
+function! s:NM_compose_attach()
+ let attachment = input('Enter attachment filename: ', '', 'file')
+ if len(attachment)
+ python nm_vim.get_current_buffer().attach(vim.eval('attachment'))
+ endif
+endfunction
+
+function! s:NM_compose_next_entry_area()
+ let lnum = line('.')
+ let hdr_end = <SID>NM_compose_find_line_match(1,'^$',1)
+ if lnum < hdr_end
+ let lnum = lnum + 1
+ let line = getline(lnum)
+ if match(line, '^\([^:]\+\):\s*$') == -1
+ call cursor(lnum, strlen(line) + 1)
+ return ''
+ endif
+ while match(getline(lnum+1), '^\s') != -1
+ let lnum = lnum + 1
+ endwhile
+ call cursor(lnum, strlen(getline(lnum)) + 1)
+ return ''
+
+ elseif lnum == hdr_end
+ call cursor(lnum+1, strlen(getline(lnum+1)) + 1)
+ return ''
+ endif
+ if mode() == 'i'
+ if !getbufvar(bufnr('.'), '&et')
+ return "\t"
+ endif
+ let space = ''
+ let shiftwidth = a:shiftwidth
+ let shiftwidth = shiftwidth - ((virtcol('.')-1) % shiftwidth)
+ " we assume no one has shiftwidth set to more than 40 :)
+ return ' '[0:shiftwidth]
+ endif
+endfunction
+
+" --- --- compose screen helper functions {{{2
+
+function! s:NM_compose_get_user_email()
+ " TODO: do this properly (still), i.e., allow for multiple email accounts
+ let email = substitute(system('notmuch config get user.primary_email'), '\v(^\s*|\s*$|\n)', '', 'g')
+ return email
+endfunction
+
+function! s:NM_compose_find_line_match(start, pattern, failure)
+ let lnum = a:start
+ let lend = line('$')
+ while lnum < lend
+ if match(getline(lnum), a:pattern) != -1
+ return lnum
+ endif
+ let lnum = lnum + 1
+ endwhile
+ return a:failure
+endfunction
+
+
+" --- notmuch helper functions {{{1
+function! s:NM_create_buffer(type)
+ let prev_bufnr = bufnr('%')
+
+ enew
+ setlocal buftype=nofile
+ setlocal bufhidden=hide
+ execute printf('set filetype=nm_vimpy-%s', a:type)
+ execute printf('set syntax=nm_vimpy-%s', a:type)
+ "XXX this should probably go
+ let b:nm_prev_bufnr = prev_bufnr
+endfunction
+
+"set some options for "menu"-like buffers -- folders/searches
+function! s:NM_finalize_menu_buffer()
+ setlocal nomodifiable
+ setlocal cursorline
+ setlocal nowrap
+endfunction
+
+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
+ setlocal nomodifiable
+ execute printf('set filetype=notmuch-%s', a:type)
+ execute printf('set syntax=notmuch-%s', a:type)
+endfunction
+
+function! s:NM_newFileBuffer(fdir, fname, type, lines)
+ let fdir = expand(a:fdir)
+ if !isdirectory(fdir)
+ call mkdir(fdir, 'p')
+ endif
+ let file_name = <SID>NM_mktemp(fdir, a:fname)
+ if writefile(a:lines, file_name)
+ throw 'Eeek! couldn''t write to temporary file ' . file_name
+ endif
+ exec printf('edit %s', file_name)
+ setlocal buftype= noreadonly modifiable scrolloff=0 sidescrolloff=0
+ execute printf('set filetype=notmuch-%s', a:type)
+ execute printf('set syntax=notmuch-%s', 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: <Tab> - 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
+ if exists('g:nm_vimpy_signature')
+ call extend(lines, ['', '-- '])
+ call extend(lines, g:nm_vimpy_signature)
+ endif
+
+
+ let prev_bufnr = bufnr('%')
+ call <SID>NM_newFileBuffer(g:notmuch_compose_temp_file_dir, '%s.mail',
+ \ 'compose', lines)
+ let b:nm_prev_bufnr = prev_bufnr
+
+ call <SID>NM_set_map('n', g:nm_vimpy_compose_nmaps)
+
+ 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 <SID>NM_compose_next_entry_area()
+ endif
+
+ if g:notmuch_compose_insert_mode_start
+ startinsert!
+ endif
+
+ python nm_vim.Compose()
+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)
+ " TODO: check if it exists, try again
+ return file_name
+endfunction
+
+function! s:NM_shell_escape(word)
+ " TODO: use shellescape()
+ let word = substitute(a:word, '''', '\\''', 'g')
+ return '''' . word . ''''
+endfunction
+
+function! s:NM_run(args)
+ let words = a:args
+ call map(words, 's:NM_shell_escape(v:val)')
+ let cmd = g:notmuch_cmd . ' ' . join(words) . '< /dev/null'
+
+ let out = system(cmd)
+ let err = v:shell_error
+
+ if err
+ echohl Error
+ echo substitute(out, '\n*$', '', '')
+ echohl None
+ return ''
+ else
+ return out
+ endif
+endfunction
+
+" --- external mail handling helpers {{{1
+
+function! s:NM_new_mail()
+ call <SID>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_kill_this_buffer()
+ let prev_bufnr = b:nm_prev_bufnr
+ python nm_vim.delete_current_buffer()
+ bdelete!
+ exec printf("buffer %d", prev_bufnr)
+endfunction
+
+function! s:NM_search_expand(arg)
+ let word = expand(a:arg)
+ let prev_bufnr = bufnr('%')
+ call <SID>NM_cmd_search(word, 0)
+ let b:nm_prev_bufnr = prev_bufnr
+endfunction
+
+function! s:NM_tag(filter, tags)
+ let filter = len(a:filter) ? a:filter : [<SID>NM_search_thread_id()]
+ if !len(filter)
+ throw 'Eeek! I couldn''t find the thead id!'
+ endif
+ python nm_vim.get_current_buffer().tag(tags = vim.eval("a:tags"), querystr = vim.eval('join(filter)'))
+endfunction
+
+" --- process and set the defaults {{{1
+
+function! s:NM_set_defaults(force)
+ setlocal bufhidden=hide
+ for [key, dflt] in items(s:nm_vimpy_defaults)
+ let cmd = ''
+ if !a:force && exists(key) && type(dflt) == type(eval(key))
+ continue
+ elseif type(dflt) == type(0)
+ let cmd = printf('let %s = %d', key, dflt)
+ elseif type(dflt) == type('')
+ let cmd = printf('let %s = ''%s''', key, dflt)
+ " FIXME: not sure why this didn't work when dflt is an array
+ "elseif type(dflt) == type([])
+ " let cmd = printf('let %s = %s', key, string(dflt))
+ else
+ echoe printf('E: Unknown type in NM_set_defaults(%d) using [%s,%s]',
+ \ a:force, key, string(dflt))
+ continue
+ endif
+ exec cmd
+ endfor
+endfunction
+call <SID>NM_set_defaults(0)
+
+" for some reason NM_set_defaults() didn't work for arrays...
+if !exists('g:nm_vimpy_folders')
+ let g:nm_vimpy_folders = s:nm_vimpy_folders_defaults
+endif
+
+if !exists('g:nm_vimpy_show_headers')
+ let g:nm_vimpy_show_headers = s:nm_vimpy_show_headers_defaults
+endif
+
+if !exists('g:nm_vimpy_signature')
+ if filereadable(glob('~/.signature'))
+ let g:nm_vimpy_signature = readfile(glob('~/.signature'))
+ endif
+endif
+if !exists('g:nm_vimpy_compose_headers')
+ let g:nm_vimpy_compose_headers = s:nm_vimpy_compose_headers_defaults
+endif
+
+" --- assign keymaps {{{1
+
+function! s:NM_set_map(type, maps)
+ for [key, code] in items(a:maps)
+ exec printf('%snoremap <buffer> %s %s', a:type, key, code)
+ endfor
+endfunction
+
+" --- command handler {{{1
+
+function! NMVimpy()
+ call <SID>NM_cmd_folders(g:nm_vimpy_folders)
+endfunction
+
+"Custom foldtext() for show buffers, which indents folds to
+"represent thread structure
+function! NM_show_foldtext()
+ if v:foldlevel != 1
+ return foldtext()
+ endif
+ let indentlevel = matchstr(getline(v:foldstart), '^[0-9]\+')
+ return repeat(' ', indentlevel) . getline(v:foldstart + 1)
+endfunction
+
+"Completion of search prompt
+function! NM_search_type_completion(arg_lead, cmd_line, cursor_pos)
+ let keywords = 'from:' . "\n" .
+ \ 'to:' . "\n" .
+ \ 'subject:' . "\n" .
+ \ 'attachment:' . "\n" .
+ \ 'tag:' . "\n" .
+ \ 'id:' . "\n" .
+ \ 'thread:' . "\n" .
+ \ 'folder:' . "\n" .
+ \ 'and' . "\n" .
+ \ 'or'
+ let s_idx = strridx(a:arg_lead, " ")
+ let col_idx = stridx(a:arg_lead, ":", s_idx)
+ let prefix = strpart(a:arg_lead, 0, s_idx + 1)
+ if col_idx < 0
+ return prefix . substitute(keywords, "\n", "\n" . prefix, "g")
+ endif
+ if stridx(a:arg_lead, 'tag:', s_idx) >= 0
+ python nm_vim.vim_get_tags()
+ return prefix . 'tag:' . substitute(taglist, "\n", "\n" . prefix . 'tag:', "g")
+ endif
+ return ''
+endfunction
+
+function! NM_tag_name_completion(arg_lead, cmd_line, cursor_pos)
+ let s_idx = strridx(a:arg_lead, " ")
+ let prefix = strpart(a:arg_lead, 0, s_idx + 1)
+ python nm_vim.vim_get_tags()
+ return prefix . substitute(taglist, "\n", "\n" . prefix, "g")
+endfunction
+
+let s:notmuch_loaded = 1