sup 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sup might be problematic. Click here for more details.
- data/HACKING +50 -0
- data/History.txt +12 -0
- data/Manifest.txt +2 -0
- data/Rakefile +5 -1
- data/bin/sup +24 -8
- data/bin/sup-add +106 -0
- data/bin/sup-import +85 -165
- data/doc/FAQ.txt +48 -11
- data/doc/TODO +45 -12
- data/doc/UserGuide.txt +54 -42
- data/lib/sup.rb +6 -2
- data/lib/sup/buffer.rb +20 -4
- data/lib/sup/contact.rb +22 -12
- data/lib/sup/draft.rb +27 -7
- data/lib/sup/imap.rb +107 -97
- data/lib/sup/index.rb +35 -25
- data/lib/sup/label.rb +2 -2
- data/lib/sup/mbox.rb +20 -15
- data/lib/sup/mbox/loader.rb +0 -1
- data/lib/sup/mbox/ssh-file.rb +58 -47
- data/lib/sup/mbox/ssh-loader.rb +13 -20
- data/lib/sup/message.rb +15 -9
- data/lib/sup/mode.rb +16 -6
- data/lib/sup/modes/compose-mode.rb +7 -3
- data/lib/sup/modes/contact-list-mode.rb +50 -25
- data/lib/sup/modes/edit-message-mode.rb +11 -8
- data/lib/sup/modes/inbox-mode.rb +19 -3
- data/lib/sup/modes/label-list-mode.rb +4 -12
- data/lib/sup/modes/log-mode.rb +6 -0
- data/lib/sup/modes/reply-mode.rb +19 -6
- data/lib/sup/modes/resume-mode.rb +13 -9
- data/lib/sup/modes/scroll-mode.rb +13 -2
- data/lib/sup/modes/thread-index-mode.rb +94 -43
- data/lib/sup/modes/thread-view-mode.rb +198 -130
- data/lib/sup/person.rb +1 -1
- data/lib/sup/poll.rb +60 -47
- data/lib/sup/sent.rb +3 -2
- data/lib/sup/source.rb +14 -6
- data/lib/sup/textfield.rb +1 -0
- data/lib/sup/thread.rb +76 -23
- data/lib/sup/update.rb +2 -3
- data/lib/sup/util.rb +13 -13
- metadata +14 -2
data/lib/sup/mbox/loader.rb
CHANGED
data/lib/sup/mbox/ssh-file.rb
CHANGED
@@ -92,6 +92,9 @@ class SSHFile
|
|
92
92
|
REASONABLE_TRANSFER_SIZE = 1024 * 32
|
93
93
|
SIZE_CHECK_INTERVAL = 60 * 1 # seconds
|
94
94
|
|
95
|
+
## upon these errors we'll try to rereconnect a few times
|
96
|
+
RECOVERABLE_ERRORS = [ Errno::EPIPE, Errno::ETIMEDOUT ]
|
97
|
+
|
95
98
|
@@shells = {}
|
96
99
|
@@shells_mutex = Mutex.new
|
97
100
|
|
@@ -105,46 +108,15 @@ class SSHFile
|
|
105
108
|
@say_id = nil
|
106
109
|
@broken_msg = nil
|
107
110
|
@shell = nil
|
108
|
-
@shell_mutex =
|
111
|
+
@shell_mutex = nil
|
109
112
|
@buf_mutex = Mutex.new
|
110
113
|
end
|
111
114
|
|
112
|
-
def to_s; "mbox+ssh://#@host/#@fn"; end ## TODO: remove
|
115
|
+
def to_s; "mbox+ssh://#@host/#@fn"; end ## TODO: remove this EVILness
|
113
116
|
def broken?; !@broken_msg.nil?; end
|
114
117
|
|
115
|
-
## TODO: share this code with imap
|
116
|
-
def say s
|
117
|
-
@say_id = BufferManager.say s, @say_id if BufferManager.instantiated?
|
118
|
-
Redwood::log s
|
119
|
-
end
|
120
|
-
def shutup
|
121
|
-
BufferManager.clear @say_id if BufferManager.instantiated? && @say_id
|
122
|
-
@say_id = nil
|
123
|
-
end
|
124
|
-
private :say, :shutup
|
125
|
-
|
126
118
|
def connect
|
127
|
-
|
128
|
-
return if @shell
|
129
|
-
|
130
|
-
@key = [@host, @ssh_opts[:username]]
|
131
|
-
begin
|
132
|
-
@shell = @@shells_mutex.synchronize do
|
133
|
-
unless @@shells.member? @key
|
134
|
-
say "Opening SSH connection to #{@host} for #@fn..."
|
135
|
-
#raise SSHFileError, "simulated SSH file error"
|
136
|
-
session = Net::SSH.start @host, @ssh_opts
|
137
|
-
say "Starting SSH shell..."
|
138
|
-
@@shells[@key] = session.shell.sync
|
139
|
-
end
|
140
|
-
@@shells[@key]
|
141
|
-
end
|
142
|
-
|
143
|
-
say "Checking for #@fn..."
|
144
|
-
@shell_mutex.synchronize { raise Errno::ENOENT, @fn unless @shell.test("-e #@fn").status == 0 }
|
145
|
-
ensure
|
146
|
-
shutup
|
147
|
-
end
|
119
|
+
do_remote nil
|
148
120
|
end
|
149
121
|
|
150
122
|
def eof?; @offset >= size; end
|
@@ -180,32 +152,71 @@ class SSHFile
|
|
180
152
|
|
181
153
|
private
|
182
154
|
|
155
|
+
## TODO: share this code with imap
|
156
|
+
def say s
|
157
|
+
@say_id = BufferManager.say s, @say_id if BufferManager.instantiated?
|
158
|
+
Redwood::log s
|
159
|
+
end
|
160
|
+
|
161
|
+
def shutup
|
162
|
+
BufferManager.clear @say_id if BufferManager.instantiated? && @say_id
|
163
|
+
@say_id = nil
|
164
|
+
end
|
165
|
+
|
166
|
+
def unsafe_connect
|
167
|
+
raise SSHFileError, @broken_msg if broken?
|
168
|
+
return if @shell
|
169
|
+
|
170
|
+
@key = [@host, @ssh_opts[:username]]
|
171
|
+
begin
|
172
|
+
@shell, @shell_mutex = @@shells_mutex.synchronize do
|
173
|
+
unless @@shells.member? @key
|
174
|
+
say "Opening SSH connection to #{@host} for #@fn..."
|
175
|
+
#raise SSHFileError, "simulated SSH file error"
|
176
|
+
session = Net::SSH.start @host, @ssh_opts
|
177
|
+
say "Starting SSH shell..."
|
178
|
+
@@shells[@key] = [session.shell.sync, Mutex.new]
|
179
|
+
end
|
180
|
+
@@shells[@key]
|
181
|
+
end
|
182
|
+
|
183
|
+
say "Checking for #@fn..."
|
184
|
+
@shell_mutex.synchronize { raise Errno::ENOENT, @fn unless @shell.test("-e #@fn").status == 0 }
|
185
|
+
ensure
|
186
|
+
shutup
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
183
190
|
def do_remote cmd, expected_size=0
|
191
|
+
retries = 0
|
192
|
+
result = nil
|
184
193
|
begin
|
185
|
-
retries = 0
|
186
|
-
connect
|
187
|
-
# MBox::debug "sending command: #{cmd.inspect}"
|
188
194
|
begin
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
195
|
+
unsafe_connect
|
196
|
+
if cmd
|
197
|
+
# MBox::debug "sending command: #{cmd.inspect}"
|
198
|
+
result = @shell_mutex.synchronize { x = @shell.send_command cmd; sleep 0.25; x }
|
199
|
+
raise SSHFileError, "Failure during remote command #{cmd.inspect}: #{(result.stderr || result.stdout || "")[0 .. 100]}" unless result.status == 0
|
200
|
+
end
|
201
|
+
|
202
|
+
## Net::SSH::Exceptions seem to happen every once in a while for
|
203
|
+
## no good reason.
|
204
|
+
rescue Net::SSH::Exception, *RECOVERABLE_ERRORS
|
205
|
+
if (retries += 1) <= 3
|
196
206
|
@@shells_mutex.synchronize do
|
197
207
|
@shell = nil
|
198
208
|
@@shells[@key] = nil
|
199
209
|
end
|
200
|
-
connect
|
201
210
|
retry
|
202
211
|
end
|
212
|
+
raise
|
203
213
|
end
|
204
|
-
rescue Net::SSH::Exception, SSHFileError,
|
214
|
+
rescue Net::SSH::Exception, SSHFileError, SystemCallError => e
|
205
215
|
@broken_msg = e.message
|
206
216
|
raise
|
207
217
|
end
|
208
|
-
|
218
|
+
|
219
|
+
result.stdout if cmd
|
209
220
|
end
|
210
221
|
|
211
222
|
def get_bytes offset, size
|
data/lib/sup/mbox/ssh-loader.rb
CHANGED
@@ -36,26 +36,21 @@ class SSHLoader < Source
|
|
36
36
|
@labels << File.basename(filename).intern unless File.dirname(filename) =~ /\b(var|usr|spool)\b/
|
37
37
|
end
|
38
38
|
|
39
|
+
def connect; safely { @f.connect }; end
|
39
40
|
def host; @parsed_uri.host; end
|
40
41
|
def filename; @parsed_uri.path[1..-1] end
|
41
42
|
|
42
43
|
def next
|
43
44
|
return if broken?
|
44
|
-
|
45
|
+
safely do
|
45
46
|
offset, labels = @loader.next
|
46
47
|
self.cur_offset = @loader.cur_offset # superclass keeps @cur_offset which is used by yaml
|
47
48
|
[offset, (labels + @labels).uniq] # add our labels
|
48
|
-
rescue Net::SSH::Exception, SocketError, SSHFileError, Errno::ENOENT => e
|
49
|
-
recover_from e
|
50
49
|
end
|
51
50
|
end
|
52
51
|
|
53
52
|
def end_offset
|
54
|
-
|
55
|
-
@f.size
|
56
|
-
rescue Net::SSH::Exception, SocketError, SSHFileError, Errno::ENOENT => e
|
57
|
-
recover_from e
|
58
|
-
end
|
53
|
+
safely { @f.size }
|
59
54
|
end
|
60
55
|
|
61
56
|
def cur_offset= o; @cur_offset = @loader.cur_offset = o; @dirty = true; end
|
@@ -64,21 +59,19 @@ class SSHLoader < Source
|
|
64
59
|
# def cur_offset; @loader.cur_offset; end # think we'll be ok without this
|
65
60
|
def to_s; @parsed_uri.to_s; end
|
66
61
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
62
|
+
def safely
|
63
|
+
begin
|
64
|
+
yield
|
65
|
+
rescue Net::SSH::Exception, SocketError, SSHFileError, SystemCallError => e
|
66
|
+
m = "error communicating with SSH server #{host} (#{e.class.name}): #{e.message}"
|
67
|
+
Redwood::log m
|
68
|
+
self.broken_msg = @loader.broken_msg = m
|
69
|
+
raise SourceError, m
|
70
|
+
end
|
72
71
|
end
|
73
72
|
|
74
73
|
[:start_offset, :load_header, :load_message, :raw_header, :raw_full_message].each do |meth|
|
75
|
-
define_method
|
76
|
-
begin
|
77
|
-
@loader.send meth, *a
|
78
|
-
rescue Net::SSH::Exception, SocketError, SSHFileError, Errno::ENOENT => e
|
79
|
-
recover_from e
|
80
|
-
end
|
81
|
-
end
|
74
|
+
define_method(meth) { |*a| safely { @loader.send meth, *a } }
|
82
75
|
end
|
83
76
|
end
|
84
77
|
|
data/lib/sup/message.rb
CHANGED
@@ -17,6 +17,7 @@ class MessageFormatError < StandardError; end
|
|
17
17
|
## appropriately.
|
18
18
|
class Message
|
19
19
|
SNIPPET_LEN = 80
|
20
|
+
WRAP_LEN = 80 # wrap at this width
|
20
21
|
RE_PATTERN = /^((re|re[\[\(]\d[\]\)]):\s*)+/i
|
21
22
|
|
22
23
|
## some utility methods
|
@@ -45,6 +46,7 @@ class Message
|
|
45
46
|
|
46
47
|
## TODO: handle unknown mime-types
|
47
48
|
system "/usr/bin/run-mailcap --action=view #{@content_type}:#{@file.path}"
|
49
|
+
$? == 0
|
48
50
|
end
|
49
51
|
|
50
52
|
def to_s; @part.decode; end
|
@@ -54,7 +56,7 @@ class Message
|
|
54
56
|
attr_reader :lines
|
55
57
|
def initialize lines
|
56
58
|
## do some wrapping
|
57
|
-
@lines = lines.map { |l| l.chomp.wrap
|
59
|
+
@lines = lines.map { |l| l.chomp.wrap WRAP_LEN }.flatten
|
58
60
|
end
|
59
61
|
end
|
60
62
|
|
@@ -75,14 +77,14 @@ class Message
|
|
75
77
|
QUOTE_PATTERN = /^\s{0,4}[>|\}]/
|
76
78
|
BLOCK_QUOTE_PATTERN = /^-----\s*Original Message\s*----+$/
|
77
79
|
QUOTE_START_PATTERN = /(^\s*Excerpts from)|(^\s*In message )|(^\s*In article )|(^\s*Quoting )|((wrote|writes|said|says)\s*:\s*$)/
|
78
|
-
SIG_PATTERN = /(^-- ?$)|(^\s*----------+\s*$)|(^\s*_________+\s*$)/
|
80
|
+
SIG_PATTERN = /(^-- ?$)|(^\s*----------+\s*$)|(^\s*_________+\s*$)|(^\s*--~--~-)/
|
79
81
|
MAX_SIG_DISTANCE = 15 # lines from the end
|
80
82
|
DEFAULT_SUBJECT = "(missing subject)"
|
81
83
|
DEFAULT_SENDER = "(missing sender)"
|
82
84
|
|
83
85
|
attr_reader :id, :date, :from, :subj, :refs, :replytos, :to, :source,
|
84
86
|
:cc, :bcc, :labels, :list_address, :recipient_email, :replyto,
|
85
|
-
:source_info
|
87
|
+
:source_info, :chunks
|
86
88
|
|
87
89
|
bool_reader :dirty, :source_marked_read
|
88
90
|
|
@@ -95,6 +97,7 @@ class Message
|
|
95
97
|
@have_snippet = !opts[:snippet].nil?
|
96
98
|
@labels = opts[:labels] || []
|
97
99
|
@dirty = false
|
100
|
+
@chunks = nil
|
98
101
|
|
99
102
|
read_header(opts[:header] || @source.load_header(@source_info))
|
100
103
|
end
|
@@ -130,13 +133,13 @@ class Message
|
|
130
133
|
nil
|
131
134
|
end
|
132
135
|
|
133
|
-
@recipient_email = header["
|
136
|
+
@recipient_email = header["envelope-to"] || header["x-original-to"] || header["delivered-to"]
|
134
137
|
@source_marked_read = header["status"] == "RO"
|
135
138
|
end
|
136
139
|
private :read_header
|
137
140
|
|
138
141
|
def broken?; @source.broken?; end
|
139
|
-
def snippet; @snippet ||
|
142
|
+
def snippet; @snippet || chunks && @snippet; end
|
140
143
|
def is_list_message?; !@list_address.nil?; end
|
141
144
|
def is_draft?; DraftLoader === @source; end
|
142
145
|
def draft_filename
|
@@ -172,7 +175,7 @@ class Message
|
|
172
175
|
end
|
173
176
|
|
174
177
|
## this is called when the message body needs to actually be loaded.
|
175
|
-
def
|
178
|
+
def load_from_source!
|
176
179
|
@chunks ||=
|
177
180
|
if @source.broken?
|
178
181
|
[Text.new(error_message(@source.broken_msg.split("\n")))]
|
@@ -184,6 +187,8 @@ class Message
|
|
184
187
|
## i could just store that in the index, but i think there might
|
185
188
|
## be other things like that in the future, and i'd rather not
|
186
189
|
## bloat the index.
|
190
|
+
## actually, it's also the differentiation between to/cc/bcc,
|
191
|
+
## so i will keep this.
|
187
192
|
read_header @source.load_header(@source_info)
|
188
193
|
message_to_chunks @source.load_message(@source_info)
|
189
194
|
rescue SourceError, SocketError, MessageFormatError => e
|
@@ -223,18 +228,19 @@ EOS
|
|
223
228
|
end
|
224
229
|
|
225
230
|
def content
|
231
|
+
load_from_source!
|
226
232
|
[
|
227
233
|
from && "#{from.name} #{from.email}",
|
228
234
|
to.map { |p| "#{p.name} #{p.email}" },
|
229
235
|
cc.map { |p| "#{p.name} #{p.email}" },
|
230
236
|
bcc.map { |p| "#{p.name} #{p.email}" },
|
231
|
-
|
237
|
+
chunks.select { |c| c.is_a? Text }.map { |c| c.lines },
|
232
238
|
Message.normalize_subj(subj),
|
233
239
|
].flatten.compact.join " "
|
234
240
|
end
|
235
241
|
|
236
242
|
def basic_body_lines
|
237
|
-
|
243
|
+
chunks.find_all { |c| c.is_a?(Text) || c.is_a?(Quote) }.map { |c| c.lines }.flatten
|
238
244
|
end
|
239
245
|
|
240
246
|
def basic_header_lines
|
@@ -300,7 +306,7 @@ private
|
|
300
306
|
when :quote
|
301
307
|
newstate = nil
|
302
308
|
|
303
|
-
if line =~ QUOTE_PATTERN || line =~ QUOTE_START_PATTERN
|
309
|
+
if line =~ QUOTE_PATTERN || line =~ QUOTE_START_PATTERN #|| line =~ /^\s*$/
|
304
310
|
chunk_lines << line
|
305
311
|
elsif line =~ SIG_PATTERN && (lines.length - i) < MAX_SIG_DISTANCE
|
306
312
|
newstate = :sig
|
data/lib/sup/mode.rb
CHANGED
@@ -48,12 +48,9 @@ class Mode
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def handle_input c
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
else
|
55
|
-
false
|
56
|
-
end
|
51
|
+
action = resolve_input(c) or return false
|
52
|
+
send action
|
53
|
+
true
|
57
54
|
end
|
58
55
|
|
59
56
|
def help_text
|
@@ -75,6 +72,19 @@ EOS
|
|
75
72
|
s
|
76
73
|
end.compact.join "\n"
|
77
74
|
end
|
75
|
+
|
76
|
+
## helper function
|
77
|
+
def save_to_file fn
|
78
|
+
if File.exists? fn
|
79
|
+
return unless BufferManager.ask_yes_or_no "File exists. Overwrite?"
|
80
|
+
end
|
81
|
+
begin
|
82
|
+
File.open(fn, "w") { |f| yield f }
|
83
|
+
BufferManager.flash "Successfully wrote #{fn}."
|
84
|
+
rescue SystemCallError => e
|
85
|
+
BufferManager.flash "Error writing to file: #{e.message}"
|
86
|
+
end
|
87
|
+
end
|
78
88
|
end
|
79
89
|
|
80
90
|
end
|
@@ -3,15 +3,19 @@ module Redwood
|
|
3
3
|
class ComposeMode < EditMessageMode
|
4
4
|
attr_reader :body, :header
|
5
5
|
|
6
|
-
def initialize
|
6
|
+
def initialize opts={}
|
7
7
|
super()
|
8
8
|
@header = {
|
9
9
|
"From" => AccountManager.default_account.full_address,
|
10
10
|
"Message-Id" => gen_message_id,
|
11
11
|
}
|
12
12
|
|
13
|
-
@header["To"] = [
|
14
|
-
@
|
13
|
+
@header["To"] = opts[:to].map { |p| p.full_address }.join(", ") if opts[:to]
|
14
|
+
@header["Cc"] = opts[:cc].map { |p| p.full_address }.join(", ") if opts[:cc]
|
15
|
+
@header["Bcc"] = opts[:bcc].map { |p| p.full_address }.join(", ") if opts[:bcc]
|
16
|
+
@header["Subject"] = opts[:subj] if opts[:subj]
|
17
|
+
|
18
|
+
@body = (opts[:body] || []) + sig_lines
|
15
19
|
regen_text
|
16
20
|
end
|
17
21
|
|
@@ -1,12 +1,23 @@
|
|
1
1
|
module Redwood
|
2
2
|
|
3
|
+
module CanAliasContacts
|
4
|
+
def alias_contact p
|
5
|
+
a = BufferManager.ask(:alias, "Nickname for #{p.longname}: ", ContactManager.alias_for(p)) or return
|
6
|
+
if a.empty?
|
7
|
+
ContactManager.drop_contact p
|
8
|
+
else
|
9
|
+
ContactManager.set_contact p, a
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
3
14
|
class ContactListMode < LineCursorMode
|
4
15
|
LOAD_MORE_CONTACTS_NUM = 10
|
5
16
|
|
6
17
|
register_keymap do |k|
|
7
18
|
k.add :load_more, "Load #{LOAD_MORE_CONTACTS_NUM} more contacts", 'M'
|
8
|
-
k.add :reload, "
|
9
|
-
k.add :alias, "Edit alias for contact", 'a'
|
19
|
+
k.add :reload, "Drop contact list and reload", 'D'
|
20
|
+
k.add :alias, "Edit nickname/alias for contact", 'a'
|
10
21
|
k.add :toggle_tagged, "Tag/untag current line", 't'
|
11
22
|
k.add :apply_to_tagged, "Apply next command to all tagged items", ';'
|
12
23
|
k.add :search, "Search for messages from particular people", 'S'
|
@@ -15,10 +26,18 @@ class ContactListMode < LineCursorMode
|
|
15
26
|
def initialize mode = :regular
|
16
27
|
@mode = mode
|
17
28
|
@tags = Tagger.new self
|
18
|
-
@num =
|
29
|
+
@num = nil
|
30
|
+
@text = []
|
19
31
|
super()
|
20
32
|
end
|
21
33
|
|
34
|
+
include CanAliasContacts
|
35
|
+
def alias
|
36
|
+
p = @contacts[curpos] or return
|
37
|
+
alias_contact p
|
38
|
+
update
|
39
|
+
end
|
40
|
+
|
22
41
|
def lines; @text.length; end
|
23
42
|
def [] i; @text[i]; end
|
24
43
|
|
@@ -31,15 +50,15 @@ class ContactListMode < LineCursorMode
|
|
31
50
|
|
32
51
|
def multi_toggle_tagged threads
|
33
52
|
@tags.drop_all_tags
|
34
|
-
|
53
|
+
update
|
35
54
|
end
|
36
55
|
|
37
56
|
def apply_to_tagged; @tags.apply_to_tagged; end
|
38
57
|
|
39
|
-
def load; regen_text; end
|
40
58
|
def load_more num=LOAD_MORE_CONTACTS_NUM
|
41
59
|
@num += num
|
42
|
-
|
60
|
+
load
|
61
|
+
update
|
43
62
|
BufferManager.flash "Added #{num} contacts."
|
44
63
|
end
|
45
64
|
|
@@ -59,7 +78,7 @@ class ContactListMode < LineCursorMode
|
|
59
78
|
|
60
79
|
def multi_search people
|
61
80
|
mode = PersonSearchResultsMode.new people
|
62
|
-
BufferManager.spawn "
|
81
|
+
BufferManager.spawn "search for #{people.map { |p| p.name }.join(', ')}", mode
|
63
82
|
mode.load_threads :num => mode.buffer.content_height
|
64
83
|
end
|
65
84
|
|
@@ -70,49 +89,55 @@ class ContactListMode < LineCursorMode
|
|
70
89
|
|
71
90
|
def reload
|
72
91
|
@tags.drop_all_tags
|
92
|
+
@num = nil
|
73
93
|
load
|
74
94
|
end
|
75
95
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
else
|
82
|
-
ContactManager.set_contact p, a
|
83
|
-
@user_contacts[p] = a
|
84
|
-
update_text_for_line curpos
|
96
|
+
def load_in_background
|
97
|
+
Redwood::reporting_thread do
|
98
|
+
load
|
99
|
+
update
|
100
|
+
BufferManager.draw_screen
|
85
101
|
end
|
86
102
|
end
|
87
103
|
|
104
|
+
def load
|
105
|
+
@num ||= buffer.content_height
|
106
|
+
@user_contacts = ContactManager.contacts
|
107
|
+
num = [@num - @user_contacts.length, 0].max
|
108
|
+
BufferManager.say("Loading #{num} contacts from index...") do
|
109
|
+
recentc = Index.load_contacts AccountManager.user_emails, :num => num
|
110
|
+
@contacts = (@user_contacts + recentc).sort_by { |p| p.sort_by_me }.uniq
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
88
114
|
protected
|
89
115
|
|
116
|
+
def update
|
117
|
+
regen_text
|
118
|
+
buffer.mark_dirty if buffer
|
119
|
+
end
|
120
|
+
|
90
121
|
def update_text_for_line line
|
91
122
|
@text[line] = text_for_contact @contacts[line]
|
92
|
-
buffer.mark_dirty
|
123
|
+
buffer.mark_dirty if buffer
|
93
124
|
end
|
94
125
|
|
95
126
|
def text_for_contact p
|
96
|
-
aalias =
|
127
|
+
aalias = ContactManager.alias_for(p) || ""
|
97
128
|
[[:tagged_color, @tags.tagged?(p) ? ">" : " "],
|
98
129
|
[:none, sprintf("%-#{@awidth}s %-#{@nwidth}s %s", aalias, p.name, p.email)]]
|
99
130
|
end
|
100
131
|
|
101
132
|
def regen_text
|
102
|
-
@user_contacts = ContactManager.contacts.invert
|
103
|
-
recent = Index.load_contacts AccountManager.user_emails, :num => [@num - @user_contacts.length, 0].max
|
104
|
-
|
105
|
-
@contacts = (@user_contacts.keys + recent.select { |p| !@user_contacts[p] }).sort_by { |p| p.sort_by_me + (p.name || "") + p.email }.remove_successive_dupes
|
106
|
-
|
107
133
|
@awidth, @nwidth = 0, 0
|
108
134
|
@contacts.each do |p|
|
109
|
-
aalias =
|
135
|
+
aalias = ContactManager.alias_for(p)
|
110
136
|
@awidth = aalias.length if aalias && aalias.length > @awidth
|
111
137
|
@nwidth = p.name.length if p.name && p.name.length > @nwidth
|
112
138
|
end
|
113
139
|
|
114
140
|
@text = @contacts.map { |p| text_for_contact p }
|
115
|
-
buffer.mark_dirty if buffer
|
116
141
|
end
|
117
142
|
end
|
118
143
|
|