[RFC/PATCH] Vim client rewrite
anton at khirnov.net
anton at khirnov.net
Sun May 15 12:15:31 PDT 2011
Hi,
my attempts to make the vim client more usable somehow spiraled out of
control and turned into a huge rewrite. The intermediate results I
hereby present for your amusement and comments.
(attached as whole files, since the patch would be unreadable)
The main point of the rewrite is splitting of a large part of the code
into Python. This should have the following advantages:
1) python-notmuch bindings can be used, which should allow for cleaner
and more reliable code than running the binary and parsing its output
with regexps.
(also provides a nice use case for python-notmuch)
2) Python's huge standard library makes implementing some features MUCH easier.
3) More people know Python than vimscript, thus making the client
development easier
The code is α quality, but should be close to usable.
It already has some features not present in the mainline vim client,
like attachments (viewing and sending, saving to file should be trivial
to add, will be done when I have some time), better support for unicode
and more.
Some UI features from the mainline versions that I didn't use were
removed and customization options are somewhat lacking atm. This is of
course to be improved later, depending on the responses.
Comments, bugreports and fixes very much welcome.
--
Anton Khirnov
-------------- next part --------------
" notmuch.vim plugin --- run notmuch within vim
"
" Copyright © Carl Worth
"
" 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/>.
"
" Authors: Bart Trojanowski <bart at jukie.net>
" Contributors: Felipe Contreras <felipe.contreras at gmail.com>,
" Peter Hartman <peterjohnhartman at gmail.com>
"
if exists('s:notmuch_loaded') || &cp
finish
endif
let s:notmuch_loaded = 1
" --- configuration defaults {{{1
let s:notmuch_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:notmuch_fcc_maildir': 'sent' ,
\ }
" defaults for g:notmuch_folders
" override with: let g:notmuch_folders = [ ... ]
let s:notmuch_folders_defaults = [
\ [ 'new', 'tag:inbox and tag:unread' ],
\ [ 'inbox', 'tag:inbox' ],
\ [ 'unread', 'tag:unread' ],
\ ]
let s:notmuch_show_headers_defaults = [
\ 'From',
\ 'To',
\ 'Cc',
\ 'Subject',
\ 'Date',
\ 'Reply-To',
\ 'Message-Id',
\]
" defaults for g:notmuch_compose_headers
" override with: let g:notmuch_compose_headers = [ ... ]
let s:notmuch_compose_headers_defaults = [
\ 'From',
\ 'To',
\ 'Cc',
\ 'Bcc',
\ 'Subject'
\ ]
" --- keyboard mapping definitions {{{1
" --- --- bindings for folders mode {{{2
let g:notmuch_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:notmuch_search_maps = {
\ '<Space>': ':call <SID>NM_search_show_thread()<CR>',
\ '<Enter>': ':call <SID>NM_search_show_thread()<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:notmuch_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>',
\
\ '<S-Tab>': ':call <SID>NM_show_previous_fold()<CR>',
\ '<Tab>': ':call <SID>NM_show_next_fold()<CR>',
\ '<Enter>': ':call <SID>NM_show_view_attachment()<CR>',
\
\ 'r': ':call <SID>NM_show_reply()<CR>',
\ 'm': ':call <SID>NM_new_mail()<CR>',
\ }
" --- --- bindings for compose screen {{{2
let g:notmuch_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:notmuch_compose_imaps = {
\ '<Tab>': '<C-r>=<SID>NM_compose_next_entry_area()<CR>',
\ }
" --- implement folders screen {{{1
" Create the folders buffer.
" Takes a list of [ folder name, query string]
" TODO decorate (help on the first line?)
function! s:NM_cmd_folders(folders)
call <SID>NM_create_buffer('folders')
silent 0put!=' Notmuch plugin.'
python nm_vim.SavedSearches(vim.eval("a:folders"))
call <SID>NM_finalize_menu_buffer()
call <SID>NM_set_map('n', g:notmuch_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')
if a:relative
exec printf('python nm_vim.Search(querystr = "%s", parent = nm_vim.nm_buffers["%d"])', a:querystr, cur_buf)
else
exec printf('python nm_vim.Search(querystr = "%s")', a:querystr)
endif
call <SID>NM_finalize_menu_buffer()
call <SID>NM_set_map('n', g:notmuch_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
" 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,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,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 ggdG
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
" TODO: input() can support completion
let text = input(a:prompt)
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')
exec printf('python nm_vim.ShowThread("%s")', a:querystr)
call <SID>NM_set_map('n', g:notmuch_show_maps)
setlocal fillchars=
setlocal foldtext=NM_show_foldtext()
setlocal foldcolumn=6
setlocal foldmethod=syntax
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
function! s:NM_show_view_raw_message()
echo 'not implemented'
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
" --- --- 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:notmuch_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:notmuch_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)
exec printf('python nm_vim.get_current_buffer().attach("%s")', 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
execute printf('set filetype=notmuch-%s', a:type)
execute printf('set syntax=notmuch-%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:notmuch_signature')
call extend(lines, ['', '--'])
call extend(lines, g:notmuch_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:notmuch_compose_nmaps)
call <SID>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 <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
exec printf('python nm_vim.get_current_buffer().tag(tags = vim.eval("a:tags"), querystr = "%s")', join(filter))
endfunction
" --- process and set the defaults {{{1
function! NM_set_defaults(force)
setlocal bufhidden=hide
for [key, dflt] in items(s:notmuch_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 NM_set_defaults(0)
" for some reason NM_set_defaults() didn't work for arrays...
if !exists('g:notmuch_folders')
let g:notmuch_folders = s:notmuch_folders_defaults
endif
if !exists('g:notmuch_show_headers')
let g:notmuch_show_headers = s:notmuch_show_headers_defaults
endif
if !exists('g:notmuch_signature')
if filereadable(glob('~/.signature'))
let g:notmuch_signature = readfile(glob('~/.signature'))
endif
endif
if !exists('g:notmuch_compose_headers')
let g:notmuch_compose_headers = s:notmuch_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! NotMuch()
if !exists('s:notmuch_inited')
" init the python layer
python import sys
exec "python sys.path += [r'" . s:python_path . "']"
python import vim, nm_vim
let s:notmuch_inited = 1
endif
call <SID>NM_cmd_folders(g:notmuch_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 numlines = v:foldend - v:foldstart + 1
let indentlevel = matchstr(getline(v:foldstart), '^[0-9]\+')
return repeat(' ', indentlevel) . getline(v:foldstart + 1)
endfunction
"Completion of search prompt
"TODO properly deal with complex queries
function! Search_type_completion(arg_lead, cmd_line, cursor_pos)
let idx = stridx(a:arg_lead, ':')
if idx < 0
return 'from:' . "\n" .
\ 'to:' . "\n" .
\ 'subject:' . "\n" .
\ 'attachment:' . "\n" .
\ 'tag:' . "\n" .
\ 'id:' . "\n" .
\ 'thread:' . "\n" .
\ 'folder:'
endif
if stridx(a:arg_lead, 'tag:') >= 0
python nm_vim.vim_get_tags()
return 'tag:' . substitute(taglist, "\n", "\ntag:", "g")
endif
return ''
endfunction
" --- glue {{{1
command! NotMuch call NotMuch()
let s:python_path = expand('<sfile>:p:h')
-------------- next part --------------
A non-text attachment was scrubbed...
Name: nm_vim.py
Type: text/x-python
Size: 19445 bytes
Desc: not available
URL: <http://notmuchmail.org/pipermail/notmuch/attachments/20110515/6e9c6542/attachment-0001.py>
-------------- next part --------------
" notmuch folders mode syntax file
syntax region nmFolfers start=/^/ end=/$/ oneline contains=nmFoldersMessageCount
syntax match nmFoldersMessageCount /^ *[0-9]\+ */ contained nextgroup=nmFoldersUnreadCount
syntax match nmFoldersUnreadCount /(.\{-}) */ contained nextgroup=nmFoldersName
syntax match nmFoldersName /.*\ze(/ contained nextgroup=nmFoldersSearch
syntax match nmFoldersSearch /([^()]\+)$/
highlight link nmFoldersMessageCount Statement
highlight link nmFoldersUnreadCount Underlined
highlight link nmFoldersName Type
highlight link nmFoldersSearch String
highlight CursorLine term=reverse cterm=reverse gui=reverse
-------------- next part --------------
syntax region nmSearch start=/^/ end=/$/ oneline contains=nmSearchDate keepend
syntax match nmSearchDate /^.\{-13}/ contained nextgroup=nmSearchNum skipwhite
syntax match nmSearchNum "[0-9]\+\/" contained nextgroup=nmSearchTotal skipwhite
syntax match nmSearchTotal /[0-9]\+/ contained nextgroup=nmSearchFrom skipwhite
syntax match nmSearchFrom /.\{-}\ze|/ contained nextgroup=nmSearchSubject skipwhite
"XXX this fails on some messages with multiple authors
syntax match nmSearchSubject /.*\ze(/ contained nextgroup=nmSearchTags
syntax match nmSearchTags /.\+$/ contained
syntax match nmUnread /^.*(.*\<unread\>.*)$/
highlight link nmSearchDate Statement
highlight link nmSearchNum Number
highlight link nmSearchTotal Type
highlight link nmSearchFrom Include
highlight link nmSearchSubject Normal
highlight link nmSearchTags String
highlight link nmUnread Underlined
-------------- next part --------------
" notmuch show mode syntax file
setlocal conceallevel=2
setlocal concealcursor=vinc
syntax region nmMessage matchgroup=Ignore concealends start='[0-9]\+\/-*message start-*\\' end='\\-*message end-*\/' fold contains=@nmShowMsgBody keepend
"TODO what about those
syntax cluster nmShowMsgDesc contains=nmShowMsgDescWho,nmShowMsgDescDate,nmShowMsgDescTags
syntax match nmShowMsgDescWho /[^)]\+)/ contained
syntax match nmShowMsgDescDate / ([^)]\+[0-9]) / contained
syntax match nmShowMsgDescTags /([^)]\+)$/ contained
syntax cluster nmShowMsgBody contains=@nmShowMsgBodyMail, at nmShowMsgBodyGit
syntax include @nmShowMsgBodyMail syntax/mail.vim
silent! syntax include @nmShowMsgBodyGit syntax/notmuch-git-diff.vim
highlight nmShowMsgDescWho term=reverse cterm=reverse gui=reverse
highlight link nmShowMsgDescDate Type
highlight link nmShowMsgDescTags String
"TODO what about this?
highlight Folded term=reverse ctermfg=LightGrey ctermbg=Black guifg=LightGray guibg=Black
More information about the notmuch
mailing list