vmail 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/NOTES +17 -13
- data/Rakefile +2 -4
- data/lib/vmail.rb +14 -10
- data/lib/vmail.vim +107 -91
- data/lib/vmail/imap_client.rb +30 -22
- data/lib/vmail/message_formatter.rb +91 -89
- data/lib/vmail/string_ext.rb +18 -0
- data/lib/vmail/version.rb +1 -1
- data/test/message_formatter_test.rb +2 -1
- data/test/test_helper.rb +0 -2
- metadata +3 -6
- data/config/environment.rb +0 -18
- data/config/gmail.orig.yml +0 -8
- data/lib/gmail.rb +0 -145
data/NOTES
CHANGED
@@ -467,6 +467,14 @@ DONE
|
|
467
467
|
window. redirect streams from daemon to a log file
|
468
468
|
- package gem
|
469
469
|
- vmail (client)
|
470
|
+
- mvim = allow using through VMAIL_EDITOR env variable
|
471
|
+
- need to put mailbox and search on status line from startup
|
472
|
+
- c-j c-k to go to prev/next message
|
473
|
+
- turn search into an input() style prompt
|
474
|
+
- remember and show last search
|
475
|
+
- map u update from message window
|
476
|
+
- do search after loading vim or mvim and send size first
|
477
|
+
- vim resize event
|
470
478
|
|
471
479
|
next:
|
472
480
|
- sending an attachment
|
@@ -474,11 +482,9 @@ next:
|
|
474
482
|
attached and sent
|
475
483
|
- forwarding attachments
|
476
484
|
- could be "attach: [uid] attachments to signal"
|
477
|
-
- use :wq to send compose message, not ,d (easy to confuse with delete!)
|
478
|
-
- close! to cancel
|
479
|
-
- fewer mappings to remember this way
|
480
485
|
- put instructions in message window; put in a top section
|
481
|
-
-
|
486
|
+
- help /readme
|
487
|
+
- help document should just be readme? or part of it.
|
482
488
|
- follow mysql and use -u and -p flags on startup of server?
|
483
489
|
- omitting -p flag forces prompt
|
484
490
|
- get rid of config file
|
@@ -486,21 +492,18 @@ next:
|
|
486
492
|
- tempname()
|
487
493
|
- system() allows a parameters that is written to tmp file and passed
|
488
494
|
to stdin
|
489
|
-
- help document should just be readme? or part of it.
|
490
495
|
- print all selected messages into file (append)
|
491
496
|
- can pipe through a command line tool, so we don't need to clutter
|
492
497
|
viewer.vim with more mappings and functions
|
493
|
-
- put "move to >" and "select mailbox >" in command window
|
494
|
-
- make compat with ruby 1.8
|
495
|
-
- help
|
496
|
-
- readme
|
497
498
|
- ,s s confusion? Star vs search
|
498
|
-
-
|
499
|
-
|
500
|
-
|
501
|
-
- mvim
|
499
|
+
- enhance contacts auto fix with more advanced vim script
|
500
|
+
- sort contacts by frequency, then take first 10 or so of any match
|
501
|
+
- mvim - window width not correct
|
502
|
+
- mvim - starred messages not syntax colored
|
502
503
|
|
503
504
|
later:
|
505
|
+
- mvim redrawstatus line bug
|
506
|
+
http://vim.1045645.n5.nabble.com/Redrawing-bug-in-MacVim-Command-T-since-commit-ba44868-td3248742.html
|
504
507
|
- allow one daemon, multiple clients (select mailbox?)
|
505
508
|
- remember searches?
|
506
509
|
- archive function (shortcut for move to all mail)
|
@@ -518,6 +521,7 @@ later:
|
|
518
521
|
- do fast action like deletes
|
519
522
|
- reload after window resize
|
520
523
|
- sometimes update doesn't work - bug
|
524
|
+
- show total messages from a search, showing 100
|
521
525
|
- message threads
|
522
526
|
|
523
527
|
|
data/Rakefile
CHANGED
@@ -3,12 +3,10 @@ require 'rake/testtask'
|
|
3
3
|
require 'bundler'
|
4
4
|
Bundler::GemHelper.install_tasks
|
5
5
|
|
6
|
-
|
7
|
-
require(File.join(File.dirname(__FILE__), 'config', 'environment'))
|
8
|
-
end
|
6
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), 'lib')
|
9
7
|
|
10
8
|
desc "Run tests"
|
11
|
-
task :test
|
9
|
+
task :test do
|
12
10
|
$:.unshift File.expand_path("test")
|
13
11
|
require 'test_helper'
|
14
12
|
require 'time_format_test'
|
data/lib/vmail.rb
CHANGED
@@ -4,33 +4,37 @@ module Vmail
|
|
4
4
|
extend self
|
5
5
|
|
6
6
|
def start
|
7
|
+
vim = ENV['VMAIL_VIM'] || 'vim'
|
8
|
+
|
7
9
|
config = YAML::load(File.read(File.expand_path("~/gmail.yml")))
|
8
|
-
|
10
|
+
logfile = (vim == 'mvim') ? STDERR : 'vmail.log'
|
11
|
+
config.merge! 'logfile' => logfile
|
9
12
|
|
10
13
|
puts "starting vmail imap client with config #{config}"
|
11
14
|
|
12
15
|
drb_uri = Vmail::ImapClient.daemon config
|
13
16
|
|
14
17
|
server = DRbObject.new_with_uri drb_uri
|
18
|
+
|
19
|
+
# TODO this is useless if we're using mvim
|
15
20
|
server.window_width = `stty size`.strip.split(' ')[1]
|
16
|
-
server.select_mailbox ARGV.shift || 'INBOX'
|
17
21
|
|
18
|
-
|
22
|
+
mailbox = ARGV.shift || 'INBOX'
|
23
|
+
server.select_mailbox mailbox
|
19
24
|
|
25
|
+
query = ARGV.empty? ? [100, 'ALL'] : ARGV
|
26
|
+
puts "mailbox: #{mailbox}"
|
27
|
+
puts "query: #{query.inspect}"
|
20
28
|
|
21
|
-
buffer_file = "
|
29
|
+
buffer_file = "vmailbuffer.txt"
|
22
30
|
puts "using buffer file: #{buffer_file}"
|
23
31
|
File.open(buffer_file, "w") do |file|
|
24
|
-
file.puts
|
32
|
+
file.puts "just a moment..."
|
25
33
|
end
|
26
34
|
|
27
35
|
# invoke vim
|
28
|
-
# TODO
|
29
|
-
# - mvim; move viewer.vim to new file
|
30
|
-
|
31
|
-
vim = ENV['VMAIL_VIM'] || 'vim'
|
32
36
|
vimscript = File.expand_path("../vmail.vim", __FILE__)
|
33
|
-
vim_command = "DRB_URI='#{drb_uri}' #{vim} -S #{vimscript} #{buffer_file}"
|
37
|
+
vim_command = "DRB_URI='#{drb_uri}' VMAIL_MAILBOX=#{String.shellescape(mailbox)} VMAIL_QUERY=#{String.shellescape(query.join(' '))} #{vim} -S #{vimscript} #{buffer_file}"
|
34
38
|
puts vim_command
|
35
39
|
system(vim_command)
|
36
40
|
|
data/lib/vmail.vim
CHANGED
@@ -1,13 +1,12 @@
|
|
1
|
-
let s:mailbox =
|
2
|
-
let s:
|
3
|
-
let s:query = ''
|
1
|
+
let s:mailbox = $VMAIL_MAILBOX
|
2
|
+
let s:query = $VMAIL_QUERY
|
4
3
|
|
5
4
|
let s:drb_uri = $DRB_URI
|
6
5
|
|
7
6
|
let s:client_script = "vmail_client " . s:drb_uri . " "
|
8
|
-
let s:
|
7
|
+
let s:set_window_width_command = s:client_script . "window_width= "
|
9
8
|
let s:list_mailboxes_command = s:client_script . "list_mailboxes "
|
10
|
-
let s:
|
9
|
+
let s:show_message_command = s:client_script . "show_message "
|
11
10
|
let s:update_command = s:client_script . "update"
|
12
11
|
let s:fetch_headers_command = s:client_script . "fetch_headers "
|
13
12
|
let s:select_mailbox_command = s:client_script . "select_mailbox "
|
@@ -26,7 +25,7 @@ let s:message_bufname = "MessageWindow"
|
|
26
25
|
let s:list_bufname = "MessageListWindow"
|
27
26
|
|
28
27
|
function! VmailStatusLine()
|
29
|
-
return "%<%f\ " . s:mailbox . "%r%=%-14.(%l,%c%V%)\ %P"
|
28
|
+
return "%<%f\ " . s:mailbox . " " . s:query . "%r%=%-14.(%l,%c%V%)\ %P"
|
30
29
|
endfunction
|
31
30
|
|
32
31
|
function! s:create_list_window()
|
@@ -47,6 +46,7 @@ function! s:create_list_window()
|
|
47
46
|
" we need the bufnr to find the window later
|
48
47
|
let s:listbufnr = bufnr('%')
|
49
48
|
setlocal statusline=%!VmailStatusLine()
|
49
|
+
call s:message_list_window_mappings()
|
50
50
|
endfunction
|
51
51
|
|
52
52
|
" the message display buffer window
|
@@ -56,28 +56,7 @@ function! s:create_message_window()
|
|
56
56
|
" setlocal noswapfile
|
57
57
|
" setlocal nobuflisted
|
58
58
|
let s:message_window_bufnr = bufnr('%')
|
59
|
-
|
60
|
-
noremap <silent> <buffer> <cr> :call <SID>focus_list_window()<CR>
|
61
|
-
noremap <silent> <buffer> <Leader>q :call <SID>focus_list_window()<CR>
|
62
|
-
noremap <silent> <buffer> q <Leader>q
|
63
|
-
noremap <silent> <buffer> <Leader>r :call <SID>compose_reply(0)<CR>
|
64
|
-
noremap <silent> <buffer> <Leader>a :call <SID>compose_reply(1)<CR>
|
65
|
-
noremap <silent> <buffer> <Leader>R :call <SID>show_raw()<cr>
|
66
|
-
noremap <silent> <buffer> <Leader>R :call <SID>show_raw()<cr>
|
67
|
-
noremap <silent> <buffer> <Leader>f :call <SID>compose_forward()<CR><cr>
|
68
|
-
" TODO improve this
|
69
|
-
noremap <silent> <buffer> <Leader>o yE :!open '<C-R>"'<CR><CR>
|
70
|
-
noremap <silent> <buffer> <leader>j :call <SID>show_next_message()<CR>
|
71
|
-
noremap <silent> <buffer> <leader>k :call <SID>show_previous_message()<CR>
|
72
|
-
noremap <silent> <buffer> <Leader>c :call <SID>compose_message()<CR>
|
73
|
-
noremap <silent> <buffer> <Leader>h :call <SID>open_html_part()<CR><cr>
|
74
|
-
nnoremap <silent> <buffer> q :close<cr>
|
75
|
-
nnoremap <silent> <buffer> <leader>d :call <SID>focus_list_window()<cr>:call <SID>delete_messages("Deleted")<cr>
|
76
|
-
nnoremap <silent> <buffer> s :call <SID>focus_list_window()<cr>:call <SID>toggle_star()<cr>
|
77
|
-
nnoremap <silent> <buffer> <Leader>m :call <SID>focus_list_window()<cr>:call <SID>mailbox_window()<CR>
|
78
|
-
nnoremap <silent> <buffer> <Leader>A :call <SID>save_attachments()<cr>
|
79
|
-
" go fullscreen
|
80
|
-
nnoremap <silent> <buffer> <Space> :call <SID>toggle_fullscreen()<cr>
|
59
|
+
call s:message_window_mappings()
|
81
60
|
close
|
82
61
|
endfunction
|
83
62
|
|
@@ -101,8 +80,9 @@ function! s:show_message()
|
|
101
80
|
redraw
|
102
81
|
let selected_uid = matchstr(line, '^\d\+')
|
103
82
|
let s:current_uid = selected_uid
|
104
|
-
let command = s:
|
105
|
-
|
83
|
+
let command = s:show_message_command . s:current_uid
|
84
|
+
echom "Loading message. Please wait..."
|
85
|
+
redrawstatus
|
106
86
|
let res = system(command)
|
107
87
|
call s:focus_message_window()
|
108
88
|
setlocal modifiable
|
@@ -133,7 +113,7 @@ endfunction
|
|
133
113
|
|
134
114
|
" invoked from withint message window
|
135
115
|
function! s:show_raw()
|
136
|
-
let command = s:
|
116
|
+
let command = s:show_message_command . s:current_uid . ' raw'
|
137
117
|
echo command
|
138
118
|
setlocal modifiable
|
139
119
|
1,$delete
|
@@ -178,7 +158,7 @@ endfunction
|
|
178
158
|
" gets new messages since last update
|
179
159
|
function! s:update()
|
180
160
|
let command = s:update_command
|
181
|
-
echo
|
161
|
+
echo "checking for new messages. please wait..."
|
182
162
|
let res = system(command)
|
183
163
|
if match(res, '^\d\+') != -1
|
184
164
|
setlocal modifiable
|
@@ -189,11 +169,11 @@ function! s:update()
|
|
189
169
|
redraw
|
190
170
|
call cursor(line + 1, 0)
|
191
171
|
normal z.
|
192
|
-
|
193
|
-
|
172
|
+
echom "you have " . num . " new message" . (num == 1 ? '' : 's') . "!"
|
173
|
+
redrawstatus
|
194
174
|
else
|
195
|
-
|
196
|
-
|
175
|
+
echom "no new messages"
|
176
|
+
redrawstatus
|
197
177
|
endif
|
198
178
|
endfunction
|
199
179
|
|
@@ -215,7 +195,11 @@ function! s:toggle_star() range
|
|
215
195
|
let action = " -FLAGS"
|
216
196
|
endif
|
217
197
|
let command = s:flag_command . uid_set . action . " Flagged"
|
218
|
-
|
198
|
+
if len(uids) == 1
|
199
|
+
echom "toggling flag on message " . uid_set
|
200
|
+
else
|
201
|
+
echom "toggling flags on messages " . join(uid_set, ",")
|
202
|
+
endif
|
219
203
|
" toggle [*] on lines
|
220
204
|
let res = system(command)
|
221
205
|
setlocal modifiable
|
@@ -383,7 +367,6 @@ function! s:select_mailbox()
|
|
383
367
|
endif
|
384
368
|
let s:mailbox = mailbox
|
385
369
|
let command = s:select_mailbox_command . shellescape(s:mailbox)
|
386
|
-
echo command
|
387
370
|
call system(command)
|
388
371
|
redraw
|
389
372
|
" now get latest 100 messages
|
@@ -400,19 +383,15 @@ function! s:select_mailbox()
|
|
400
383
|
normal z.
|
401
384
|
endfunction
|
402
385
|
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
call feedkeys("i")
|
411
|
-
endfunction
|
386
|
+
func! s:search_query()
|
387
|
+
if !exists("s:query")
|
388
|
+
let s:query = ""
|
389
|
+
endif
|
390
|
+
let s:query = input("search query: ", s:query)
|
391
|
+
call s:do_search()
|
392
|
+
endfunc
|
412
393
|
|
413
394
|
function! s:do_search()
|
414
|
-
let s:query = getline(line('.'))
|
415
|
-
close
|
416
395
|
" empty query
|
417
396
|
if match(s:query, '^\s*$') != -1
|
418
397
|
return
|
@@ -420,12 +399,21 @@ function! s:do_search()
|
|
420
399
|
" close message window if open
|
421
400
|
call s:focus_message_window()
|
422
401
|
close
|
423
|
-
"
|
424
|
-
let
|
425
|
-
|
402
|
+
" if query doesn't start with a number, set max returned to 100
|
403
|
+
let limit = 100
|
404
|
+
let imap_query = s:query
|
405
|
+
if match(s:query, '^\d') == 0
|
406
|
+
let query_chunks = split(s:query, '\s')
|
407
|
+
let limit = remove(query_chunks, 0)
|
408
|
+
let imap_query = join(query_chunks, ' ')
|
409
|
+
end
|
410
|
+
let s:query = limit . ' ' . imap_query
|
411
|
+
let command = s:search_command . limit . ' ' . shellescape(imap_query)
|
412
|
+
redraw
|
426
413
|
call s:focus_list_window()
|
427
|
-
let res = system(command)
|
428
414
|
setlocal modifiable
|
415
|
+
echo "running query on " . s:mailbox . ": " . s:query . ". please wait..."
|
416
|
+
let res = system(command)
|
429
417
|
1,$delete
|
430
418
|
put! =res
|
431
419
|
execute "normal Gdd\<c-y>"
|
@@ -491,10 +479,7 @@ func! s:open_compose_window(command)
|
|
491
479
|
1,$delete
|
492
480
|
put! =res
|
493
481
|
normal 1G
|
494
|
-
|
495
|
-
nnoremap <silent> <buffer> q :call <SID>cancel_compose()<cr>
|
496
|
-
nnoremap <silent> <buffer> <leader>q :call <SID>cancel_compose()<cr>
|
497
|
-
nnoremap <silent> <buffer> <Leader>s :call <SID>save_draft()<CR>
|
482
|
+
call s:compose_window_mappings()
|
498
483
|
set completefunc=CompleteContact
|
499
484
|
endfunc
|
500
485
|
|
@@ -514,7 +499,7 @@ function! CompleteContact(findstart, base)
|
|
514
499
|
return start
|
515
500
|
else
|
516
501
|
" find contacts matching with "a:base"
|
517
|
-
let matches = system("grep " . shellescape(a:base) . " contacts.txt")
|
502
|
+
let matches = system("grep -i " . shellescape(a:base) . " contacts.txt")
|
518
503
|
return split(matches, "\n")
|
519
504
|
endif
|
520
505
|
endfun
|
@@ -575,49 +560,80 @@ func! s:toggle_fullscreen()
|
|
575
560
|
endif
|
576
561
|
endfunc
|
577
562
|
|
578
|
-
call s:create_list_window()
|
579
563
|
|
580
|
-
|
581
|
-
|
582
|
-
call s:focus_list_window() " to go list window
|
583
|
-
" this are list window bindings
|
584
|
-
|
585
|
-
noremap <silent> <buffer> <cr> :call <SID>show_message()<CR>
|
586
|
-
noremap <silent> <buffer> q :qal!<cr>
|
564
|
+
" --------------------------------------------------------------------------------
|
565
|
+
" MAPPINGS
|
587
566
|
|
588
|
-
|
589
|
-
noremap <silent> <buffer> <
|
567
|
+
func! s:message_window_mappings()
|
568
|
+
noremap <silent> <buffer> <cr> :call <SID>focus_list_window()<CR>
|
569
|
+
noremap <silent> <buffer> <Leader>r :call <SID>compose_reply(0)<CR>
|
570
|
+
noremap <silent> <buffer> <Leader>a :call <SID>compose_reply(1)<CR>
|
571
|
+
noremap <silent> <buffer> <Leader>R :call <SID>show_raw()<cr>
|
572
|
+
noremap <silent> <buffer> <Leader>R :call <SID>show_raw()<cr>
|
573
|
+
noremap <silent> <buffer> <Leader>f :call <SID>compose_forward()<CR><cr>
|
574
|
+
" TODO improve this
|
575
|
+
noremap <silent> <buffer> <Leader>o yE :!open '<C-R>"'<CR><CR>
|
576
|
+
noremap <silent> <buffer> <c-j> :call <SID>show_next_message()<CR>
|
577
|
+
noremap <silent> <buffer> <c-k> :call <SID>show_previous_message()<CR>
|
578
|
+
nmap <silent> <buffer> <leader>j <c-j>
|
579
|
+
nmap <silent> <buffer> <leader>k <c-k>
|
580
|
+
noremap <silent> <buffer> <Leader>c :call <SID>compose_message()<CR>
|
581
|
+
noremap <silent> <buffer> <Leader>h :call <SID>open_html_part()<CR><cr>
|
582
|
+
nnoremap <silent> <buffer> q :close<cr>
|
583
|
+
nnoremap <silent> <buffer> <leader>d :call <SID>focus_list_window()<cr>:call <SID>delete_messages("Deleted")<cr>
|
584
|
+
nnoremap <silent> <buffer> s :call <SID>focus_list_window()<cr>:call <SID>toggle_star()<cr>
|
585
|
+
nnoremap <silent> <buffer> u :call <SID>focus_list_window()<cr>:call <SID>update()<CR>
|
586
|
+
nnoremap <silent> <buffer> <Leader>m :call <SID>focus_list_window()<cr>:call <SID>mailbox_window()<CR>
|
587
|
+
nnoremap <silent> <buffer> <Leader>A :call <SID>save_attachments()<cr>
|
588
|
+
" go fullscreen
|
589
|
+
nnoremap <silent> <buffer> <Space> :call <SID>toggle_fullscreen()<cr>
|
590
|
+
endfunc
|
590
591
|
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
592
|
+
func! s:message_list_window_mappings()
|
593
|
+
noremap <silent> <buffer> <cr> :call <SID>show_message()<CR>
|
594
|
+
noremap <silent> <buffer> q :qal!<cr>
|
595
|
+
noremap <silent> <buffer> s :call <SID>toggle_star()<CR>
|
596
|
+
noremap <silent> <buffer> <leader>d :call <SID>delete_messages("Deleted")<CR>
|
597
|
+
" TODO the range doesn't quite work as expect, need <line1> <line2>
|
598
|
+
" trying to make user defined commands that work from : prompt
|
599
|
+
" command -buffer -range VmailDelete call s:toggle_star("Deleted")
|
600
|
+
" command -buffer -range VmailStar call s:toggle_star("Flagged")
|
601
|
+
noremap <silent> <buffer> <leader>! :call <SID>delete_messages("[Gmail]/Spam")<CR>
|
602
|
+
"open a link browser (os x)
|
603
|
+
"autocmd CursorMoved <buffer> call <SID>show_message()
|
604
|
+
noremap <silent> <buffer> u :call <SID>update()<CR>
|
605
|
+
noremap <silent> <buffer> <Leader>s :call <SID>search_query()<CR>
|
606
|
+
noremap <silent> <buffer> <Leader>m :call <SID>mailbox_window()<CR>
|
607
|
+
noremap <silent> <buffer> <Leader>v :call <SID>move_to_mailbox()<CR>
|
608
|
+
noremap <silent> <buffer> <Leader>c :call <SID>compose_message()<CR>
|
609
|
+
noremap <silent> <buffer> <Leader>r :call <SID>show_message()<cr>:call <SID>compose_reply(0)<CR>
|
610
|
+
noremap <silent> <buffer> <Leader>a :call <SID>show_message()<cr>:call <SID>compose_reply(1)<CR>
|
611
|
+
" go fullscreen
|
612
|
+
nnoremap <silent> <buffer> <Space> :call <SID>toggle_fullscreen()<cr>
|
613
|
+
endfunc
|
595
614
|
|
596
|
-
|
615
|
+
func! s:compose_window_mappings()
|
616
|
+
" NOTE deliver_message is a global mapping, so user can load a saved
|
617
|
+
" message from a file and send it
|
618
|
+
nnoremap <silent> <Leader>vd :call <SID>deliver_message()<CR>
|
619
|
+
nnoremap <silent> <buffer> <Leader>vs :call <SID>save_draft()<CR>
|
620
|
+
noremap <silent> <buffer> <leader>q :call <SID>cancel_compose()<cr>
|
621
|
+
nmap <silent> <buffer> q <leader>q
|
622
|
+
endfunc
|
597
623
|
|
598
|
-
|
599
|
-
"autocmd CursorMoved <buffer> call <SID>show_message()
|
624
|
+
call s:create_list_window()
|
600
625
|
|
601
|
-
|
602
|
-
noremap <silent> <buffer> <Leader>s :call <SID>search_window()<CR>
|
603
|
-
noremap <silent> <buffer> <Leader>m :call <SID>mailbox_window()<CR>
|
604
|
-
noremap <silent> <buffer> <Leader>v :call <SID>move_to_mailbox()<CR>
|
626
|
+
call s:create_message_window()
|
605
627
|
|
606
|
-
|
628
|
+
call s:focus_list_window() " to go list window
|
607
629
|
|
608
|
-
|
609
|
-
|
630
|
+
" send window width
|
631
|
+
call system(s:set_window_width_command . winwidth(1))
|
610
632
|
|
611
|
-
|
612
|
-
nnoremap <silent> <buffer> <Space> :call <SID>toggle_fullscreen()<cr>
|
633
|
+
autocmd VimResized <buffer> call system(s:set_window_width_command . winwidth(1))
|
613
634
|
|
614
|
-
|
615
|
-
|
635
|
+
call system(s:select_mailbox_command . shellescape(s:mailbox))
|
636
|
+
call s:do_search()
|
616
637
|
|
617
|
-
" go to bottom and center cursorline
|
618
|
-
normal G
|
619
|
-
normal z.
|
620
638
|
|
621
|
-
" send window width
|
622
|
-
" system(s:set_window_width_command . winwidth(1))
|
623
639
|
|
data/lib/vmail/imap_client.rb
CHANGED
@@ -26,7 +26,8 @@ module Vmail
|
|
26
26
|
@mailbox = nil
|
27
27
|
@logger = Logger.new(config['logfile'] || STDERR)
|
28
28
|
@logger.level = Logger::DEBUG
|
29
|
-
@
|
29
|
+
@current_mail = nil
|
30
|
+
@current_uid = nil
|
30
31
|
end
|
31
32
|
|
32
33
|
def open
|
@@ -127,7 +128,7 @@ module Vmail
|
|
127
128
|
subject = Mail::Encodings.unquote_and_convert_to(subject, 'utf-8')
|
128
129
|
flags = format_flags(flags)
|
129
130
|
first_col_width = max_uid.to_s.length
|
130
|
-
mid_width = @width - (first_col_width +
|
131
|
+
mid_width = @width - (first_col_width + 33)
|
131
132
|
address_col_width = (mid_width * 0.3).ceil
|
132
133
|
subject_col_width = (mid_width * 0.7).floor
|
133
134
|
[uid.to_s.col(first_col_width),
|
@@ -154,7 +155,6 @@ module Vmail
|
|
154
155
|
end
|
155
156
|
end
|
156
157
|
|
157
|
-
|
158
158
|
FLAGMAP = {:Flagged => '[*]'}
|
159
159
|
# flags is an array like [:Flagged, :Seen]
|
160
160
|
def format_flags(flags)
|
@@ -222,22 +222,24 @@ module Vmail
|
|
222
222
|
res
|
223
223
|
end
|
224
224
|
|
225
|
-
def
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
log "fetching #{uid.inspect}"
|
225
|
+
def show_message(uid, raw=false, forwarded=false)
|
226
|
+
uid = uid.to_i
|
227
|
+
return @current_mail.to_s if raw
|
228
|
+
return @current_message if uid == @current_uid
|
229
|
+
log "fetching #{uid.inspect}"
|
230
230
|
fetch_data = reconnect_if_necessary do
|
231
|
-
@imap.uid_fetch(uid
|
231
|
+
@imap.uid_fetch(uid, ["FLAGS", "RFC822", "RFC822.SIZE"])[0]
|
232
232
|
end
|
233
233
|
res = fetch_data.attr["RFC822"]
|
234
|
-
mail = Mail.new(res)
|
235
|
-
@
|
236
|
-
|
234
|
+
mail = Mail.new(res)
|
235
|
+
@current_uid = uid
|
236
|
+
@current_mail = mail # used later to show raw message or extract attachments if any
|
237
|
+
log "saving current mail with parts: #{@current_mail.parts.inspect}"
|
238
|
+
formatter = Vmail::MessageFormatter.new(mail)
|
237
239
|
part = formatter.find_text_part
|
238
240
|
out = formatter.process_body
|
239
241
|
size = fetch_data.attr["RFC822.SIZE"]
|
240
|
-
|
242
|
+
@current_message = <<-EOF
|
241
243
|
#{@mailbox} #{uid} #{number_to_human_size size} #{forwarded ? nil : format_parts_info(formatter.list_parts)}
|
242
244
|
----------------------------------------
|
243
245
|
#{format_headers(formatter.extract_headers)}
|
@@ -324,7 +326,7 @@ EOF
|
|
324
326
|
select {|x| @username !~ /#{x.mailbox}@#{x.host}/}.
|
325
327
|
map {|x| address_to_string(x)}.join(", ")
|
326
328
|
mail = Mail.new fetch_data.attr['RFC822']
|
327
|
-
formatter = MessageFormatter.new(mail)
|
329
|
+
formatter = Vmail::MessageFormatter.new(mail)
|
328
330
|
headers = formatter.extract_headers
|
329
331
|
subject = headers['subject']
|
330
332
|
if subject !~ /Re: /
|
@@ -349,7 +351,7 @@ EOF
|
|
349
351
|
|
350
352
|
# TODO, forward with attachments
|
351
353
|
def forward_template(uid)
|
352
|
-
original_body =
|
354
|
+
original_body = show_message(uid, false, true)
|
353
355
|
new_message_template +
|
354
356
|
"\n---------- Forwarded message ----------\n" +
|
355
357
|
original_body + signature
|
@@ -401,11 +403,11 @@ EOF
|
|
401
403
|
|
402
404
|
def save_attachments(dir)
|
403
405
|
log "save_attachments #{dir}"
|
404
|
-
if !@
|
406
|
+
if !@current_mail
|
405
407
|
log "missing a current message"
|
406
408
|
end
|
407
|
-
return unless dir && @
|
408
|
-
attachments = @
|
409
|
+
return unless dir && @current_mail
|
410
|
+
attachments = @current_mail.attachments
|
409
411
|
`mkdir -p #{dir}`
|
410
412
|
attachments.each do |x|
|
411
413
|
path = File.join(dir, x.filename)
|
@@ -416,10 +418,16 @@ EOF
|
|
416
418
|
|
417
419
|
def open_html_part(uid)
|
418
420
|
log "open_html_part #{uid}"
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
421
|
+
log @current_mail.parts.inspect
|
422
|
+
multipart = @current_mail.parts.detect {|part| part.multipart?}
|
423
|
+
html_part = if multipart
|
424
|
+
multipart.parts.detect {|part| part.header["Content-Type"].to_s =~ /text\/html/}
|
425
|
+
elsif ! @current_mail.parts.empty?
|
426
|
+
@current_mail.parts.detect {|part| part.header["Content-Type"].to_s =~ /text\/html/}
|
427
|
+
else
|
428
|
+
@current_mail.body
|
429
|
+
end
|
430
|
+
return if html_part.nil?
|
423
431
|
outfile = 'htmlpart.html'
|
424
432
|
File.open(outfile, 'w') {|f| f.puts(html_part.decoded)}
|
425
433
|
# client should handle opening the html file
|
@@ -2,112 +2,114 @@ require 'mail'
|
|
2
2
|
require 'open3'
|
3
3
|
require 'iconv'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def list_parts(parts = (@mail.parts.empty? ? [@mail] : @mail.parts))
|
13
|
-
if parts.empty?
|
14
|
-
return []
|
5
|
+
module Vmail
|
6
|
+
class MessageFormatter
|
7
|
+
# initialize with a Mail object
|
8
|
+
def initialize(mail, uid = nil)
|
9
|
+
@mail = mail
|
10
|
+
@uid = uid
|
15
11
|
end
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
12
|
+
|
13
|
+
def list_parts(parts = (@mail.parts.empty? ? [@mail] : @mail.parts))
|
14
|
+
if parts.empty?
|
15
|
+
return []
|
16
|
+
end
|
17
|
+
lines = parts.map do |part|
|
18
|
+
if part.multipart?
|
19
|
+
list_parts(part.parts)
|
20
|
+
else
|
21
|
+
# part.charset could be used
|
22
|
+
"- #{part.content_type}"
|
23
|
+
end
|
22
24
|
end
|
25
|
+
lines.flatten
|
23
26
|
end
|
24
|
-
lines.flatten
|
25
|
-
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
28
|
+
def process_body
|
29
|
+
part = find_text_part(@mail.parts)
|
30
|
+
body = if part && part.respond_to?(:header)
|
31
|
+
if part.header["Content-Type"].to_s =~ /text\/plain/
|
32
|
+
format_text_body(part)
|
33
|
+
elsif part.header["Content-Type"].to_s =~ /text\/html/
|
34
|
+
format_html_body(part)
|
35
|
+
else
|
36
|
+
format_text_body(part)
|
37
|
+
end
|
38
|
+
else
|
39
|
+
"NO BODY"
|
36
40
|
end
|
37
|
-
|
38
|
-
|
41
|
+
rescue
|
42
|
+
puts $!
|
43
|
+
body
|
39
44
|
end
|
40
|
-
rescue
|
41
|
-
puts $!
|
42
|
-
body
|
43
|
-
end
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
if part
|
51
|
-
find_text_part(part.parts)
|
52
|
-
else
|
53
|
-
# no multipart part
|
54
|
-
part = parts.detect {|part| (part.header["Content-Type"].to_s =~ /text\/plain/) }
|
46
|
+
def find_text_part(parts = @mail.parts)
|
47
|
+
if parts.empty?
|
48
|
+
return @mail
|
49
|
+
end
|
50
|
+
part = parts.detect {|part| part.multipart?}
|
55
51
|
if part
|
56
|
-
|
52
|
+
find_text_part(part.parts)
|
57
53
|
else
|
58
|
-
|
54
|
+
# no multipart part
|
55
|
+
part = parts.detect {|part| (part.header["Content-Type"].to_s =~ /text\/plain/) }
|
56
|
+
if part
|
57
|
+
return part
|
58
|
+
else
|
59
|
+
parts.first
|
60
|
+
end
|
59
61
|
end
|
60
62
|
end
|
61
|
-
end
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
64
|
+
def format_text_body(part)
|
65
|
+
text = part.body.decoded.gsub("\r", '')
|
66
|
+
charset = part.content_type_parameters && part.content_type_parameters['charset']
|
67
|
+
if charset
|
68
|
+
Iconv.conv('utf-8//translit//ignore', charset, text)
|
69
|
+
else
|
70
|
+
text
|
71
|
+
end
|
70
72
|
end
|
71
|
-
end
|
72
|
-
|
73
|
-
# depend on lynx
|
74
|
-
def format_html_body(part)
|
75
|
-
html = part.body.decoded.gsub("\r", '')
|
76
|
-
stdin, stdout, stderr = Open3.popen3("lynx -stdin -dump")
|
77
|
-
stdin.puts html
|
78
|
-
stdin.close
|
79
|
-
output = stdout.read
|
80
|
-
charset = part.content_type_parameters && part.content_type_parameters['charset']
|
81
|
-
charset ? Iconv.conv('utf-8//translit//ignore', charset, output) : output
|
82
|
-
end
|
83
73
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
74
|
+
# depend on lynx
|
75
|
+
def format_html_body(part)
|
76
|
+
html = part.body.decoded.gsub("\r", '')
|
77
|
+
stdin, stdout, stderr = Open3.popen3("lynx -stdin -dump")
|
78
|
+
stdin.puts html
|
79
|
+
stdin.close
|
80
|
+
output = stdout.read
|
81
|
+
charset = part.content_type_parameters && part.content_type_parameters['charset']
|
82
|
+
charset ? Iconv.conv('utf-8//translit//ignore', charset, output) : output
|
92
83
|
end
|
93
|
-
|
94
|
-
|
84
|
+
|
85
|
+
def extract_headers(mail = @mail)
|
86
|
+
headers = {'from' => utf8(mail['from'].decoded),
|
87
|
+
'date' => (mail.date.strftime('%a, %b %d %I:%M %p %Z %Y') rescue mail.date),
|
88
|
+
'to' => mail['to'].nil? ? nil : utf8(mail['to'].decoded),
|
89
|
+
'subject' => utf8(mail.subject)
|
90
|
+
}
|
91
|
+
if !mail.cc.nil?
|
92
|
+
headers['cc'] = utf8(mail['cc'].decoded.to_s)
|
93
|
+
end
|
94
|
+
if !mail.reply_to.nil?
|
95
|
+
headers['reply_to'] = utf8(mail['reply_to'].decoded)
|
96
|
+
end
|
97
|
+
headers
|
98
|
+
rescue
|
99
|
+
{'error' => $!}
|
95
100
|
end
|
96
|
-
headers
|
97
|
-
rescue
|
98
|
-
{'error' => $!}
|
99
|
-
end
|
100
101
|
|
101
|
-
|
102
|
-
|
103
|
-
|
102
|
+
def encoding
|
103
|
+
@encoding ||= @mail.header.charset || 'utf-8'
|
104
|
+
end
|
104
105
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
106
|
+
def utf8(string)
|
107
|
+
return '' unless string
|
108
|
+
return string unless encoding
|
109
|
+
Iconv.conv('utf-8//translit/ignore', encoding, string)
|
110
|
+
rescue
|
111
|
+
puts $!
|
112
|
+
string
|
113
|
+
end
|
112
114
|
end
|
113
115
|
end
|
data/lib/vmail/string_ext.rb
CHANGED
@@ -6,6 +6,24 @@ class String
|
|
6
6
|
def rcol(width) #right justified
|
7
7
|
self[0,width].rjust(width)
|
8
8
|
end
|
9
|
+
|
10
|
+
def self.shellescape(str)
|
11
|
+
# An empty argument will be skipped, so return empty quotes.
|
12
|
+
return "''" if str.empty?
|
13
|
+
|
14
|
+
str = str.dup
|
15
|
+
|
16
|
+
# Process as a single byte sequence because not all shell
|
17
|
+
# implementations are multibyte aware.
|
18
|
+
str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
|
19
|
+
|
20
|
+
# A LF cannot be escaped with a backslash because a backslash + LF
|
21
|
+
# combo is regarded as line continuation and simply ignored.
|
22
|
+
str.gsub!(/\n/, "'\n'")
|
23
|
+
|
24
|
+
return str
|
25
|
+
end
|
26
|
+
|
9
27
|
end
|
10
28
|
|
11
29
|
|
data/lib/vmail/version.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'test_helper'
|
3
|
+
require 'vmail/message_formatter'
|
3
4
|
|
4
|
-
describe MessageFormatter do
|
5
|
+
describe Vmail::MessageFormatter do
|
5
6
|
describe "message with email addresses along with names" do
|
6
7
|
before do
|
7
8
|
@raw = File.read(File.expand_path('../fixtures/google-affiliate.eml', __FILE__))
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 4
|
9
|
+
version: 0.0.4
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Daniel Choi
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-12-
|
17
|
+
date: 2010-12-13 00:00:00 -05:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -49,11 +49,8 @@ files:
|
|
49
49
|
- bin/mvmail
|
50
50
|
- bin/vmail
|
51
51
|
- bin/vmail_client
|
52
|
-
- config/environment.rb
|
53
|
-
- config/gmail.orig.yml
|
54
52
|
- gmail.vim
|
55
53
|
- lib/contacts_extractor.rb
|
56
|
-
- lib/gmail.rb
|
57
54
|
- lib/vmail.rb
|
58
55
|
- lib/vmail.vim
|
59
56
|
- lib/vmail/imap_client.rb
|
data/config/environment.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
# configure activerecord to use mysql
|
2
|
-
#require 'active_record'
|
3
|
-
require 'logger'
|
4
|
-
require 'yaml'
|
5
|
-
require 'net/imap'
|
6
|
-
require 'mail'
|
7
|
-
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
8
|
-
|
9
|
-
#config_file = File.join(File.dirname(__FILE__), 'database.yml')
|
10
|
-
#config = YAML::load(File.read(config_file))['development']
|
11
|
-
#ActiveRecord::Base.establish_connection config
|
12
|
-
|
13
|
-
gmail_config_file = File.join(File.dirname(__FILE__), 'gmail.yml')
|
14
|
-
gmail_config = YAML::load(File.read(gmail_config_file))
|
15
|
-
require 'gmail'
|
16
|
-
$gmail = Gmail.new gmail_config['login'], gmail_config['password']
|
17
|
-
|
18
|
-
require 'message_formatter'
|
data/config/gmail.orig.yml
DELETED
data/lib/gmail.rb
DELETED
@@ -1,145 +0,0 @@
|
|
1
|
-
require 'net/imap'
|
2
|
-
|
3
|
-
class Gmail
|
4
|
-
DEMARC = "------=gmail-tool="
|
5
|
-
|
6
|
-
def initialize(username, password)
|
7
|
-
@username, @password = username, password
|
8
|
-
end
|
9
|
-
|
10
|
-
def open
|
11
|
-
raise "block missing" unless block_given?
|
12
|
-
@imap = Net::IMAP.new('imap.gmail.com', 993, true, nil, false)
|
13
|
-
@imap.login(@username, @password)
|
14
|
-
yield @imap
|
15
|
-
rescue Exception => ex
|
16
|
-
raise
|
17
|
-
ensure
|
18
|
-
if @imap
|
19
|
-
@imap.close rescue Net::IMAP::BadResponseError
|
20
|
-
@imap.disconnect
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
# lists mailboxes
|
25
|
-
def mailboxes
|
26
|
-
open do |imap|
|
27
|
-
imap.list("[Gmail]/", "%") + imap.list("", "%")
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# selects the mailbox and returns self
|
32
|
-
def mailbox(x)
|
33
|
-
@mailbox = x
|
34
|
-
# allow chaining
|
35
|
-
return self
|
36
|
-
end
|
37
|
-
|
38
|
-
def fetch(opts = {})
|
39
|
-
num_messages = opts[:num_messages] || 10
|
40
|
-
mailbox_label = opts[:mailbox] || @mailbox || 'inbox'
|
41
|
-
query = opts[:query] || ["ALL"]
|
42
|
-
open do |imap|
|
43
|
-
imap.select(mailbox_label)
|
44
|
-
all_uids = imap.uid_search(query)
|
45
|
-
STDERR.puts "#{all_uids.size} UIDS TOTAL"
|
46
|
-
uids = all_uids[-([num_messages, all_uids.size].min)..-1] || []
|
47
|
-
STDERR.puts "imap process uids #{uids.inspect}"
|
48
|
-
yield imap, uids
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
# generic mailbox operations
|
53
|
-
def imap
|
54
|
-
open do |imap|
|
55
|
-
imap.select((@mailbox || 'inbox'))
|
56
|
-
yield imap
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
|
62
|
-
class String
|
63
|
-
def col(width)
|
64
|
-
self[0,width].ljust(width)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def format_time(x)
|
69
|
-
Time.parse(x.to_s).localtime.strftime "%D %I:%M%P"
|
70
|
-
end
|
71
|
-
|
72
|
-
require 'time'
|
73
|
-
|
74
|
-
def search
|
75
|
-
mailbox = ARGV.shift
|
76
|
-
num_messages = ARGV.shift.to_i
|
77
|
-
#query = ["BODY", "politics"]
|
78
|
-
query = ARGV
|
79
|
-
puts mailbox
|
80
|
-
$gmail.mailbox(mailbox).fetch(:num_messages => num_messages, :query => query) do |imap,uids|
|
81
|
-
uids.each do |uid|
|
82
|
-
res = imap.uid_fetch(uid, ["FLAGS", "BODY", "ENVELOPE", "RFC822.HEADER"])[0]
|
83
|
-
#puts res.inspect
|
84
|
-
#puts res
|
85
|
-
header = res.attr["RFC822.HEADER"]
|
86
|
-
mail = Mail.new(header)
|
87
|
-
mail_id = uid
|
88
|
-
flags = res.attr["FLAGS"]
|
89
|
-
puts "#{mail_id} #{format_time(mail.date.to_s)} #{mail.from[0][0,30].ljust(30)} #{mail.subject.to_s[0,70].ljust(70)} #{flags.inspect.col(30)}"
|
90
|
-
|
91
|
-
next
|
92
|
-
|
93
|
-
mail = Mail.new(res)
|
94
|
-
foldline = [mail[:from], mail[:date], mail[:subject]].join(" ")
|
95
|
-
puts foldline + " {{{1"
|
96
|
-
if mail.parts.empty?
|
97
|
-
puts mail.body.decoded
|
98
|
-
else
|
99
|
-
puts mail.parts.inspect
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def lookup(raw=false)
|
106
|
-
mailbox, uid = *ARGV[0,2]
|
107
|
-
$gmail.mailbox(mailbox).imap do |imap|
|
108
|
-
res = imap.uid_fetch(uid.to_i, ["FLAGS", "RFC822"])[0].attr["RFC822"]
|
109
|
-
if raw
|
110
|
-
puts res
|
111
|
-
return
|
112
|
-
end
|
113
|
-
mail = Mail.new(res)
|
114
|
-
if mail.parts.empty?
|
115
|
-
puts mail.header["Content-Type"]
|
116
|
-
puts mail.body.charset
|
117
|
-
puts mail.body.decoded
|
118
|
-
else
|
119
|
-
puts mail.parts.inspect
|
120
|
-
part = mail.parts.detect {|part|
|
121
|
-
(part.header["Content-Type"].to_s =~ /text\/plain/)
|
122
|
-
}
|
123
|
-
if part
|
124
|
-
puts "PART"
|
125
|
-
puts part.header["Content-Type"]
|
126
|
-
puts part.charset
|
127
|
-
puts part.body.decoded
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
if __FILE__ == $0
|
134
|
-
require 'yaml'
|
135
|
-
require 'mail'
|
136
|
-
config = YAML::load(File.read(File.expand_path("../../config/gmail.yml", __FILE__)))
|
137
|
-
$gmail = Gmail.new(config['login'], config['password'])
|
138
|
-
if ARGV.length == 2
|
139
|
-
lookup
|
140
|
-
elsif ARGV[2] == 'raw'
|
141
|
-
lookup(raw=true)
|
142
|
-
else
|
143
|
-
search
|
144
|
-
end
|
145
|
-
end
|