sup 0.2 → 0.3
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/History.txt +10 -0
- data/bin/sup +50 -68
- data/doc/NewUserGuide.txt +11 -7
- data/doc/TODO +34 -22
- data/lib/sup.rb +30 -24
- data/lib/sup/buffer.rb +124 -39
- data/lib/sup/colormap.rb +4 -4
- data/lib/sup/draft.rb +1 -1
- data/lib/sup/hook.rb +18 -5
- data/lib/sup/imap.rb +11 -13
- data/lib/sup/index.rb +52 -14
- data/lib/sup/keymap.rb +1 -1
- data/lib/sup/logger.rb +1 -0
- data/lib/sup/maildir.rb +9 -0
- data/lib/sup/mbox.rb +3 -1
- data/lib/sup/message-chunks.rb +21 -7
- data/lib/sup/message.rb +31 -15
- data/lib/sup/mode.rb +2 -0
- data/lib/sup/modes/buffer-list-mode.rb +7 -3
- data/lib/sup/modes/compose-mode.rb +14 -16
- data/lib/sup/modes/contact-list-mode.rb +2 -2
- data/lib/sup/modes/edit-message-mode.rb +55 -23
- data/lib/sup/modes/forward-mode.rb +22 -5
- data/lib/sup/modes/inbox-mode.rb +3 -7
- data/lib/sup/modes/label-list-mode.rb +30 -10
- data/lib/sup/modes/label-search-results-mode.rb +12 -0
- data/lib/sup/modes/line-cursor-mode.rb +13 -0
- data/lib/sup/modes/log-mode.rb +0 -6
- data/lib/sup/modes/poll-mode.rb +0 -3
- data/lib/sup/modes/reply-mode.rb +19 -11
- data/lib/sup/modes/scroll-mode.rb +111 -20
- data/lib/sup/modes/search-results-mode.rb +21 -0
- data/lib/sup/modes/text-mode.rb +10 -2
- data/lib/sup/modes/thread-index-mode.rb +200 -90
- data/lib/sup/modes/thread-view-mode.rb +27 -10
- data/lib/sup/person.rb +1 -0
- data/lib/sup/poll.rb +15 -7
- data/lib/sup/source.rb +6 -1
- data/lib/sup/suicide.rb +1 -1
- data/lib/sup/textfield.rb +14 -14
- data/lib/sup/thread.rb +6 -2
- data/lib/sup/util.rb +111 -9
- metadata +13 -6
@@ -6,12 +6,33 @@ class SearchResultsMode < ThreadIndexMode
|
|
6
6
|
super [], { :qobj => @qobj }
|
7
7
|
end
|
8
8
|
|
9
|
+
register_keymap do |k|
|
10
|
+
k.add :refine_search, "Refine search", '.'
|
11
|
+
end
|
12
|
+
|
13
|
+
def refine_search
|
14
|
+
query = BufferManager.ask :search, "query: ", (@qobj.to_s + " ")
|
15
|
+
return unless query && query !~ /^\s*$/
|
16
|
+
SearchResultsMode.spawn_from_query query
|
17
|
+
end
|
18
|
+
|
9
19
|
## a proper is_relevant? method requires some way of asking ferret
|
10
20
|
## if an in-memory object satisfies a query. i'm not sure how to do
|
11
21
|
## that yet. in the worst case i can make an in-memory index, add
|
12
22
|
## the message, and search against it to see if i have > 0 results,
|
13
23
|
## but that seems pretty insane.
|
14
24
|
|
25
|
+
def self.spawn_from_query text
|
26
|
+
begin
|
27
|
+
qobj = Index.parse_user_query_string(text) or return
|
28
|
+
short_text = text.length < 20 ? text : text[0 ... 20] + "..."
|
29
|
+
mode = SearchResultsMode.new qobj
|
30
|
+
BufferManager.spawn "search: \"#{short_text}\"", mode
|
31
|
+
mode.load_threads :num => mode.buffer.content_height
|
32
|
+
rescue Ferret::QueryParser::QueryParseException => e
|
33
|
+
BufferManager.flash "Couldn't parse query."
|
34
|
+
end
|
35
|
+
end
|
15
36
|
end
|
16
37
|
|
17
38
|
end
|
data/lib/sup/modes/text-mode.rb
CHANGED
@@ -2,13 +2,21 @@ module Redwood
|
|
2
2
|
|
3
3
|
class TextMode < ScrollMode
|
4
4
|
attr_reader :text
|
5
|
+
register_keymap do |k|
|
6
|
+
k.add :save_to_disk, "Save to disk", 's'
|
7
|
+
end
|
5
8
|
|
6
9
|
def initialize text=""
|
7
|
-
@text = text
|
10
|
+
@text = text
|
8
11
|
update_lines
|
9
12
|
buffer.mark_dirty if buffer
|
10
13
|
super()
|
11
14
|
end
|
15
|
+
|
16
|
+
def save_to_disk
|
17
|
+
fn = BufferManager.ask_for_filename :filename, "Save to file: "
|
18
|
+
save_to_file(fn) { |f| f.puts text } if fn
|
19
|
+
end
|
12
20
|
|
13
21
|
def text= t
|
14
22
|
@text = t
|
@@ -35,7 +43,7 @@ class TextMode < ScrollMode
|
|
35
43
|
|
36
44
|
def [] i
|
37
45
|
return nil unless i < @lines.length
|
38
|
-
@text[@lines[i] ... (i + 1 < @lines.length ? @lines[i + 1] - 1 : @text.length)]
|
46
|
+
@text[@lines[i] ... (i + 1 < @lines.length ? @lines[i + 1] - 1 : @text.length)].normalize_whitespace
|
39
47
|
# (@lines[i] ... (i + 1 < @lines.length ? @lines[i + 1] - 1 : @text.length)).inspect
|
40
48
|
end
|
41
49
|
|
@@ -5,9 +5,15 @@ module Redwood
|
|
5
5
|
|
6
6
|
class ThreadIndexMode < LineCursorMode
|
7
7
|
DATE_WIDTH = Time::TO_NICE_S_MAX_LEN
|
8
|
-
|
8
|
+
MIN_FROM_WIDTH = 15
|
9
9
|
LOAD_MORE_THREAD_NUM = 20
|
10
10
|
|
11
|
+
HookManager.register "index-mode-size-widget", <<EOS
|
12
|
+
Generates the per-thread size widget for each thread.
|
13
|
+
Variables:
|
14
|
+
thread: The message thread to be formatted.
|
15
|
+
EOS
|
16
|
+
|
11
17
|
register_keymap do |k|
|
12
18
|
k.add :load_threads, "Load #{LOAD_MORE_THREAD_NUM} more threads", 'M'
|
13
19
|
k.add :reload, "Refresh view", '@'
|
@@ -23,24 +29,28 @@ class ThreadIndexMode < LineCursorMode
|
|
23
29
|
k.add :jump_to_next_new, "Jump to next new thread", :tab
|
24
30
|
k.add :reply, "Reply to latest message in a thread", 'r'
|
25
31
|
k.add :forward, "Forward latest message in a thread", 'f'
|
26
|
-
k.add :toggle_tagged, "Tag/untag
|
32
|
+
k.add :toggle_tagged, "Tag/untag selected thread", 't'
|
33
|
+
k.add :toggle_tagged_all, "Tag/untag all threads", 'T'
|
27
34
|
k.add :apply_to_tagged, "Apply next command to all tagged threads", ';'
|
28
35
|
end
|
29
36
|
|
30
37
|
def initialize hidden_labels=[], load_thread_opts={}
|
31
38
|
super()
|
32
|
-
@mutex = Mutex.new
|
39
|
+
@mutex = Mutex.new # covers the following variables:
|
40
|
+
@threads = {}
|
41
|
+
@hidden_threads = {}
|
42
|
+
@size_widget_width = nil
|
43
|
+
@size_widgets = {}
|
44
|
+
@tags = Tagger.new self
|
45
|
+
|
46
|
+
## these guys, and @text and @lines, are not covered
|
33
47
|
@load_thread = nil
|
34
48
|
@load_thread_opts = load_thread_opts
|
35
49
|
@hidden_labels = hidden_labels + LabelManager::HIDDEN_RESERVED_LABELS
|
36
50
|
@date_width = DATE_WIDTH
|
37
|
-
@from_width = FROM_WIDTH
|
38
|
-
@size_width = nil
|
39
51
|
|
40
|
-
@
|
41
|
-
|
42
|
-
initialize_threads
|
43
|
-
update
|
52
|
+
initialize_threads # defines @ts and @ts_mutex
|
53
|
+
update # defines @text and @lines
|
44
54
|
|
45
55
|
UpdateManager.register self
|
46
56
|
|
@@ -55,7 +65,7 @@ class ThreadIndexMode < LineCursorMode
|
|
55
65
|
|
56
66
|
def lines; @text.length; end
|
57
67
|
def [] i; @text[i]; end
|
58
|
-
def contains_thread? t;
|
68
|
+
def contains_thread? t; @threads.include?(t) end
|
59
69
|
|
60
70
|
def reload
|
61
71
|
drop_all_threads
|
@@ -65,12 +75,17 @@ class ThreadIndexMode < LineCursorMode
|
|
65
75
|
|
66
76
|
## open up a thread view window
|
67
77
|
def select t=nil
|
68
|
-
t ||=
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
78
|
+
t ||= cursor_thread or return
|
79
|
+
|
80
|
+
Redwood::reporting_thread("load messages for thread-view-mode") do
|
81
|
+
num = t.size
|
82
|
+
message = "Loading #{num.pluralize 'message body'}..."
|
83
|
+
BufferManager.say(message) do |sid|
|
84
|
+
t.each_with_index do |(m, *o), i|
|
85
|
+
next unless m
|
86
|
+
BufferManager.say "#{message} (#{i}/#{num})", sid if t.size > 1
|
87
|
+
m.load_from_source!
|
88
|
+
end
|
74
89
|
end
|
75
90
|
mode = ThreadViewMode.new t, @hidden_labels
|
76
91
|
BufferManager.spawn t.subj, mode
|
@@ -90,7 +105,11 @@ class ThreadIndexMode < LineCursorMode
|
|
90
105
|
end
|
91
106
|
|
92
107
|
def handle_label_update sender, m
|
93
|
-
t = @ts.thread_for(m) or return
|
108
|
+
t = @ts_mutex.synchronize { @ts.thread_for(m) } or return
|
109
|
+
handle_label_thread_update sender, t
|
110
|
+
end
|
111
|
+
|
112
|
+
def handle_label_thread_update sender, t
|
94
113
|
l = @lines[t] or return
|
95
114
|
update_text_for_line l
|
96
115
|
BufferManager.draw_screen
|
@@ -98,7 +117,7 @@ class ThreadIndexMode < LineCursorMode
|
|
98
117
|
|
99
118
|
def handle_read_update sender, t
|
100
119
|
l = @lines[t] or return
|
101
|
-
update_text_for_line
|
120
|
+
update_text_for_line l
|
102
121
|
BufferManager.draw_screen
|
103
122
|
end
|
104
123
|
|
@@ -114,30 +133,36 @@ class ThreadIndexMode < LineCursorMode
|
|
114
133
|
def is_relevant? m; false; end
|
115
134
|
|
116
135
|
def handle_add_update sender, m
|
117
|
-
|
136
|
+
@ts_mutex.synchronize do
|
137
|
+
return unless is_relevant?(m) || @ts.is_relevant?(m)
|
118
138
|
@ts.load_thread_for_message m
|
119
|
-
update
|
120
|
-
BufferManager.draw_screen
|
121
139
|
end
|
140
|
+
update
|
141
|
+
BufferManager.draw_screen
|
122
142
|
end
|
123
143
|
|
124
144
|
def handle_delete_update sender, mid
|
125
|
-
|
145
|
+
@ts_mutex.synchronize do
|
146
|
+
return unless @ts.contains_id? mid
|
126
147
|
@ts.remove mid
|
127
|
-
update
|
128
|
-
BufferManager.draw_screen
|
129
148
|
end
|
149
|
+
update
|
150
|
+
BufferManager.draw_screen
|
130
151
|
end
|
131
152
|
|
132
153
|
def update
|
133
|
-
|
134
|
-
|
135
|
-
|
154
|
+
@mutex.synchronize do
|
155
|
+
## let's see you do THIS in python
|
156
|
+
@threads = @ts.threads.select { |t| !@hidden_threads[t] }.sort_by { |t| t.date }.reverse
|
157
|
+
@size_widgets = @threads.map { |t| size_widget_for_thread t }
|
158
|
+
@size_widget_width = @size_widgets.max_of { |w| w.length }
|
159
|
+
end
|
160
|
+
|
136
161
|
regen_text
|
137
162
|
end
|
138
163
|
|
139
164
|
def edit_message
|
140
|
-
return unless(t =
|
165
|
+
return unless(t = cursor_thread)
|
141
166
|
message, *crap = t.find { |m, *o| m.has_label? :draft }
|
142
167
|
if message
|
143
168
|
mode = ResumeMode.new message
|
@@ -158,7 +183,7 @@ class ThreadIndexMode < LineCursorMode
|
|
158
183
|
end
|
159
184
|
|
160
185
|
def toggle_starred
|
161
|
-
t =
|
186
|
+
t = cursor_thread or return
|
162
187
|
actually_toggle_starred t
|
163
188
|
update_text_for_line curpos
|
164
189
|
cursor_down
|
@@ -200,7 +225,7 @@ class ThreadIndexMode < LineCursorMode
|
|
200
225
|
end
|
201
226
|
|
202
227
|
def toggle_archived
|
203
|
-
t =
|
228
|
+
t = cursor_thread or return
|
204
229
|
actually_toggle_archived t
|
205
230
|
update_text_for_line curpos
|
206
231
|
end
|
@@ -211,7 +236,7 @@ class ThreadIndexMode < LineCursorMode
|
|
211
236
|
end
|
212
237
|
|
213
238
|
def toggle_new
|
214
|
-
t =
|
239
|
+
t = cursor_thread or return
|
215
240
|
t.toggle_label :unread
|
216
241
|
update_text_for_line curpos
|
217
242
|
cursor_down
|
@@ -223,12 +248,15 @@ class ThreadIndexMode < LineCursorMode
|
|
223
248
|
end
|
224
249
|
|
225
250
|
def multi_toggle_tagged threads
|
226
|
-
@tags.drop_all_tags
|
251
|
+
@mutex.synchronize { @tags.drop_all_tags }
|
227
252
|
regen_text
|
228
253
|
end
|
229
254
|
|
230
255
|
def jump_to_next_new
|
231
|
-
n =
|
256
|
+
n = @mutex.synchronize do
|
257
|
+
((curpos + 1) ... lines).find { |i| @threads[i].has_label? :unread } ||
|
258
|
+
(0 ... curpos).find { |i| @threads[i].has_label? :unread }
|
259
|
+
end
|
232
260
|
if n
|
233
261
|
## jump there if necessary
|
234
262
|
jump_to_line n unless n >= topline && n < botline
|
@@ -239,7 +267,7 @@ class ThreadIndexMode < LineCursorMode
|
|
239
267
|
end
|
240
268
|
|
241
269
|
def toggle_spam
|
242
|
-
t =
|
270
|
+
t = cursor_thread or return
|
243
271
|
multi_toggle_spam [t]
|
244
272
|
end
|
245
273
|
|
@@ -259,7 +287,7 @@ class ThreadIndexMode < LineCursorMode
|
|
259
287
|
end
|
260
288
|
|
261
289
|
def toggle_deleted
|
262
|
-
t =
|
290
|
+
t = cursor_thread or return
|
263
291
|
multi_toggle_deleted [t]
|
264
292
|
end
|
265
293
|
|
@@ -273,7 +301,7 @@ class ThreadIndexMode < LineCursorMode
|
|
273
301
|
end
|
274
302
|
|
275
303
|
def kill
|
276
|
-
t =
|
304
|
+
t = cursor_thread or return
|
277
305
|
multi_kill [t]
|
278
306
|
end
|
279
307
|
|
@@ -283,11 +311,11 @@ class ThreadIndexMode < LineCursorMode
|
|
283
311
|
hide_thread t
|
284
312
|
end
|
285
313
|
regen_text
|
286
|
-
BufferManager.flash "
|
314
|
+
BufferManager.flash "#{threads.size.pluralize 'Thread'} killed."
|
287
315
|
end
|
288
316
|
|
289
317
|
def save
|
290
|
-
dirty_threads = (@threads + @hidden_threads.keys).select { |t| t.dirty? }
|
318
|
+
dirty_threads = @mutex.synchronize { (@threads + @hidden_threads.keys).select { |t| t.dirty? } }
|
291
319
|
return if dirty_threads.empty?
|
292
320
|
|
293
321
|
BufferManager.say("Saving threads...") do |say_id|
|
@@ -312,16 +340,21 @@ class ThreadIndexMode < LineCursorMode
|
|
312
340
|
end
|
313
341
|
|
314
342
|
def toggle_tagged
|
315
|
-
t =
|
316
|
-
@tags.toggle_tag_for t
|
343
|
+
t = cursor_thread or return
|
344
|
+
@mutex.synchronize { @tags.toggle_tag_for t }
|
317
345
|
update_text_for_line curpos
|
318
346
|
cursor_down
|
319
347
|
end
|
348
|
+
|
349
|
+
def toggle_tagged_all
|
350
|
+
@mutex.synchronize { @threads.each { |t| @tags.toggle_tag_for t } }
|
351
|
+
regen_text
|
352
|
+
end
|
320
353
|
|
321
354
|
def apply_to_tagged; @tags.apply_to_tagged; end
|
322
355
|
|
323
356
|
def edit_labels
|
324
|
-
thread =
|
357
|
+
thread = cursor_thread or return
|
325
358
|
speciall = (@hidden_labels + LabelManager::RESERVED_LABELS).uniq
|
326
359
|
keepl, modifyl = thread.labels.partition { |t| speciall.member? t }
|
327
360
|
|
@@ -349,7 +382,7 @@ class ThreadIndexMode < LineCursorMode
|
|
349
382
|
end
|
350
383
|
|
351
384
|
def reply
|
352
|
-
t =
|
385
|
+
t = cursor_thread or return
|
353
386
|
m = t.latest_message
|
354
387
|
return if m.nil? # probably won't happen
|
355
388
|
m.load_from_source!
|
@@ -358,31 +391,30 @@ class ThreadIndexMode < LineCursorMode
|
|
358
391
|
end
|
359
392
|
|
360
393
|
def forward
|
361
|
-
t =
|
394
|
+
t = cursor_thread or return
|
362
395
|
m = t.latest_message
|
363
396
|
return if m.nil? # probably won't happen
|
364
397
|
m.load_from_source!
|
365
|
-
|
366
|
-
BufferManager.spawn "Forward of #{m.subj}", mode
|
367
|
-
mode.edit_message
|
398
|
+
ForwardMode.spawn_nicely m
|
368
399
|
end
|
369
400
|
|
370
401
|
def load_n_threads_background n=LOAD_MORE_THREAD_NUM, opts={}
|
371
402
|
return if @load_thread # todo: wrap in mutex
|
372
|
-
@load_thread = Redwood::reporting_thread do
|
403
|
+
@load_thread = Redwood::reporting_thread("load threads for thread-index-mode") do
|
373
404
|
num = load_n_threads n, opts
|
374
405
|
opts[:when_done].call(num) if opts[:when_done]
|
375
406
|
@load_thread = nil
|
376
407
|
end
|
377
408
|
end
|
378
409
|
|
410
|
+
## TODO: figure out @ts_mutex in this method
|
379
411
|
def load_n_threads n=LOAD_MORE_THREAD_NUM, opts={}
|
380
412
|
@mbid = BufferManager.say "Searching for threads..."
|
381
413
|
orig_size = @ts.size
|
382
|
-
last_update = Time.now
|
414
|
+
last_update = Time.now
|
383
415
|
@ts.load_n_threads(@ts.size + n, opts) do |i|
|
384
|
-
BufferManager.say "Loaded #{i} threads...", @mbid
|
385
416
|
if (Time.now - last_update) >= 0.25
|
417
|
+
BufferManager.say "Loaded #{i.pluralize 'thread'}...", @mbid
|
386
418
|
update
|
387
419
|
BufferManager.draw_screen
|
388
420
|
last_update = Time.now
|
@@ -396,7 +428,7 @@ class ThreadIndexMode < LineCursorMode
|
|
396
428
|
BufferManager.draw_screen
|
397
429
|
@ts.size - orig_size
|
398
430
|
end
|
399
|
-
|
431
|
+
ignore_concurrent_calls :load_n_threads
|
400
432
|
|
401
433
|
def status
|
402
434
|
if (l = lines) == 0
|
@@ -412,7 +444,7 @@ class ThreadIndexMode < LineCursorMode
|
|
412
444
|
myopts = @load_thread_opts.merge({ :when_done => (lambda do |num|
|
413
445
|
opts[:when_done].call(num) if opts[:when_done]
|
414
446
|
if num > 0
|
415
|
-
BufferManager.flash "Found #{num}
|
447
|
+
BufferManager.flash "Found #{num.pluralize 'thread'}."
|
416
448
|
else
|
417
449
|
BufferManager.flash "No matches."
|
418
450
|
end
|
@@ -424,10 +456,20 @@ class ThreadIndexMode < LineCursorMode
|
|
424
456
|
load_n_threads n, myopts
|
425
457
|
end
|
426
458
|
end
|
459
|
+
ignore_concurrent_calls :load_threads
|
460
|
+
|
461
|
+
def resize rows, cols
|
462
|
+
regen_text
|
463
|
+
super
|
464
|
+
end
|
427
465
|
|
428
466
|
protected
|
429
467
|
|
430
|
-
def
|
468
|
+
def size_widget_for_thread t
|
469
|
+
HookManager.run("index-mode-size-widget", :thread => t) || default_size_widget_for(t)
|
470
|
+
end
|
471
|
+
|
472
|
+
def cursor_thread; @mutex.synchronize { @threads[curpos] }; end
|
431
473
|
|
432
474
|
def drop_all_threads
|
433
475
|
@tags.drop_all_tags
|
@@ -436,60 +478,108 @@ protected
|
|
436
478
|
end
|
437
479
|
|
438
480
|
def hide_thread t
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
if @hidden_threads[t]
|
447
|
-
@hidden_threads.delete t
|
448
|
-
else
|
449
|
-
@ts.add_thread t
|
481
|
+
@mutex.synchronize do
|
482
|
+
i = @threads.index(t) or return
|
483
|
+
raise "already hidden" if @hidden_threads[t]
|
484
|
+
@hidden_threads[t] = true
|
485
|
+
@threads.delete_at i
|
486
|
+
@size_widgets.delete_at i
|
487
|
+
@tags.drop_tag_for t
|
450
488
|
end
|
451
|
-
update
|
452
489
|
end
|
453
490
|
|
454
491
|
def update_text_for_line l
|
455
492
|
return unless l # not sure why this happens, but it does, occasionally
|
456
|
-
|
457
|
-
|
493
|
+
|
494
|
+
need_update = false
|
495
|
+
|
496
|
+
@mutex.synchronize do
|
497
|
+
@size_widgets[l] = size_widget_for_thread @threads[l]
|
498
|
+
|
499
|
+
## if the widget size has increased, we need to redraw everyone
|
500
|
+
need_update = @size_widgets[l].size > @size_widget_width
|
501
|
+
end
|
502
|
+
|
503
|
+
if need_update
|
504
|
+
update
|
505
|
+
else
|
506
|
+
@text[l] = text_for_thread_at l
|
507
|
+
buffer.mark_dirty if buffer
|
508
|
+
end
|
458
509
|
end
|
459
510
|
|
460
511
|
def regen_text
|
461
|
-
|
462
|
-
@
|
512
|
+
threads = @mutex.synchronize { @threads }
|
513
|
+
@text = threads.map_with_index { |t, i| text_for_thread_at i }
|
514
|
+
@lines = threads.map_with_index { |t, i| [t, i] }.to_h
|
463
515
|
buffer.mark_dirty if buffer
|
464
516
|
end
|
465
517
|
|
466
|
-
def
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
518
|
+
def authors; map { |m, *o| m.from if m }.compact.uniq; end
|
519
|
+
|
520
|
+
def author_names_and_newness_for_thread t
|
521
|
+
new = {}
|
522
|
+
authors = t.map do |m, *o|
|
523
|
+
next unless m
|
524
|
+
|
525
|
+
name =
|
526
|
+
if AccountManager.is_account?(m.from)
|
527
|
+
"me"
|
528
|
+
elsif t.authors.size == 1
|
529
|
+
m.from.mediumname
|
530
|
+
else
|
531
|
+
m.from.shortname
|
532
|
+
end
|
533
|
+
|
534
|
+
new[name] ||= m.has_label?(:unread)
|
535
|
+
name
|
536
|
+
end
|
537
|
+
|
538
|
+
authors.compact.uniq.map { |a| [a, new[a]] }
|
476
539
|
end
|
477
540
|
|
478
|
-
def
|
541
|
+
def text_for_thread_at line
|
542
|
+
t, size_widget = @mutex.synchronize { [@threads[line], @size_widgets[line]] }
|
543
|
+
|
479
544
|
date = t.date.to_nice_s
|
480
|
-
from = author_text_for_thread t
|
481
|
-
if from.length > @from_width
|
482
|
-
from = from[0 ... (@from_width - 1)]
|
483
|
-
from += "." unless from[-1] == ?\s
|
484
|
-
end
|
485
545
|
|
486
546
|
new = t.has_label?(:unread)
|
487
547
|
starred = t.has_label?(:starred)
|
488
548
|
|
549
|
+
## format the from column
|
550
|
+
cur_width = 0
|
551
|
+
ann = author_names_and_newness_for_thread t
|
552
|
+
from = []
|
553
|
+
ann.each_with_index do |(name, newness), i|
|
554
|
+
break if cur_width >= from_width
|
555
|
+
last = i == ann.length - 1
|
556
|
+
|
557
|
+
abbrev =
|
558
|
+
if cur_width + name.length > from_width
|
559
|
+
name[0 ... (from_width - cur_width - 1)] + "."
|
560
|
+
elsif cur_width + name.length == from_width
|
561
|
+
name[0 ... (from_width - cur_width)]
|
562
|
+
else
|
563
|
+
if last
|
564
|
+
name[0 ... (from_width - cur_width)]
|
565
|
+
else
|
566
|
+
name[0 ... (from_width - cur_width - 1)] + ","
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
cur_width += abbrev.length
|
571
|
+
|
572
|
+
if last && from_width > cur_width
|
573
|
+
abbrev += " " * (from_width - cur_width)
|
574
|
+
end
|
575
|
+
|
576
|
+
from << [(newness ? :index_new_color : (starred ? :index_starred_color : :index_old_color)), abbrev]
|
577
|
+
end
|
578
|
+
|
489
579
|
dp = t.direct_participants.any? { |p| AccountManager.is_account? p }
|
490
580
|
p = dp || t.participants.any? { |p| AccountManager.is_account? p }
|
491
581
|
|
492
|
-
|
582
|
+
subj_color =
|
493
583
|
if new
|
494
584
|
:index_new_color
|
495
585
|
elsif starred
|
@@ -498,24 +588,44 @@ protected
|
|
498
588
|
:index_old_color
|
499
589
|
end
|
500
590
|
|
591
|
+
snippet = t.snippet + (t.snippet.empty? ? "" : "...")
|
592
|
+
|
593
|
+
size_widget_text = sprintf "%#{ @size_widget_width}s", size_widget
|
594
|
+
|
501
595
|
[
|
502
596
|
[:tagged_color, @tags.tagged?(t) ? ">" : " "],
|
503
597
|
[:none, sprintf("%#{@date_width}s", date)],
|
504
598
|
(starred ? [:starred_color, "*"] : [:none, " "]),
|
505
|
-
|
506
|
-
|
599
|
+
] +
|
600
|
+
from +
|
601
|
+
[
|
602
|
+
[subj_color, size_widget_text],
|
507
603
|
[:to_me_color, dp ? " >" : (p ? ' +' : " ")],
|
508
|
-
[
|
604
|
+
[subj_color, t.subj + (t.subj.empty? ? "" : " ")],
|
509
605
|
] +
|
510
606
|
(t.labels - @hidden_labels).map { |label| [:label_color, "+#{label} "] } +
|
511
|
-
[[:snippet_color,
|
607
|
+
[[:snippet_color, snippet]
|
512
608
|
]
|
609
|
+
|
513
610
|
end
|
514
611
|
|
515
|
-
def dirty?; (@hidden_threads.keys + @threads).any? { |t| t.dirty? }
|
612
|
+
def dirty?; @mutex.synchronize { (@hidden_threads.keys + @threads).any? { |t| t.dirty? } } end
|
516
613
|
|
517
614
|
private
|
518
615
|
|
616
|
+
def default_size_widget_for t
|
617
|
+
case t.size
|
618
|
+
when 1
|
619
|
+
""
|
620
|
+
else
|
621
|
+
"(#{t.size})"
|
622
|
+
end
|
623
|
+
end
|
624
|
+
|
625
|
+
def from_width
|
626
|
+
[(buffer.content_width.to_f * 0.2).to_i, MIN_FROM_WIDTH].max
|
627
|
+
end
|
628
|
+
|
519
629
|
def initialize_threads
|
520
630
|
@ts = ThreadSet.new Index.instance, $config[:thread_by_subject]
|
521
631
|
@ts_mutex = Mutex.new
|