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.

Files changed (43) hide show
  1. data/History.txt +10 -0
  2. data/bin/sup +50 -68
  3. data/doc/NewUserGuide.txt +11 -7
  4. data/doc/TODO +34 -22
  5. data/lib/sup.rb +30 -24
  6. data/lib/sup/buffer.rb +124 -39
  7. data/lib/sup/colormap.rb +4 -4
  8. data/lib/sup/draft.rb +1 -1
  9. data/lib/sup/hook.rb +18 -5
  10. data/lib/sup/imap.rb +11 -13
  11. data/lib/sup/index.rb +52 -14
  12. data/lib/sup/keymap.rb +1 -1
  13. data/lib/sup/logger.rb +1 -0
  14. data/lib/sup/maildir.rb +9 -0
  15. data/lib/sup/mbox.rb +3 -1
  16. data/lib/sup/message-chunks.rb +21 -7
  17. data/lib/sup/message.rb +31 -15
  18. data/lib/sup/mode.rb +2 -0
  19. data/lib/sup/modes/buffer-list-mode.rb +7 -3
  20. data/lib/sup/modes/compose-mode.rb +14 -16
  21. data/lib/sup/modes/contact-list-mode.rb +2 -2
  22. data/lib/sup/modes/edit-message-mode.rb +55 -23
  23. data/lib/sup/modes/forward-mode.rb +22 -5
  24. data/lib/sup/modes/inbox-mode.rb +3 -7
  25. data/lib/sup/modes/label-list-mode.rb +30 -10
  26. data/lib/sup/modes/label-search-results-mode.rb +12 -0
  27. data/lib/sup/modes/line-cursor-mode.rb +13 -0
  28. data/lib/sup/modes/log-mode.rb +0 -6
  29. data/lib/sup/modes/poll-mode.rb +0 -3
  30. data/lib/sup/modes/reply-mode.rb +19 -11
  31. data/lib/sup/modes/scroll-mode.rb +111 -20
  32. data/lib/sup/modes/search-results-mode.rb +21 -0
  33. data/lib/sup/modes/text-mode.rb +10 -2
  34. data/lib/sup/modes/thread-index-mode.rb +200 -90
  35. data/lib/sup/modes/thread-view-mode.rb +27 -10
  36. data/lib/sup/person.rb +1 -0
  37. data/lib/sup/poll.rb +15 -7
  38. data/lib/sup/source.rb +6 -1
  39. data/lib/sup/suicide.rb +1 -1
  40. data/lib/sup/textfield.rb +14 -14
  41. data/lib/sup/thread.rb +6 -2
  42. data/lib/sup/util.rb +111 -9
  43. 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
@@ -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.normalize_whitespace
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
- FROM_WIDTH = 15
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 current line", 't'
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
- @tags = Tagger.new self
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; !@lines[t].nil?; end
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 ||= @threads[curpos] or return
69
-
70
- ## TODO: don't regen text completely
71
- Redwood::reporting_thread do
72
- BufferManager.say("Loading message bodies...") do |sid|
73
- t.each { |m, *o| m.load_from_source! if m }
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 @lines[t]
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
- if is_relevant?(m) || @ts.is_relevant?(m)
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
- if @ts.contains_id? mid
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
- ## let's see you do THIS in python
134
- @threads = @ts.threads.select { |t| !@hidden_threads[t] }.sort_by { |t| t.date }.reverse
135
- @size_width = (@threads.max_of { |t| t.size } || 0).num_digits
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 = @threads[curpos])
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 = @threads[curpos] or return
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 = @threads[curpos] or return
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 = @threads[curpos] or return
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 = ((curpos + 1) ... lines).find { |i| @threads[i].has_label? :unread } || (0 ... curpos).find { |i| @threads[i].has_label? :unread }
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 = @threads[curpos] or return
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 = @threads[curpos] or return
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 = @threads[curpos] or return
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 "Thread#{threads.size == 1 ? '' : 's'} killed."
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 = @threads[curpos] or return
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 = @threads[curpos] or return
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 = @threads[curpos] or return
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 = @threads[curpos] or return
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
- mode = ForwardMode.new m
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 - 9999 # oh yeah
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
- synchronized :load_n_threads
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} threads."
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 cursor_thread; @threads[curpos]; end
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
- raise "already hidden" if @hidden_threads[t]
440
- @hidden_threads[t] = true
441
- @threads.delete t
442
- @tags.drop_tag_for t
443
- end
444
-
445
- def show_thread t
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
- @text[l] = text_for_thread @threads[l]
457
- buffer.mark_dirty if buffer
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
- @text = @threads.map_with_index { |t, i| text_for_thread t }
462
- @lines = @threads.map_with_index { |t, i| [t, i] }.to_h
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 author_text_for_thread t
467
- t.authors.map do |p|
468
- if AccountManager.is_account?(p)
469
- "me"
470
- elsif t.authors.size == 1
471
- p.mediumname
472
- else
473
- p.shortname
474
- end
475
- end.uniq.join ","
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 text_for_thread t
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
- base_color =
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
- [base_color, sprintf("%-#{@from_width}s", from)],
506
- [:none, t.size == 1 ? " " * (@size_width + 2) : sprintf("(%#{@size_width}d)", t.size)],
599
+ ] +
600
+ from +
601
+ [
602
+ [subj_color, size_widget_text],
507
603
  [:to_me_color, dp ? " >" : (p ? ' +' : " ")],
508
- [base_color, t.subj + (t.subj.empty? ? "" : " ")],
604
+ [subj_color, t.subj + (t.subj.empty? ? "" : " ")],
509
605
  ] +
510
606
  (t.labels - @hidden_labels).map { |label| [:label_color, "+#{label} "] } +
511
- [[:snippet_color, t.snippet]
607
+ [[:snippet_color, snippet]
512
608
  ]
609
+
513
610
  end
514
611
 
515
- def dirty?; (@hidden_threads.keys + @threads).any? { |t| t.dirty? }; end
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