sup 0.9.1 → 0.10
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/CONTRIBUTORS +10 -6
- data/History.txt +11 -0
- data/ReleaseNotes +10 -0
- data/bin/sup +55 -19
- data/bin/sup-add +18 -8
- data/bin/sup-config +2 -2
- data/bin/sup-convert-ferret-index +84 -0
- data/bin/sup-dump +4 -3
- data/bin/sup-sync +4 -3
- data/bin/sup-sync-back +3 -2
- data/bin/sup-tweak-labels +3 -3
- data/lib/sup.rb +35 -4
- data/lib/sup/buffer.rb +12 -6
- data/lib/sup/colormap.rb +1 -0
- data/lib/sup/crypto.rb +76 -55
- data/lib/sup/ferret_index.rb +6 -1
- data/lib/sup/index.rb +62 -8
- data/lib/sup/logger.rb +2 -1
- data/lib/sup/maildir.rb +4 -2
- data/lib/sup/mbox/loader.rb +4 -3
- data/lib/sup/message-chunks.rb +9 -7
- data/lib/sup/message.rb +29 -27
- data/lib/sup/mode.rb +11 -4
- data/lib/sup/modes/buffer-list-mode.rb +5 -0
- data/lib/sup/modes/console-mode.rb +4 -0
- data/lib/sup/modes/edit-message-mode.rb +4 -2
- data/lib/sup/modes/file-browser-mode.rb +1 -1
- data/lib/sup/modes/inbox-mode.rb +18 -1
- data/lib/sup/modes/label-list-mode.rb +44 -3
- data/lib/sup/modes/text-mode.rb +1 -1
- data/lib/sup/modes/thread-index-mode.rb +63 -52
- data/lib/sup/modes/thread-view-mode.rb +68 -7
- data/lib/sup/poll.rb +20 -5
- data/lib/sup/source.rb +1 -0
- data/lib/sup/thread.rb +1 -1
- data/lib/sup/util.rb +49 -11
- data/lib/sup/xapian_index.rb +151 -112
- metadata +4 -10
- data/lib/sup/hook.rb.BACKUP.8625.rb +0 -158
- data/lib/sup/hook.rb.BACKUP.8681.rb +0 -158
- data/lib/sup/hook.rb.BASE.8625.rb +0 -155
- data/lib/sup/hook.rb.BASE.8681.rb +0 -155
- data/lib/sup/hook.rb.LOCAL.8625.rb +0 -142
- data/lib/sup/hook.rb.LOCAL.8681.rb +0 -142
- data/lib/sup/hook.rb.REMOTE.8625.rb +0 -145
- data/lib/sup/hook.rb.REMOTE.8681.rb +0 -145
data/lib/sup/maildir.rb
CHANGED
@@ -59,7 +59,7 @@ class Maildir < Source
|
|
59
59
|
File.stat(tmp_path)
|
60
60
|
rescue Errno::ENOENT #this is what we want.
|
61
61
|
begin
|
62
|
-
File.open(tmp_path, '
|
62
|
+
File.open(tmp_path, 'wb') do |f|
|
63
63
|
yield f #provide a writable interface for the caller
|
64
64
|
f.fsync
|
65
65
|
end
|
@@ -187,6 +187,8 @@ class Maildir < Source
|
|
187
187
|
def mark_seen msg; maildir_mark_file msg, "S" unless seen? msg; end
|
188
188
|
def mark_trashed msg; maildir_mark_file msg, "T" unless trashed? msg; end
|
189
189
|
|
190
|
+
def filename_for_id id; @ids_to_fns[id] end
|
191
|
+
|
190
192
|
private
|
191
193
|
|
192
194
|
def make_id fn
|
@@ -205,7 +207,7 @@ private
|
|
205
207
|
def with_file_for id
|
206
208
|
fn = @ids_to_fns[id] or raise OutOfSyncSourceError, "No such id: #{id.inspect}."
|
207
209
|
begin
|
208
|
-
File.open(fn) { |f| yield f }
|
210
|
+
File.open(fn, 'rb') { |f| yield f }
|
209
211
|
rescue SystemCallError, IOError => e
|
210
212
|
raise FatalSourceError, "Problem reading file for id #{id.inspect}: #{fn.inspect}: #{e.message}."
|
211
213
|
end
|
data/lib/sup/mbox/loader.rb
CHANGED
@@ -12,7 +12,7 @@ class Loader < Source
|
|
12
12
|
attr_reader :labels
|
13
13
|
|
14
14
|
## uri_or_fp is horrific. need to refactor.
|
15
|
-
def initialize uri_or_fp, start_offset=
|
15
|
+
def initialize uri_or_fp, start_offset=nil, usual=true, archived=false, id=nil, labels=nil
|
16
16
|
@mutex = Mutex.new
|
17
17
|
@labels = Set.new((labels || []) - LabelManager::RESERVED_LABELS)
|
18
18
|
|
@@ -22,13 +22,14 @@ class Loader < Source
|
|
22
22
|
raise ArgumentError, "not an mbox uri" unless uri.scheme == "mbox"
|
23
23
|
raise ArgumentError, "mbox URI ('#{uri}') cannot have a host: #{uri.host}" if uri.host
|
24
24
|
raise ArgumentError, "mbox URI must have a path component" unless uri.path
|
25
|
-
@f = File.open uri.path
|
25
|
+
@f = File.open uri.path, 'rb'
|
26
26
|
@path = uri.path
|
27
27
|
else
|
28
28
|
@f = uri_or_fp
|
29
29
|
@path = uri_or_fp.path
|
30
30
|
end
|
31
31
|
|
32
|
+
start_offset ||= 0
|
32
33
|
super uri_or_fp, start_offset, usual, archived, id
|
33
34
|
end
|
34
35
|
|
@@ -114,7 +115,7 @@ class Loader < Source
|
|
114
115
|
|
115
116
|
def store_message date, from_email, &block
|
116
117
|
need_blank = File.exists?(@filename) && !File.zero?(@filename)
|
117
|
-
File.open(@filename, "
|
118
|
+
File.open(@filename, "ab") do |f|
|
118
119
|
f.puts if need_blank
|
119
120
|
f.puts "From #{from_email} #{date.rfc2822}"
|
120
121
|
yield f
|
data/lib/sup/message-chunks.rb
CHANGED
@@ -41,8 +41,6 @@ end
|
|
41
41
|
|
42
42
|
module Redwood
|
43
43
|
module Chunk
|
44
|
-
WRAP_LEN = 80 # wrap messages and text attachments at this width
|
45
|
-
|
46
44
|
class Attachment
|
47
45
|
HookManager.register "mime-decode", <<EOS
|
48
46
|
Decodes a MIME attachment into text form. The text will be displayed
|
@@ -99,7 +97,7 @@ EOS
|
|
99
97
|
|
100
98
|
text = case @content_type
|
101
99
|
when /^text\/plain\b/
|
102
|
-
|
100
|
+
@raw_content
|
103
101
|
else
|
104
102
|
HookManager.run "mime-decode", :content_type => content_type,
|
105
103
|
:filename => lambda { write_to_disk },
|
@@ -109,8 +107,8 @@ EOS
|
|
109
107
|
|
110
108
|
@lines = nil
|
111
109
|
if text
|
110
|
+
text = text.transcode(encoded_content.charset || $encoding)
|
112
111
|
@lines = text.gsub("\r\n", "\n").gsub(/\t/, " ").gsub(/\r/, "").split("\n")
|
113
|
-
@lines = lines.map {|l| l.chomp.wrap WRAP_LEN}.flatten
|
114
112
|
@quotable = true
|
115
113
|
end
|
116
114
|
end
|
@@ -132,7 +130,12 @@ EOS
|
|
132
130
|
def initial_state; :open end
|
133
131
|
def viewable?; @lines.nil? end
|
134
132
|
def view_default! path
|
135
|
-
|
133
|
+
case Config::CONFIG['arch']
|
134
|
+
when /darwin/
|
135
|
+
cmd = "open '#{path}'"
|
136
|
+
else
|
137
|
+
cmd = "/usr/bin/run-mailcap --action=view '#{@content_type}:#{path}'"
|
138
|
+
end
|
136
139
|
debug "running: #{cmd.inspect}"
|
137
140
|
BufferManager.shell_out(cmd)
|
138
141
|
$? == 0
|
@@ -162,8 +165,7 @@ EOS
|
|
162
165
|
|
163
166
|
attr_reader :lines
|
164
167
|
def initialize lines
|
165
|
-
@lines = lines
|
166
|
-
|
168
|
+
@lines = lines
|
167
169
|
## trim off all empty lines except one
|
168
170
|
@lines.pop while @lines.length > 1 && @lines[-1] =~ /^\s*$/ && @lines[-2] =~ /^\s*$/
|
169
171
|
end
|
data/lib/sup/message.rb
CHANGED
@@ -31,6 +31,7 @@ class Message
|
|
31
31
|
MAX_SIG_DISTANCE = 15 # lines from the end
|
32
32
|
DEFAULT_SUBJECT = ""
|
33
33
|
DEFAULT_SENDER = "(missing sender)"
|
34
|
+
MAX_HEADER_VALUE_SIZE = 4096
|
34
35
|
|
35
36
|
attr_reader :id, :date, :from, :subj, :refs, :replytos, :to, :source,
|
36
37
|
:cc, :bcc, :labels, :attachments, :list_address, :recipient_email, :replyto,
|
@@ -59,13 +60,15 @@ class Message
|
|
59
60
|
#parse_header(opts[:header] || @source.load_header(@source_info))
|
60
61
|
end
|
61
62
|
|
62
|
-
def
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
63
|
+
def decode_header_field v
|
64
|
+
return unless v
|
65
|
+
return v unless v.is_a? String
|
66
|
+
return unless v.size < MAX_HEADER_VALUE_SIZE # avoid regex blowup on spam
|
67
|
+
Rfc2047.decode_to $encoding, Iconv.easy_decode($encoding, 'ASCII', v)
|
68
|
+
end
|
69
|
+
|
70
|
+
def parse_header encoded_header
|
71
|
+
header = SavingHash.new { |k| decode_header_field encoded_header[k] }
|
69
72
|
|
70
73
|
@id = if header["message-id"]
|
71
74
|
mid = header["message-id"] =~ /<(.+?)>/ ? $1 : header["message-id"]
|
@@ -100,7 +103,7 @@ class Message
|
|
100
103
|
Time.now
|
101
104
|
end
|
102
105
|
|
103
|
-
@subj = header
|
106
|
+
@subj = header["subject"] ? header["subject"].gsub(/\s+/, " ").gsub(/\s+$/, "") : DEFAULT_SUBJECT
|
104
107
|
@to = Person.from_address_list header["to"]
|
105
108
|
@cc = Person.from_address_list header["cc"]
|
106
109
|
@bcc = Person.from_address_list header["bcc"]
|
@@ -114,12 +117,16 @@ class Message
|
|
114
117
|
@replytos = (header["in-reply-to"] || "").scan(/<(.+?)>/).map { |x| sanitize_message_id x.first }
|
115
118
|
|
116
119
|
@replyto = Person.from_address header["reply-to"]
|
117
|
-
@list_address =
|
118
|
-
if header["list-post"]
|
119
|
-
|
120
|
-
|
121
|
-
|
120
|
+
@list_address = if header["list-post"]
|
121
|
+
address = if header["list-post"] =~ /mailto:(.*?)[>\s$]/
|
122
|
+
$1
|
123
|
+
elsif header["list-post"] =~ /@/
|
124
|
+
header["list-post"] # just try the whole fucking thing
|
122
125
|
end
|
126
|
+
address && Person.from_address(address)
|
127
|
+
elsif header["x-mailing-list"]
|
128
|
+
Person.from_address header["x-mailing-list"]
|
129
|
+
end
|
123
130
|
|
124
131
|
@recipient_email = header["envelope-to"] || header["x-original-to"] || header["delivered-to"]
|
125
132
|
@source_marked_read = header["status"] == "RO"
|
@@ -182,11 +189,8 @@ class Message
|
|
182
189
|
## don't tempt me.
|
183
190
|
def sanitize_message_id mid; mid.gsub(/(\s|[^\000-\177])+/, "")[0..254] end
|
184
191
|
|
185
|
-
def
|
186
|
-
return unless @dirty
|
187
|
-
index.update_message_state self
|
192
|
+
def clear_dirty
|
188
193
|
@dirty = false
|
189
|
-
true
|
190
194
|
end
|
191
195
|
|
192
196
|
def has_label? t; @labels.member? t; end
|
@@ -235,8 +239,9 @@ class Message
|
|
235
239
|
## bloat the index.
|
236
240
|
## actually, it's also the differentiation between to/cc/bcc,
|
237
241
|
## so i will keep this.
|
238
|
-
|
239
|
-
|
242
|
+
rmsg = @source.load_message(@source_info)
|
243
|
+
parse_header rmsg.header
|
244
|
+
message_to_chunks rmsg
|
240
245
|
rescue SourceError, SocketError => e
|
241
246
|
warn "problem getting messages from #{@source}: #{e.message}"
|
242
247
|
## we need force_to_top here otherwise this window will cover
|
@@ -442,15 +447,12 @@ private
|
|
442
447
|
from = payload.header.from.first ? payload.header.from.first.format : ""
|
443
448
|
to = payload.header.to.map { |p| p.format }.join(", ")
|
444
449
|
cc = payload.header.cc.map { |p| p.format }.join(", ")
|
445
|
-
subj = payload.header.subject
|
446
|
-
subj =
|
447
|
-
if Rfc2047.is_encoded? subj
|
448
|
-
subj = Rfc2047.decode_to $encoding, subj
|
449
|
-
end
|
450
|
+
subj = decode_header_field(payload.header.subject) || DEFAULT_SUBJECT
|
451
|
+
subj = Message.normalize_subj(subj.gsub(/\s+/, " ").gsub(/\s+$/, ""))
|
450
452
|
msgdate = payload.header.date
|
451
|
-
from_person = from ? Person.from_address(from) : nil
|
452
|
-
to_people = to ? Person.from_address_list(to) : nil
|
453
|
-
cc_people = cc ? Person.from_address_list(cc) : nil
|
453
|
+
from_person = from ? Person.from_address(decode_header_field from) : nil
|
454
|
+
to_people = to ? Person.from_address_list(decode_header_field to) : nil
|
455
|
+
cc_people = cc ? Person.from_address_list(decode_header_field cc) : nil
|
454
456
|
[Chunk::EnclosedMessage.new(from_person, to_people, cc_people, msgdate, subj)] + message_to_chunks(payload, encrypted)
|
455
457
|
else
|
456
458
|
debug "no body for message/rfc822 enclosure; skipping"
|
data/lib/sup/mode.rb
CHANGED
@@ -74,15 +74,22 @@ EOS
|
|
74
74
|
|
75
75
|
### helper functions
|
76
76
|
|
77
|
-
def save_to_file fn
|
77
|
+
def save_to_file fn, talk=true
|
78
78
|
if File.exists? fn
|
79
|
-
|
79
|
+
unless BufferManager.ask_yes_or_no "File \"#{fn}\" exists. Overwrite?"
|
80
|
+
info "Not overwriting #{fn}"
|
81
|
+
return
|
82
|
+
end
|
80
83
|
end
|
81
84
|
begin
|
82
85
|
File.open(fn, "w") { |f| yield f }
|
83
|
-
BufferManager.flash "Successfully wrote #{fn}."
|
86
|
+
BufferManager.flash "Successfully wrote #{fn}." if talk
|
87
|
+
true
|
84
88
|
rescue SystemCallError, IOError => e
|
85
|
-
|
89
|
+
m = "Error writing file: #{e.message}"
|
90
|
+
info m
|
91
|
+
BufferManager.flash m
|
92
|
+
false
|
86
93
|
end
|
87
94
|
end
|
88
95
|
|
@@ -4,6 +4,7 @@ class BufferListMode < LineCursorMode
|
|
4
4
|
register_keymap do |k|
|
5
5
|
k.add :jump_to_buffer, "Jump to selected buffer", :enter
|
6
6
|
k.add :reload, "Reload buffer list", "@"
|
7
|
+
k.add :kill_selected_buffer, "Kill selected buffer", "X"
|
7
8
|
end
|
8
9
|
|
9
10
|
def initialize
|
@@ -40,6 +41,10 @@ protected
|
|
40
41
|
def jump_to_buffer
|
41
42
|
BufferManager.raise_to_front @bufs[curpos][1]
|
42
43
|
end
|
44
|
+
|
45
|
+
def kill_selected_buffer
|
46
|
+
reload if BufferManager.kill_buffer_safely @bufs[curpos][1]
|
47
|
+
end
|
43
48
|
end
|
44
49
|
|
45
50
|
end
|
@@ -21,6 +21,10 @@ class Console
|
|
21
21
|
|
22
22
|
def xapian; Index.instance.instance_variable_get :@xapian; end
|
23
23
|
def ferret; Index.instance.instance_variable_get :@index; end
|
24
|
+
|
25
|
+
def loglevel; Redwood::Logger.level; end
|
26
|
+
def set_loglevel(level); Redwood::Logger.level = level; end
|
27
|
+
|
24
28
|
def special_methods; methods - Object.methods end
|
25
29
|
|
26
30
|
## files that won't cause problems when reloaded
|
@@ -162,8 +162,10 @@ EOS
|
|
162
162
|
fn = BufferManager.ask_for_filename :attachment, "File name (enter for browser): "
|
163
163
|
return unless fn
|
164
164
|
begin
|
165
|
-
|
166
|
-
|
165
|
+
Dir[fn].each do |f|
|
166
|
+
@attachments << RMail::Message.make_file_attachment(f)
|
167
|
+
@attachment_names << f
|
168
|
+
end
|
167
169
|
update
|
168
170
|
rescue SystemCallError => e
|
169
171
|
BufferManager.flash "Can't read #{fn}: #{e.message}"
|
data/lib/sup/modes/inbox-mode.rb
CHANGED
@@ -7,6 +7,7 @@ class InboxMode < ThreadIndexMode
|
|
7
7
|
## overwrite toggle_archived with archive
|
8
8
|
k.add :archive, "Archive thread (remove from inbox)", 'a'
|
9
9
|
k.add :read_and_archive, "Archive thread (remove from inbox) and mark read", 'A'
|
10
|
+
k.add :refine_search, "Refine search", '|'
|
10
11
|
end
|
11
12
|
|
12
13
|
def initialize
|
@@ -17,6 +18,13 @@ class InboxMode < ThreadIndexMode
|
|
17
18
|
|
18
19
|
def is_relevant? m; (m.labels & [:spam, :deleted, :killed, :inbox]) == Set.new([:inbox]) end
|
19
20
|
|
21
|
+
def refine_search
|
22
|
+
text = BufferManager.ask :search, "refine inbox with query: "
|
23
|
+
return unless text && text !~ /^\s*$/
|
24
|
+
text = "label:inbox -label:spam -label:deleted " + text
|
25
|
+
SearchResultsMode.spawn_from_query text
|
26
|
+
end
|
27
|
+
|
20
28
|
## label-list-mode wants to be able to raise us if the user selects
|
21
29
|
## the "inbox" label, so we need to keep our singletonness around
|
22
30
|
def self.instance; @@instance; end
|
@@ -29,11 +37,13 @@ class InboxMode < ThreadIndexMode
|
|
29
37
|
UndoManager.register "archiving thread" do
|
30
38
|
thread.apply_label :inbox
|
31
39
|
add_or_unhide thread.first
|
40
|
+
Index.save_thread thread
|
32
41
|
end
|
33
42
|
|
34
43
|
cursor_thread.remove_label :inbox
|
35
44
|
hide_thread cursor_thread
|
36
45
|
regen_text
|
46
|
+
Index.save_thread thread
|
37
47
|
end
|
38
48
|
|
39
49
|
def multi_archive threads
|
@@ -41,6 +51,7 @@ class InboxMode < ThreadIndexMode
|
|
41
51
|
threads.map do |t|
|
42
52
|
t.apply_label :inbox
|
43
53
|
add_or_unhide t.first
|
54
|
+
Index.save_thread t
|
44
55
|
end
|
45
56
|
regen_text
|
46
57
|
end
|
@@ -50,22 +61,26 @@ class InboxMode < ThreadIndexMode
|
|
50
61
|
hide_thread t
|
51
62
|
end
|
52
63
|
regen_text
|
64
|
+
threads.each { |t| Index.save_thread t }
|
53
65
|
end
|
54
66
|
|
55
67
|
def read_and_archive
|
56
68
|
return unless cursor_thread
|
57
69
|
thread = cursor_thread # to make sure lambda only knows about 'old' cursor_thread
|
58
70
|
|
71
|
+
was_unread = thread.labels.member? :unread
|
59
72
|
UndoManager.register "reading and archiving thread" do
|
60
73
|
thread.apply_label :inbox
|
61
|
-
thread.apply_label :unread
|
74
|
+
thread.apply_label :unread if was_unread
|
62
75
|
add_or_unhide thread.first
|
76
|
+
Index.save_thread thread
|
63
77
|
end
|
64
78
|
|
65
79
|
cursor_thread.remove_label :unread
|
66
80
|
cursor_thread.remove_label :inbox
|
67
81
|
hide_thread cursor_thread
|
68
82
|
regen_text
|
83
|
+
Index.save_thread thread
|
69
84
|
end
|
70
85
|
|
71
86
|
def multi_read_and_archive threads
|
@@ -82,10 +97,12 @@ class InboxMode < ThreadIndexMode
|
|
82
97
|
threads.zip(old_labels).each do |t, l|
|
83
98
|
t.labels = l
|
84
99
|
add_or_unhide t.first
|
100
|
+
Index.save_thread t
|
85
101
|
end
|
86
102
|
regen_text
|
87
103
|
end
|
88
104
|
|
105
|
+
threads.each { |t| Index.save_thread t }
|
89
106
|
end
|
90
107
|
|
91
108
|
def handle_unarchived_update sender, m
|
@@ -8,14 +8,38 @@ class LabelListMode < LineCursorMode
|
|
8
8
|
k.add :toggle_show_unread_only, "Toggle between showing all labels and those with unread mail", 'u'
|
9
9
|
end
|
10
10
|
|
11
|
+
HookManager.register "label-list-filter", <<EOS
|
12
|
+
Filter the label list, typically to sort.
|
13
|
+
Variables:
|
14
|
+
counted: an array of counted labels.
|
15
|
+
Return value:
|
16
|
+
An array of counted labels with sort_by output structure.
|
17
|
+
EOS
|
18
|
+
|
19
|
+
HookManager.register "label-list-format", <<EOS
|
20
|
+
Create the sprintf format string for label-list-mode.
|
21
|
+
Variables:
|
22
|
+
width: the maximum label width
|
23
|
+
tmax: the maximum total message count
|
24
|
+
umax: the maximum unread message count
|
25
|
+
Return value:
|
26
|
+
A format string for sprintf
|
27
|
+
EOS
|
28
|
+
|
11
29
|
def initialize
|
12
30
|
@labels = []
|
13
31
|
@text = []
|
14
32
|
@unread_only = false
|
15
33
|
super
|
34
|
+
UpdateManager.register self
|
16
35
|
regen_text
|
17
36
|
end
|
18
37
|
|
38
|
+
def cleanup
|
39
|
+
UpdateManager.unregister self
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
19
43
|
def lines; @text.length end
|
20
44
|
def [] i; @text[i] end
|
21
45
|
|
@@ -34,6 +58,10 @@ class LabelListMode < LineCursorMode
|
|
34
58
|
reload # make sure unread message counts are up-to-date
|
35
59
|
end
|
36
60
|
|
61
|
+
def handle_added_update sender, m
|
62
|
+
reload
|
63
|
+
end
|
64
|
+
|
37
65
|
protected
|
38
66
|
|
39
67
|
def toggle_show_unread_only
|
@@ -50,14 +78,22 @@ protected
|
|
50
78
|
@text = []
|
51
79
|
labels = LabelManager.all_labels
|
52
80
|
|
53
|
-
|
81
|
+
counted = labels.map do |label|
|
54
82
|
string = LabelManager.string_for label
|
55
83
|
total = Index.num_results_for :label => label
|
56
84
|
unread = (label == :unread)? total : Index.num_results_for(:labels => [label, :unread])
|
57
85
|
[label, string, total, unread]
|
58
|
-
end
|
86
|
+
end
|
87
|
+
|
88
|
+
if HookManager.enabled? "label-list-filter"
|
89
|
+
counts = HookManager.run "label-list-filter", :counted => counted
|
90
|
+
else
|
91
|
+
counts = counted.sort_by { |l, s, t, u| s.downcase }
|
92
|
+
end
|
59
93
|
|
60
94
|
width = counts.max_of { |l, s, t, u| s.length }
|
95
|
+
tmax = counts.max_of { |l, s, t, u| t }
|
96
|
+
umax = counts.max_of { |l, s, t, u| u }
|
61
97
|
|
62
98
|
if @unread_only
|
63
99
|
counts.delete_if { | l, s, t, u | u == 0 }
|
@@ -78,8 +114,13 @@ protected
|
|
78
114
|
next
|
79
115
|
end
|
80
116
|
|
117
|
+
fmt = HookManager.run "label-list-format", :width => width, :tmax => tmax, :umax => umax
|
118
|
+
if !fmt
|
119
|
+
fmt = "%#{width + 1}s %5d %s, %5d unread"
|
120
|
+
end
|
121
|
+
|
81
122
|
@text << [[(unread == 0 ? :labellist_old_color : :labellist_new_color),
|
82
|
-
sprintf(
|
123
|
+
sprintf(fmt, string, total, total == 1 ? " message" : "messages", unread)]]
|
83
124
|
@labels << [label, unread]
|
84
125
|
yield i if block_given?
|
85
126
|
end.compact
|