sup 0.10.2 → 0.11
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 +11 -9
- data/History.txt +14 -0
- data/README.txt +3 -11
- data/ReleaseNotes +16 -0
- data/bin/sup +67 -42
- data/bin/sup-add +2 -20
- data/bin/sup-config +0 -34
- data/bin/sup-dump +2 -5
- data/bin/sup-sync +2 -3
- data/bin/sup-sync-back +2 -3
- data/bin/sup-tweak-labels +2 -3
- data/lib/sup.rb +12 -4
- data/lib/sup/account.rb +2 -0
- data/lib/sup/buffer.rb +11 -2
- data/lib/sup/colormap.rb +59 -49
- data/lib/sup/connection.rb +63 -0
- data/lib/sup/crypto.rb +12 -0
- data/lib/sup/hook.rb +1 -0
- data/lib/sup/idle.rb +42 -0
- data/lib/sup/index.rb +562 -47
- data/lib/sup/keymap.rb +41 -3
- data/lib/sup/message.rb +1 -1
- data/lib/sup/mode.rb +8 -0
- data/lib/sup/modes/console-mode.rb +2 -3
- data/lib/sup/modes/edit-message-mode.rb +32 -7
- data/lib/sup/modes/inbox-mode.rb +4 -0
- data/lib/sup/modes/search-list-mode.rb +188 -0
- data/lib/sup/modes/search-results-mode.rb +17 -1
- data/lib/sup/modes/thread-index-mode.rb +43 -10
- data/lib/sup/modes/thread-view-mode.rb +29 -4
- data/lib/sup/poll.rb +13 -2
- data/lib/sup/search.rb +73 -0
- data/lib/sup/textfield.rb +17 -12
- data/lib/sup/util.rb +11 -0
- metadata +45 -46
- data/bin/sup-convert-ferret-index +0 -84
- data/lib/ncurses.rb +0 -289
- data/lib/sup/ferret_index.rb +0 -476
- data/lib/sup/xapian_index.rb +0 -605
data/lib/sup/keymap.rb
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
module Redwood
|
2
2
|
|
3
3
|
class Keymap
|
4
|
+
|
5
|
+
HookManager.register "keybindings", <<EOS
|
6
|
+
Add custom keybindings.
|
7
|
+
Methods:
|
8
|
+
modes: Hash from mode names to mode classes.
|
9
|
+
global_keymap: The top-level keymap.
|
10
|
+
EOS
|
11
|
+
|
4
12
|
def initialize
|
5
13
|
@map = {}
|
6
14
|
@order = []
|
@@ -60,10 +68,31 @@ class Keymap
|
|
60
68
|
end
|
61
69
|
end
|
62
70
|
|
71
|
+
def delete k
|
72
|
+
kc = Keymap.keysym_to_keycode(k)
|
73
|
+
return unless @map.member? kc
|
74
|
+
entry = @map.delete kc
|
75
|
+
keys = entry[2]
|
76
|
+
keys.delete k
|
77
|
+
@order.delete entry if keys.empty?
|
78
|
+
end
|
79
|
+
|
80
|
+
def add! action, help, *keys
|
81
|
+
keys.each { |k| delete k }
|
82
|
+
add action, help, *keys
|
83
|
+
end
|
84
|
+
|
63
85
|
def add_multi prompt, key
|
64
|
-
|
65
|
-
|
66
|
-
|
86
|
+
kc = Keymap.keysym_to_keycode(key)
|
87
|
+
if @map.member? kc
|
88
|
+
action = @map[kc].first
|
89
|
+
raise "existing action is not a keymap" unless action.is_a?(Keymap)
|
90
|
+
yield action
|
91
|
+
else
|
92
|
+
submap = Keymap.new
|
93
|
+
add submap, prompt, key
|
94
|
+
yield submap
|
95
|
+
end
|
67
96
|
end
|
68
97
|
|
69
98
|
def action_for kc
|
@@ -95,6 +124,15 @@ class Keymap
|
|
95
124
|
llen = lines.max_of { |a, b| a.length }
|
96
125
|
lines.map { |a, b| sprintf " %#{llen}s : %s", a, b }.join("\n")
|
97
126
|
end
|
127
|
+
|
128
|
+
def self.run_hook global_keymap
|
129
|
+
modes = Hash[Mode.keymaps.map { |klass,keymap| [Mode.make_name(klass.name),klass] }]
|
130
|
+
locals = {
|
131
|
+
:modes => modes,
|
132
|
+
:global_keymap => global_keymap,
|
133
|
+
}
|
134
|
+
HookManager.run 'keybindings', locals
|
135
|
+
end
|
98
136
|
end
|
99
137
|
|
100
138
|
end
|
data/lib/sup/message.rb
CHANGED
@@ -178,7 +178,7 @@ class Message
|
|
178
178
|
|
179
179
|
## sanitize message ids by removing spaces and non-ascii characters.
|
180
180
|
## also, truncate to 255 characters. all these steps are necessary
|
181
|
-
## to make
|
181
|
+
## to make the index happy. of course, we probably fuck up a couple
|
182
182
|
## valid message ids as well. as long as we're consistent, this
|
183
183
|
## should be fine, though.
|
184
184
|
##
|
data/lib/sup/mode.rb
CHANGED
@@ -20,7 +20,6 @@ class Console
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def xapian; Index.instance.instance_variable_get :@xapian; end
|
23
|
-
def ferret; Index.instance.instance_variable_get :@index; end
|
24
23
|
|
25
24
|
def loglevel; Redwood::Logger.level; end
|
26
25
|
def set_loglevel(level); Redwood::Logger.level = level; end
|
@@ -29,7 +28,7 @@ class Console
|
|
29
28
|
|
30
29
|
## files that won't cause problems when reloaded
|
31
30
|
## TODO expand this list / convert to blacklist
|
32
|
-
RELOAD_WHITELIST = %w(sup/
|
31
|
+
RELOAD_WHITELIST = %w(sup/index.rb sup/modes/console-mode.rb)
|
33
32
|
|
34
33
|
def reload
|
35
34
|
old_verbose = $VERBOSE
|
@@ -69,7 +68,7 @@ class ConsoleMode < LogMode
|
|
69
68
|
end
|
70
69
|
|
71
70
|
def initialize
|
72
|
-
super
|
71
|
+
super "console"
|
73
72
|
@console = Console.new self
|
74
73
|
@binding = @console.instance_eval { binding }
|
75
74
|
end
|
@@ -3,10 +3,6 @@ require 'socket' # just for gethostname!
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'rmail'
|
5
5
|
|
6
|
-
# from jcode.rb, not included in ruby 1.9
|
7
|
-
PATTERN_UTF8 = '[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf]'
|
8
|
-
RE_UTF8 = Regexp.new(PATTERN_UTF8, 0, 'n')
|
9
|
-
|
10
6
|
module Redwood
|
11
7
|
|
12
8
|
class SendmailCommandFailed < StandardError; end
|
@@ -40,6 +36,28 @@ Return value:
|
|
40
36
|
none
|
41
37
|
EOS
|
42
38
|
|
39
|
+
HookManager.register "mentions-attachments", <<EOS
|
40
|
+
Detects if given message mentions attachments the way it is probable
|
41
|
+
that there should be files attached to the message.
|
42
|
+
Variables:
|
43
|
+
header: a hash of headers. See 'signature' hook for documentation.
|
44
|
+
body: an array of lines of body text.
|
45
|
+
Return value:
|
46
|
+
True if attachments are mentioned.
|
47
|
+
EOS
|
48
|
+
|
49
|
+
HookManager.register "crypto-mode", <<EOS
|
50
|
+
Modifies cryptography settings based on header and message content, before
|
51
|
+
editing a new message. This can be used to set, for example, default cryptography
|
52
|
+
settings.
|
53
|
+
Variables:
|
54
|
+
header: a hash of headers. See 'signature' hook for documentation.
|
55
|
+
body: an array of lines of body text.
|
56
|
+
crypto_selector: the UI element that controls the current cryptography setting.
|
57
|
+
Return value:
|
58
|
+
none
|
59
|
+
EOS
|
60
|
+
|
43
61
|
attr_reader :status
|
44
62
|
attr_accessor :body, :header
|
45
63
|
bool_reader :edited
|
@@ -92,6 +110,9 @@ EOS
|
|
92
110
|
add_selector @crypto_selector if @crypto_selector
|
93
111
|
|
94
112
|
HookManager.run "before-edit", :header => @header, :body => @body
|
113
|
+
if @crypto_selector
|
114
|
+
HookManager.run "crypto-mode", :header => @header, :body => @body, :crypto_selector => @crypto_selector
|
115
|
+
end
|
95
116
|
|
96
117
|
super opts
|
97
118
|
regen_text
|
@@ -193,7 +214,7 @@ protected
|
|
193
214
|
end
|
194
215
|
|
195
216
|
def mime_encode_subject string
|
196
|
-
return string
|
217
|
+
return string if string.ascii_only?
|
197
218
|
mime_encode string
|
198
219
|
end
|
199
220
|
|
@@ -202,7 +223,7 @@ protected
|
|
202
223
|
# Encode "bælammet mitt <user@example.com>" into
|
203
224
|
# "=?utf-8?q?b=C3=A6lammet_mitt?= <user@example.com>
|
204
225
|
def mime_encode_address string
|
205
|
-
return string
|
226
|
+
return string if string.ascii_only?
|
206
227
|
string.sub(RE_ADDRESS) { |match| mime_encode($1) + $2 }
|
207
228
|
end
|
208
229
|
|
@@ -444,7 +465,11 @@ private
|
|
444
465
|
end
|
445
466
|
|
446
467
|
def mentions_attachments?
|
447
|
-
|
468
|
+
if HookManager.enabled? "mentions-attachments"
|
469
|
+
HookManager.run "mentions-attachments", :header => @header, :body => @body
|
470
|
+
else
|
471
|
+
@body.any? { |l| l =~ /^[^>]/ && l =~ /\battach(ment|ed|ing|)\b/i }
|
472
|
+
end
|
448
473
|
end
|
449
474
|
|
450
475
|
def top_posting?
|
data/lib/sup/modes/inbox-mode.rb
CHANGED
@@ -0,0 +1,188 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class SearchListMode < LineCursorMode
|
4
|
+
register_keymap do |k|
|
5
|
+
k.add :select_search, "Open search results", :enter
|
6
|
+
k.add :reload, "Discard saved search list and reload", '@'
|
7
|
+
k.add :jump_to_next_new, "Jump to next new thread", :tab
|
8
|
+
k.add :toggle_show_unread_only, "Toggle between showing all saved searches and those with unread mail", 'u'
|
9
|
+
k.add :delete_selected_search, "Delete selected search", "X"
|
10
|
+
k.add :rename_selected_search, "Rename selected search", "r"
|
11
|
+
k.add :edit_selected_search, "Edit selected search", "e"
|
12
|
+
k.add :add_new_search, "Add new search", "a"
|
13
|
+
end
|
14
|
+
|
15
|
+
HookManager.register "search-list-filter", <<EOS
|
16
|
+
Filter the search list, typically to sort.
|
17
|
+
Variables:
|
18
|
+
counted: an array of counted searches.
|
19
|
+
Return value:
|
20
|
+
An array of counted searches with sort_by output structure.
|
21
|
+
EOS
|
22
|
+
|
23
|
+
HookManager.register "search-list-format", <<EOS
|
24
|
+
Create the sprintf format string for search-list-mode.
|
25
|
+
Variables:
|
26
|
+
n_width: the maximum search name width
|
27
|
+
tmax: the maximum total message count
|
28
|
+
umax: the maximum unread message count
|
29
|
+
s_width: the maximum search string width
|
30
|
+
Return value:
|
31
|
+
A format string for sprintf
|
32
|
+
EOS
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@searches = []
|
36
|
+
@text = []
|
37
|
+
@unread_only = false
|
38
|
+
super
|
39
|
+
UpdateManager.register self
|
40
|
+
regen_text
|
41
|
+
end
|
42
|
+
|
43
|
+
def cleanup
|
44
|
+
UpdateManager.unregister self
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
def lines; @text.length end
|
49
|
+
def [] i; @text[i] end
|
50
|
+
|
51
|
+
def jump_to_next_new
|
52
|
+
n = ((curpos + 1) ... lines).find { |i| @searches[i][1] > 0 } || (0 ... curpos).find { |i| @searches[i][1] > 0 }
|
53
|
+
if n
|
54
|
+
## jump there if necessary
|
55
|
+
jump_to_line n unless n >= topline && n < botline
|
56
|
+
set_cursor_pos n
|
57
|
+
else
|
58
|
+
BufferManager.flash "No saved searches with unread messages."
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def focus
|
63
|
+
reload # make sure unread message counts are up-to-date
|
64
|
+
end
|
65
|
+
|
66
|
+
def handle_added_update sender, m
|
67
|
+
reload
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
def toggle_show_unread_only
|
73
|
+
@unread_only = !@unread_only
|
74
|
+
reload
|
75
|
+
end
|
76
|
+
|
77
|
+
def reload
|
78
|
+
regen_text
|
79
|
+
buffer.mark_dirty if buffer
|
80
|
+
end
|
81
|
+
|
82
|
+
def regen_text
|
83
|
+
@text = []
|
84
|
+
searches = SearchManager.all_searches
|
85
|
+
|
86
|
+
counted = searches.map do |name|
|
87
|
+
search_string = SearchManager.search_string_for name
|
88
|
+
begin
|
89
|
+
query = Index.parse_query search_string
|
90
|
+
total = Index.num_results_for :qobj => query[:qobj]
|
91
|
+
unread = Index.num_results_for :qobj => query[:qobj], :label => :unread
|
92
|
+
rescue Index::ParseError => e
|
93
|
+
BufferManager.flash "Problem: #{e.message}!"
|
94
|
+
total = 0
|
95
|
+
unread = 0
|
96
|
+
end
|
97
|
+
[name, search_string, total, unread]
|
98
|
+
end
|
99
|
+
|
100
|
+
if HookManager.enabled? "search-list-filter"
|
101
|
+
counts = HookManager.run "search-list-filter", :counted => counted
|
102
|
+
else
|
103
|
+
counts = counted.sort_by { |n, s, t, u| n.downcase }
|
104
|
+
end
|
105
|
+
|
106
|
+
n_width = counts.max_of { |n, s, t, u| n.length }
|
107
|
+
tmax = counts.max_of { |n, s, t, u| t }
|
108
|
+
umax = counts.max_of { |n, s, t, u| u }
|
109
|
+
s_width = counts.max_of { |n, s, t, u| s.length }
|
110
|
+
|
111
|
+
if @unread_only
|
112
|
+
counts.delete_if { | n, s, t, u | u == 0 }
|
113
|
+
end
|
114
|
+
|
115
|
+
@searches = []
|
116
|
+
counts.each do |name, search_string, total, unread|
|
117
|
+
fmt = HookManager.run "search-list-format", :n_width => n_width, :tmax => tmax, :umax => umax, :s_width => s_width
|
118
|
+
if !fmt
|
119
|
+
fmt = "%#{n_width + 1}s %5d %s, %5d unread: %s"
|
120
|
+
end
|
121
|
+
@text << [[(unread == 0 ? :labellist_old_color : :labellist_new_color),
|
122
|
+
sprintf(fmt, name, total, total == 1 ? " message" : "messages", unread, search_string)]]
|
123
|
+
@searches << [name, unread]
|
124
|
+
end
|
125
|
+
|
126
|
+
BufferManager.flash "No saved searches with unread messages!" if counts.empty? && @unread_only
|
127
|
+
end
|
128
|
+
|
129
|
+
def select_search
|
130
|
+
name, num_unread = @searches[curpos]
|
131
|
+
return unless name
|
132
|
+
SearchResultsMode.spawn_from_query SearchManager.search_string_for(name)
|
133
|
+
end
|
134
|
+
|
135
|
+
def delete_selected_search
|
136
|
+
name, num_unread = @searches[curpos]
|
137
|
+
return unless name
|
138
|
+
reload if SearchManager.delete name
|
139
|
+
end
|
140
|
+
|
141
|
+
def rename_selected_search
|
142
|
+
old_name, num_unread = @searches[curpos]
|
143
|
+
return unless old_name
|
144
|
+
new_name = BufferManager.ask :save_search, "Rename this saved search: ", old_name
|
145
|
+
return unless new_name && new_name !~ /^\s*$/ && new_name != old_name
|
146
|
+
new_name.strip!
|
147
|
+
unless SearchManager.valid_name? new_name
|
148
|
+
BufferManager.flash "Not renamed: " + SearchManager.name_format_hint
|
149
|
+
return
|
150
|
+
end
|
151
|
+
if SearchManager.all_searches.include? new_name
|
152
|
+
BufferManager.flash "Not renamed: \"#{new_name}\" already exists"
|
153
|
+
return
|
154
|
+
end
|
155
|
+
reload if SearchManager.rename old_name, new_name
|
156
|
+
set_cursor_pos @searches.index([new_name, num_unread])||curpos
|
157
|
+
end
|
158
|
+
|
159
|
+
def edit_selected_search
|
160
|
+
name, num_unread = @searches[curpos]
|
161
|
+
return unless name
|
162
|
+
old_search_string = SearchManager.search_string_for name
|
163
|
+
new_search_string = BufferManager.ask :search, "Edit this saved search: ", (old_search_string + " ")
|
164
|
+
return unless new_search_string && new_search_string !~ /^\s*$/ && new_search_string != old_search_string
|
165
|
+
reload if SearchManager.edit name, new_search_string.strip
|
166
|
+
set_cursor_pos @searches.index([name, num_unread])||curpos
|
167
|
+
end
|
168
|
+
|
169
|
+
def add_new_search
|
170
|
+
search_string = BufferManager.ask :search, "New search: "
|
171
|
+
return unless search_string && search_string !~ /^\s*$/
|
172
|
+
name = BufferManager.ask :save_search, "Name this search: "
|
173
|
+
return unless name && name !~ /^\s*$/
|
174
|
+
name.strip!
|
175
|
+
unless SearchManager.valid_name? name
|
176
|
+
BufferManager.flash "Not saved: " + SearchManager.name_format_hint
|
177
|
+
return
|
178
|
+
end
|
179
|
+
if SearchManager.all_searches.include? name
|
180
|
+
BufferManager.flash "Not saved: \"#{name}\" already exists"
|
181
|
+
return
|
182
|
+
end
|
183
|
+
reload if SearchManager.add name, search_string.strip
|
184
|
+
set_cursor_pos @searches.index(@searches.assoc(name))||curpos
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
@@ -8,6 +8,7 @@ class SearchResultsMode < ThreadIndexMode
|
|
8
8
|
|
9
9
|
register_keymap do |k|
|
10
10
|
k.add :refine_search, "Refine search", '|'
|
11
|
+
k.add :save_search, "Save search", '%'
|
11
12
|
end
|
12
13
|
|
13
14
|
def refine_search
|
@@ -16,7 +17,22 @@ class SearchResultsMode < ThreadIndexMode
|
|
16
17
|
SearchResultsMode.spawn_from_query text
|
17
18
|
end
|
18
19
|
|
19
|
-
|
20
|
+
def save_search
|
21
|
+
name = BufferManager.ask :save_search, "Name this search: "
|
22
|
+
return unless name && name !~ /^\s*$/
|
23
|
+
name.strip!
|
24
|
+
unless SearchManager.valid_name? name
|
25
|
+
BufferManager.flash "Not saved: " + SearchManager.name_format_hint
|
26
|
+
return
|
27
|
+
end
|
28
|
+
if SearchManager.all_searches.include? name
|
29
|
+
BufferManager.flash "Not saved: \"#{name}\" already exists"
|
30
|
+
return
|
31
|
+
end
|
32
|
+
BufferManager.flash "Search saved as \"#{name}\"" if SearchManager.add name, @query[:text].strip
|
33
|
+
end
|
34
|
+
|
35
|
+
## a proper is_relevant? method requires some way of asking the index
|
20
36
|
## if an in-memory object satisfies a query. i'm not sure how to do
|
21
37
|
## that yet. in the worst case i can make an in-memory index, add
|
22
38
|
## the message, and search against it to see if i have > 0 results,
|
@@ -12,6 +12,12 @@ class ThreadIndexMode < LineCursorMode
|
|
12
12
|
|
13
13
|
HookManager.register "index-mode-size-widget", <<EOS
|
14
14
|
Generates the per-thread size widget for each thread.
|
15
|
+
Variables:
|
16
|
+
thread: The message thread to be formatted.
|
17
|
+
EOS
|
18
|
+
|
19
|
+
HookManager.register "index-mode-date-widget", <<EOS
|
20
|
+
Generates the per-thread date widget for each thread.
|
15
21
|
Variables:
|
16
22
|
thread: The message thread to be formatted.
|
17
23
|
EOS
|
@@ -53,10 +59,12 @@ EOS
|
|
53
59
|
def initialize hidden_labels=[], load_thread_opts={}
|
54
60
|
super()
|
55
61
|
@mutex = Mutex.new # covers the following variables:
|
56
|
-
@threads =
|
62
|
+
@threads = []
|
57
63
|
@hidden_threads = {}
|
58
64
|
@size_widget_width = nil
|
59
|
-
@size_widgets =
|
65
|
+
@size_widgets = []
|
66
|
+
@date_widget_width = nil
|
67
|
+
@date_widgets = []
|
60
68
|
@tags = Tagger.new self
|
61
69
|
|
62
70
|
## these guys, and @text and @lines, are not covered
|
@@ -111,7 +119,7 @@ EOS
|
|
111
119
|
mode = ThreadViewMode.new t, @hidden_labels, self
|
112
120
|
BufferManager.spawn t.subj, mode
|
113
121
|
BufferManager.draw_screen
|
114
|
-
mode.jump_to_first_open
|
122
|
+
mode.jump_to_first_open if $config[:jump_to_open_message]
|
115
123
|
BufferManager.draw_screen # lame TODO: make this unnecessary
|
116
124
|
## the first draw_screen is needed before topline and botline
|
117
125
|
## are set, and the second to show the cursor having moved
|
@@ -226,6 +234,8 @@ EOS
|
|
226
234
|
@threads = @ts.threads.select { |t| !@hidden_threads[t] }.sort_by { |t| [t.date, t.first.id] }.reverse
|
227
235
|
@size_widgets = @threads.map { |t| size_widget_for_thread t }
|
228
236
|
@size_widget_width = @size_widgets.max_of { |w| w.display_length }
|
237
|
+
@date_widgets = @threads.map { |t| date_widget_for_thread t }
|
238
|
+
@date_widget_width = @date_widgets.max_of { |w| w.display_length }
|
229
239
|
end
|
230
240
|
set_cursor_pos @threads.index(old_cursor_thread)||curpos
|
231
241
|
|
@@ -651,7 +661,7 @@ EOS
|
|
651
661
|
if (l = lines) == 0
|
652
662
|
"line 0 of 0"
|
653
663
|
else
|
654
|
-
"line #{curpos + 1} of #{l}
|
664
|
+
"line #{curpos + 1} of #{l}"
|
655
665
|
end
|
656
666
|
end
|
657
667
|
|
@@ -719,6 +729,10 @@ protected
|
|
719
729
|
HookManager.run("index-mode-size-widget", :thread => t) || default_size_widget_for(t)
|
720
730
|
end
|
721
731
|
|
732
|
+
def date_widget_for_thread t
|
733
|
+
HookManager.run("index-mode-date-widget", :thread => t) || default_date_widget_for(t)
|
734
|
+
end
|
735
|
+
|
722
736
|
def cursor_thread; @mutex.synchronize { @threads[curpos] }; end
|
723
737
|
|
724
738
|
def drop_all_threads
|
@@ -734,6 +748,7 @@ protected
|
|
734
748
|
@hidden_threads[t] = true
|
735
749
|
@threads.delete_at i
|
736
750
|
@size_widgets.delete_at i
|
751
|
+
@date_widgets.delete_at i
|
737
752
|
@tags.drop_tag_for t
|
738
753
|
end
|
739
754
|
end
|
@@ -745,9 +760,12 @@ protected
|
|
745
760
|
|
746
761
|
@mutex.synchronize do
|
747
762
|
@size_widgets[l] = size_widget_for_thread @threads[l]
|
763
|
+
@date_widgets[l] = date_widget_for_thread @threads[l]
|
748
764
|
|
749
|
-
## if
|
750
|
-
need_update =
|
765
|
+
## if a widget size has increased, we need to redraw everyone
|
766
|
+
need_update =
|
767
|
+
(@size_widgets[l].size > @size_widget_width) or
|
768
|
+
(@date_widgets[l].size > @date_widget_width)
|
751
769
|
end
|
752
770
|
|
753
771
|
if need_update
|
@@ -793,14 +811,24 @@ protected
|
|
793
811
|
result << [name, new[a]]
|
794
812
|
end
|
795
813
|
|
814
|
+
if result.size == 1 && (author_and_newness = result.assoc("me"))
|
815
|
+
unless (recipients = t.participants - t.authors).empty?
|
816
|
+
result = recipients.collect do |r|
|
817
|
+
break if limit && result.size >= limit
|
818
|
+
name = (recipients.size == 1) ? r.mediumname : r.shortname
|
819
|
+
["(#{name})", author_and_newness[1]]
|
820
|
+
end
|
821
|
+
end
|
822
|
+
end
|
823
|
+
|
796
824
|
result
|
797
825
|
end
|
798
826
|
|
799
827
|
AUTHOR_LIMIT = 5
|
800
828
|
def text_for_thread_at line
|
801
|
-
t, size_widget = @mutex.synchronize
|
802
|
-
|
803
|
-
|
829
|
+
t, size_widget, date_widget = @mutex.synchronize do
|
830
|
+
[@threads[line], @size_widgets[line], @date_widgets[line]]
|
831
|
+
end
|
804
832
|
|
805
833
|
starred = t.has_label? :starred
|
806
834
|
|
@@ -851,10 +879,11 @@ protected
|
|
851
879
|
snippet = t.snippet + (t.snippet.empty? ? "" : "...")
|
852
880
|
|
853
881
|
size_widget_text = sprintf "%#{ @size_widget_width}s", size_widget
|
882
|
+
date_widget_text = sprintf "%#{ @date_widget_width}s", date_widget
|
854
883
|
|
855
884
|
[
|
856
885
|
[:tagged_color, @tags.tagged?(t) ? ">" : " "],
|
857
|
-
[:date_color,
|
886
|
+
[:date_color, date_widget_text],
|
858
887
|
(starred ? [:starred_color, "*"] : [:none, " "]),
|
859
888
|
] +
|
860
889
|
from +
|
@@ -883,6 +912,10 @@ private
|
|
883
912
|
end
|
884
913
|
end
|
885
914
|
|
915
|
+
def default_date_widget_for t
|
916
|
+
t.date.to_nice_s
|
917
|
+
end
|
918
|
+
|
886
919
|
def from_width
|
887
920
|
[(buffer.content_width.to_f * 0.2).to_i, MIN_FROM_WIDTH].max
|
888
921
|
end
|