Class: Redwood::ThreadIndexMode

Inherits:
LineCursorMode show all
Defined in:
lib/sup/modes/thread_index_mode.rb

Overview

subclasses should implement:

  • is_relevant?

Constant Summary collapse

DATE_WIDTH =
Time::TO_NICE_S_MAX_LEN
MIN_FROM_WIDTH =
15
LOAD_MORE_THREAD_NUM =
20

Instance Attribute Summary

Attributes inherited from LineCursorMode

#curpos

Attributes inherited from ScrollMode

#botline, #leftcol, #topline

Attributes inherited from Mode

#buffer

Instance Method Summary collapse

Methods inherited from LineCursorMode

#draw

Methods inherited from ScrollMode

#at_bottom?, #at_top?, #cancel_search!, #col_jump, #col_left, #col_right, #continue_search_in_buffer, #draw, #ensure_mode_validity, #half_page_down, #half_page_up, #in_search?, #jump_to_col, #jump_to_end, #jump_to_left, #jump_to_line, #jump_to_start, #line_down, #line_up, #page_down, #page_up, #rightcol, #search_goto_line, #search_goto_pos, #search_in_buffer, #search_start_line

Methods inherited from Mode

#blur, #cancel_search!, #draw, #focus, #handle_input, #help_text, #in_search?, keymap, keymaps, #killable?, load_all_modes, make_name, #name, #pipe_to_process, register_keymap, #resolve_input, #save_to_file

Constructor Details

#initialize(hidden_labels = [], load_thread_opts = {}) ⇒ ThreadIndexMode

Returns a new instance of ThreadIndexMode.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/sup/modes/thread_index_mode.rb', line 59

def initialize hidden_labels=[], load_thread_opts={}
  super()
  @mutex = Mutex.new # covers the following variables:
  @threads = []
  @hidden_threads = {}
  @size_widget_width = nil
  @size_widgets = []
  @date_widget_width = nil
  @date_widgets = []
  @tags = Tagger.new self

  ## these guys, and @text and @lines, are not covered
  @load_thread = nil
  @load_thread_opts = load_thread_opts
  @hidden_labels = hidden_labels + LabelManager::HIDDEN_RESERVED_LABELS
  @date_width = DATE_WIDTH

  @interrupt_search = false

  initialize_threads # defines @ts and @ts_mutex
  update # defines @text and @lines

  UpdateManager.register self

  @save_thread_mutex = Mutex.new

  @last_load_more_size = nil
  to_load_more do |size|
    next if @last_load_more_size == 0
    load_threads :num => size,
                 :when_done => lambda { |num| @last_load_more_size = num }
  end
end

Instance Method Details

#[](i) ⇒ Object



95
# File 'lib/sup/modes/thread_index_mode.rb', line 95

def [] i; @text[i]; end

#actually_toggle_archived(t) ⇒ Object

returns an undo lambda



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/sup/modes/thread_index_mode.rb', line 318

def actually_toggle_archived t
  thread = t
  pos = curpos
  if t.has_label? :inbox
    t.remove_label :inbox
    UpdateManager.relay self, :archived, t.first
    lambda do
      thread.apply_label :inbox
      update_text_for_line pos
      UpdateManager.relay self,:unarchived, thread.first
    end
  else
    t.apply_label :inbox
    UpdateManager.relay self, :unarchived, t.first
    lambda do
      thread.remove_label :inbox
      update_text_for_line pos
      UpdateManager.relay self, :unarchived, thread.first
    end
  end
end

#actually_toggle_deleted(t) ⇒ Object

returns an undo lambda



365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/sup/modes/thread_index_mode.rb', line 365

def actually_toggle_deleted t
  if t.has_label? :deleted
    t.remove_label :deleted
    add_or_unhide t.first
    UpdateManager.relay self, :undeleted, t.first
    lambda do
      t.apply_label :deleted
      hide_thread t
      UpdateManager.relay self, :deleted, t.first
    end
  else
    t.apply_label :deleted
    hide_thread t
    UpdateManager.relay self, :deleted, t.first
    lambda do
      t.remove_label :deleted
      add_or_unhide t.first
      UpdateManager.relay self, :undeleted, t.first
    end
  end
end

#actually_toggle_spammed(t) ⇒ Object

returns an undo lambda



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# File 'lib/sup/modes/thread_index_mode.rb', line 341

def actually_toggle_spammed t
  thread = t
  if t.has_label? :spam
    t.remove_label :spam
    add_or_unhide t.first
    UpdateManager.relay self, :unspammed, t.first
    lambda do
      thread.apply_label :spam
      self.hide_thread thread
      UpdateManager.relay self,:spammed, thread.first
    end
  else
    t.apply_label :spam
    hide_thread t
    UpdateManager.relay self, :spammed, t.first
    lambda do
      thread.remove_label :spam
      add_or_unhide thread.first
      UpdateManager.relay self,:unspammed, thread.first
    end
  end
end

#actually_toggle_starred(t) ⇒ Object

returns an undo lambda



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/sup/modes/thread_index_mode.rb', line 280

def actually_toggle_starred t
  if t.has_label? :starred # if ANY message has a star
    t.remove_label :starred # remove from all
    UpdateManager.relay self, :unstarred, t.first
    lambda do
      t.first.add_label :starred
      UpdateManager.relay self, :starred, t.first
      regen_text
    end
  else
    t.first.add_label :starred # add only to first
    UpdateManager.relay self, :starred, t.first
    lambda do
      t.remove_label :starred
      UpdateManager.relay self, :unstarred, t.first
      regen_text
    end
  end
end

#apply_to_taggedObject



568
# File 'lib/sup/modes/thread_index_mode.rb', line 568

def apply_to_tagged; @tags.apply_to_tagged; end

#cancel_searchObject



702
703
704
# File 'lib/sup/modes/thread_index_mode.rb', line 702

def cancel_search
  @interrupt_search = true
end

#cleanupObject



529
530
531
532
533
534
535
536
537
538
539
540
541
# File 'lib/sup/modes/thread_index_mode.rb', line 529

def cleanup
  UpdateManager.unregister self

  if @load_thread
    @load_thread.kill
    BufferManager.clear @mbid if @mbid
    sleep 0.1 # TODO: necessary?
    BufferManager.erase_flash
  end
  dirty_threads = @mutex.synchronize { (@threads + @hidden_threads.keys).select { |t| t.dirty? } }
  fail "dirty threads remain" unless dirty_threads.empty?
  super
end

#contains_thread?(t) ⇒ Boolean

Returns:

  • (Boolean)


96
# File 'lib/sup/modes/thread_index_mode.rb', line 96

def contains_thread? t; @threads.include?(t) end

#edit_labelsObject



570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
# File 'lib/sup/modes/thread_index_mode.rb', line 570

def edit_labels
  thread = cursor_thread or return
  speciall = (@hidden_labels + LabelManager::RESERVED_LABELS).uniq

  old_labels = thread.labels
  pos = curpos

  keepl, modifyl = thread.labels.partition { |t| speciall.member? t }

  user_labels = BufferManager.ask_for_labels :label, "Labels for thread: ", modifyl.sort_by {|x| x.to_s}, @hidden_labels
  return unless user_labels

  thread.labels = Set.new(keepl) + user_labels
  user_labels.each { |l| LabelManager << l }
  update_text_for_line curpos

  UndoManager.register "labeling thread" do
    thread.labels = old_labels
    update_text_for_line pos
    UpdateManager.relay self, :labeled, thread.first
    Index.save_thread thread
  end

  UpdateManager.relay self, :labeled, thread.first
  Index.save_thread thread
end

#edit_messageObject



268
269
270
271
272
273
274
275
276
277
# File 'lib/sup/modes/thread_index_mode.rb', line 268

def edit_message
  return unless(t = cursor_thread)
  message, *_ = t.find { |m, *o| m.has_label? :draft }
  if message
    mode = ResumeMode.new message
    BufferManager.spawn "Edit message", mode
  else
    BufferManager.flash "Not a draft message!"
  end
end

#flush_indexObject



490
491
492
493
494
# File 'lib/sup/modes/thread_index_mode.rb', line 490

def flush_index
  @flush_id = BufferManager.say "Flushing index..."
  Index.save_index
  BufferManager.clear @flush_id
end

#forwardObject



647
648
649
650
651
652
653
# File 'lib/sup/modes/thread_index_mode.rb', line 647

def forward
  t = cursor_thread or return
  m = t.latest_message
  return if m.nil? # probably won't happen
  m.load_from_source!
  ForwardMode.spawn_nicely :message => m
end

#handle_added_update(sender, m) ⇒ Object



198
199
200
201
# File 'lib/sup/modes/thread_index_mode.rb', line 198

def handle_added_update sender, m
  add_or_unhide m
  BufferManager.draw_screen
end

#handle_deleted_update(sender, m) ⇒ Object



231
232
233
234
235
236
# File 'lib/sup/modes/thread_index_mode.rb', line 231

def handle_deleted_update sender, m
  t = @ts_mutex.synchronize { @ts.thread_for m }
  return unless t
  hide_thread t
  update
end

#handle_labeled_update(sender, m) ⇒ Object



174
175
176
177
178
179
180
181
# File 'lib/sup/modes/thread_index_mode.rb', line 174

def handle_labeled_update sender, m
  if(t = thread_containing(m))
    l = @lines[t] or return
    update_text_for_line l
  elsif is_relevant?(m)
    add_or_unhide m
  end
end

#handle_location_deleted_update(sender, m) ⇒ Object



214
215
216
217
218
219
220
221
# File 'lib/sup/modes/thread_index_mode.rb', line 214

def handle_location_deleted_update sender, m
  t = thread_containing(m)
  delete_thread t if t and t.first.id == m.id
  @ts_mutex.synchronize do
    @ts.delete_message m if t
  end
  update
end

#handle_simple_update(sender, m) ⇒ Object



183
184
185
186
187
# File 'lib/sup/modes/thread_index_mode.rb', line 183

def handle_simple_update sender, m
  t = thread_containing(m) or return
  l = @lines[t] or return
  update_text_for_line l
end

#handle_single_message_deleted_update(sender, m) ⇒ Object



223
224
225
226
227
228
229
# File 'lib/sup/modes/thread_index_mode.rb', line 223

def handle_single_message_deleted_update sender, m
  @ts_mutex.synchronize do
    return unless @ts.contains? m
    @ts.remove_id m.id
  end
  update
end

#handle_single_message_labeled_update(sender, m) ⇒ Object



168
169
170
171
172
# File 'lib/sup/modes/thread_index_mode.rb', line 168

def handle_single_message_labeled_update sender, m
  ## no need to do anything different here; we don't differentiate
  ## messages from their containing threads
  handle_labeled_update sender, m
end

#handle_spammed_update(sender, m) ⇒ Object



238
239
240
241
242
243
# File 'lib/sup/modes/thread_index_mode.rb', line 238

def handle_spammed_update sender, m
  t = @ts_mutex.synchronize { @ts.thread_for m }
  return unless t
  hide_thread t
  update
end

#handle_undeleted_update(sender, m) ⇒ Object



245
246
247
# File 'lib/sup/modes/thread_index_mode.rb', line 245

def handle_undeleted_update sender, m
  add_or_unhide m
end

#handle_updated_update(sender, m) ⇒ Object



203
204
205
206
207
208
209
210
211
212
# File 'lib/sup/modes/thread_index_mode.rb', line 203

def handle_updated_update sender, m
  t = thread_containing(m) or return
  l = @lines[t] or return
  @ts_mutex.synchronize do
    @ts.delete_message m
    @ts.add_message m
  end
  Index.save_thread t
  update_text_for_line l
end

#is_relevant?(m) ⇒ Boolean

overwrite me!

Returns:

  • (Boolean)


196
# File 'lib/sup/modes/thread_index_mode.rb', line 196

def is_relevant? m; false; end

#join_threadsObject



423
424
425
426
427
# File 'lib/sup/modes/thread_index_mode.rb', line 423

def join_threads
  ## this command has no non-tagged form. as a convenience, allow this
  ## command to be applied to tagged threads without hitting ';'.
  @tags.apply_to_tagged :join_threads
end

#jump_to_next_newObject



436
437
438
439
440
441
442
443
444
445
446
447
448
# File 'lib/sup/modes/thread_index_mode.rb', line 436

def jump_to_next_new
  n = @mutex.synchronize do
    ((curpos + 1) ... lines).find { |i| @threads[i].has_label? :unread } ||
      (0 ... curpos).find { |i| @threads[i].has_label? :unread }
  end
  if n
    ## jump there if necessary
    jump_to_line n unless n >= topline && n < botline
    set_cursor_pos n
  else
    BufferManager.flash "No new messages."
  end
end

#killObject



485
486
487
488
# File 'lib/sup/modes/thread_index_mode.rb', line 485

def kill
  t = cursor_thread or return
  multi_kill [t]
end

#launch_another_thread(thread, direction, &b) ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/sup/modes/thread_index_mode.rb', line 151

def launch_another_thread thread, direction, &b
  l = @lines[thread] or return
  target_l = l + direction
  t = @mutex.synchronize do
    if target_l >= 0 && target_l < @threads.length
      @threads[target_l]
    end
  end

  if t # there's a next thread
    set_cursor_pos target_l # move out of mutex?
    select t, b
  elsif b # no next thread. call the block anyways
    b.call
  end
end

#launch_next_thread_after(thread, &b) ⇒ Object

these two methods are called by thread-view-modes when the user wants to view the previous/next thread without going back to index-mode. we update the cursor as a convenience.



143
144
145
# File 'lib/sup/modes/thread_index_mode.rb', line 143

def launch_next_thread_after thread, &b
  launch_another_thread thread, 1, &b
end

#launch_prev_thread_before(thread, &b) ⇒ Object



147
148
149
# File 'lib/sup/modes/thread_index_mode.rb', line 147

def launch_prev_thread_before thread, &b
  launch_another_thread thread, -1, &b
end

#linesObject



94
# File 'lib/sup/modes/thread_index_mode.rb', line 94

def lines; @text.length; end

#load_all_threadsObject



706
707
708
# File 'lib/sup/modes/thread_index_mode.rb', line 706

def load_all_threads
  load_threads :num => -1
end

#load_n_threads(n = LOAD_MORE_THREAD_NUM, opts = {}) ⇒ Object

TODO: figure out @ts_mutex in this method



665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
# File 'lib/sup/modes/thread_index_mode.rb', line 665

def load_n_threads n=LOAD_MORE_THREAD_NUM, opts={}
  @interrupt_search = false
  @mbid = BufferManager.say "Searching for threads..."

  ts_to_load = n
  ts_to_load = ts_to_load + @ts.size unless n == -1 # -1 means all threads

  orig_size = @ts.size
  last_update = Time.now
  @ts.load_n_threads(ts_to_load, opts) do |i|
    if (Time.now - last_update) >= 0.25
      BufferManager.say "Loaded #{i.pluralize 'thread'}...", @mbid
      update
      BufferManager.draw_screen
      last_update = Time.now
    end
    ::Thread.pass
    break if @interrupt_search
  end
  @ts.threads.each { |th| th.labels.each { |l| LabelManager << l } }

  update
  BufferManager.clear @mbid
  @mbid = nil
  BufferManager.draw_screen
  @ts.size - orig_size
end

#load_n_threads_background(n = LOAD_MORE_THREAD_NUM, opts = {}) ⇒ Object



655
656
657
658
659
660
661
662
# File 'lib/sup/modes/thread_index_mode.rb', line 655

def load_n_threads_background n=LOAD_MORE_THREAD_NUM, opts={}
  return if @load_thread # todo: wrap in mutex
  @load_thread = Redwood::reporting_thread("load threads for thread-index-mode") do
    num = load_n_threads n, opts
    opts[:when_done].call(num) if opts[:when_done]
    @load_thread = nil
  end
end

#load_threads(opts = {}) ⇒ Object



710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
# File 'lib/sup/modes/thread_index_mode.rb', line 710

def load_threads opts={}
  if opts[:num].nil?
    n = ThreadIndexMode::LOAD_MORE_THREAD_NUM
  else
    n = opts[:num]
  end

  myopts = @load_thread_opts.merge({ :when_done => (lambda do |num|
    opts[:when_done].call(num) if opts[:when_done]

    if num > 0
      BufferManager.flash "Found #{num.pluralize 'thread'}."
    else
      BufferManager.flash "No matches."
    end
  end)})

  if opts[:background] || opts[:background].nil?
    load_n_threads_background n, myopts
  else
    load_n_threads n, myopts
  end
end

#multi_edit_labels(threads) ⇒ Object



597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
# File 'lib/sup/modes/thread_index_mode.rb', line 597

def multi_edit_labels threads
  user_labels = BufferManager.ask_for_labels :labels, "Add/remove labels (use -label to remove): ", [], @hidden_labels
  return unless user_labels

  user_labels.map! { |l| (l.to_s =~ /^-/)? [l.to_s.gsub(/^-?/, '').to_sym, true] : [l, false] }
  hl = user_labels.select { |(l,_)| @hidden_labels.member? l }
  unless hl.empty?
    BufferManager.flash "'#{hl}' is a reserved label!"
    return
  end

  old_labels = threads.map { |t| t.labels.dup }

  threads.each do |t|
    user_labels.each do |(l, to_remove)|
      if to_remove
        t.remove_label l
      else
        t.apply_label l
        LabelManager << l
      end
    end
    UpdateManager.relay self, :labeled, t.first
  end

  regen_text

  UndoManager.register "labeling #{threads.size.pluralize 'thread'}" do
    threads.zip(old_labels).map do |t, old_labels|
      t.labels = old_labels
      UpdateManager.relay self, :labeled, t.first
      Index.save_thread t
    end
    regen_text
  end

  threads.each { |t| Index.save_thread t }
end

#multi_join_threads(threads) ⇒ Object



429
430
431
432
433
434
# File 'lib/sup/modes/thread_index_mode.rb', line 429

def multi_join_threads threads
  @ts.join_threads threads or return
  threads.each { |t| Index.save_thread t }
  @tags.drop_all_tags # otherwise we have tag pointers to invalid threads!
  update
end

#multi_kill(threads) ⇒ Object

m-m-m-m-MULTI-KILL



497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
# File 'lib/sup/modes/thread_index_mode.rb', line 497

def multi_kill threads
  UndoManager.register "killing/unkilling #{threads.size.pluralize 'threads'}" do
    threads.each do |t|
      if t.toggle_label :killed
        add_or_unhide t.first
      else
        hide_thread t
      end
    end.each do |t|
      UpdateManager.relay self, :labeled, t.first
      Index.save_thread t
    end
    regen_text
  end

  threads.each do |t|
    if t.toggle_label :killed
      hide_thread t
    else
      add_or_unhide t.first
    end
  end.each do |t|
    # send 'labeled'... this might be more specific
    UpdateManager.relay self, :labeled, t.first
    Index.save_thread t
  end

  killed, unkilled = threads.partition { |t| t.has_label? :killed }.map(&:size)
  BufferManager.flash "#{killed.pluralize 'thread'} killed, #{unkilled} unkilled"
  regen_text
end

#multi_select(threads) ⇒ Object



136
137
138
# File 'lib/sup/modes/thread_index_mode.rb', line 136

def multi_select threads
  threads.each { |t| select t }
end

#multi_toggle_archived(threads) ⇒ Object



396
397
398
399
400
401
402
# File 'lib/sup/modes/thread_index_mode.rb', line 396

def multi_toggle_archived threads
  undos = threads.map { |t| actually_toggle_archived t }
  UndoManager.register "deleting/undeleting #{threads.size.pluralize 'thread'}", undos, lambda { regen_text },
                       lambda { threads.each { |t| Index.save_thread t } }
  regen_text
  threads.each { |t| Index.save_thread t }
end

#multi_toggle_deleted(threads) ⇒ Object

see comment for multi_toggle_spam



477
478
479
480
481
482
483
# File 'lib/sup/modes/thread_index_mode.rb', line 477

def multi_toggle_deleted threads
  undos = threads.map { |t| actually_toggle_deleted t }
  UndoManager.register "deleting/undeleting #{threads.size.pluralize 'thread'}",
                       undos, lambda { regen_text }, lambda { threads.each { |t| Index.save_thread t } }
  regen_text
  threads.each { |t| Index.save_thread t }
end

#multi_toggle_new(threads) ⇒ Object



412
413
414
415
416
# File 'lib/sup/modes/thread_index_mode.rb', line 412

def multi_toggle_new threads
  threads.each { |t| t.toggle_label :unread }
  regen_text
  threads.each { |t| Index.save_thread t }
end

#multi_toggle_spam(threads) ⇒ Object

both spam and deleted have the curious characteristic that you always want to hide the thread after either applying or removing that label. in all thread-index-views except for label-search-results-mode, when you mark a message as spam or deleted, you want it to disappear immediately; in LSRM, you only see deleted or spam emails, and when you undelete or unspam them you also want them to disappear immediately.



462
463
464
465
466
467
468
469
# File 'lib/sup/modes/thread_index_mode.rb', line 462

def multi_toggle_spam threads
  undos = threads.map { |t| actually_toggle_spammed t }
  threads.each { |t| HookManager.run("mark-as-spam", :thread => t) }
  UndoManager.register "marking/unmarking  #{threads.size.pluralize 'thread'} as spam",
                       undos, lambda { regen_text }, lambda { threads.each { |t| Index.save_thread t } }
  regen_text
  threads.each { |t| Index.save_thread t }
end

#multi_toggle_starred(threads) ⇒ Object



309
310
311
312
313
314
315
# File 'lib/sup/modes/thread_index_mode.rb', line 309

def multi_toggle_starred threads
  UndoManager.register "toggling #{threads.size.pluralize 'thread'} starred status",
    threads.map { |t| actually_toggle_starred t },
    lambda { threads.each { |t| Index.save_thread t } }
  regen_text
  threads.each { |t| Index.save_thread t }
end

#multi_toggle_tagged(threads) ⇒ Object



418
419
420
421
# File 'lib/sup/modes/thread_index_mode.rb', line 418

def multi_toggle_tagged threads
  @mutex.synchronize { @tags.drop_all_tags }
  regen_text
end

#reloadObject



98
99
100
101
102
103
# File 'lib/sup/modes/thread_index_mode.rb', line 98

def reload
  drop_all_threads
  UndoManager.clear
  BufferManager.draw_screen
  load_threads :num => buffer.content_height
end

#reply(type_arg = nil) ⇒ Object



636
637
638
639
640
641
642
643
# File 'lib/sup/modes/thread_index_mode.rb', line 636

def reply type_arg=nil
  t = cursor_thread or return
  m = t.latest_message
  return if m.nil? # probably won't happen
  m.load_from_source!
  mode = ReplyMode.new m, type_arg
  BufferManager.spawn "Reply to #{m.subj}", mode
end

#reply_allObject



645
# File 'lib/sup/modes/thread_index_mode.rb', line 645

def reply_all; reply :all; end

#resize(rows, cols) ⇒ Object



735
736
737
738
# File 'lib/sup/modes/thread_index_mode.rb', line 735

def resize rows, cols
  regen_text
  super
end

#select(t = nil, when_done = nil) ⇒ Object

open up a thread view window



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/sup/modes/thread_index_mode.rb', line 106

def select t=nil, when_done=nil
  t ||= cursor_thread or return

  Redwood::reporting_thread("load messages for thread-view-mode") do
    num = t.size
    message = "Loading #{num.pluralize 'message body'}..."
    BufferManager.say(message) do |sid|
      t.each_with_index do |(m, *_), i|
        next unless m
        BufferManager.say "#{message} (#{i}/#{num})", sid if t.size > 1
        m.load_from_source!
      end
    end
    mode = ThreadViewMode.new t, @hidden_labels, self
    BufferManager.spawn t.subj, mode
    BufferManager.draw_screen
    mode.jump_to_first_open if $config[:jump_to_open_message]
    BufferManager.draw_screen # lame TODO: make this unnecessary
    ## the first draw_screen is needed before topline and botline
    ## are set, and the second to show the cursor having moved

    t.remove_label :unread
    Index.save_thread t

    update_text_for_line curpos
    UpdateManager.relay self, :read, t.first
    when_done.call if when_done
  end
end

#statusObject



694
695
696
697
698
699
700
# File 'lib/sup/modes/thread_index_mode.rb', line 694

def status
  if (l = lines) == 0
    "line 0 of 0"
  else
    "line #{curpos + 1} of #{l}"
  end
end

#tag_matchingObject



555
556
557
558
559
560
561
562
563
564
565
566
# File 'lib/sup/modes/thread_index_mode.rb', line 555

def tag_matching
  query = BufferManager.ask :search, "tag threads matching (regex): "
  return if query.nil? || query.empty?
  query = begin
    /#{query}/i
  rescue RegexpError => e
    BufferManager.flash "error interpreting '#{query}': #{e.message}"
    return
  end
  @mutex.synchronize { @threads.each { |t| @tags.tag t if thread_matches?(t, query) } }
  regen_text
end

#toggle_archivedObject



387
388
389
390
391
392
393
394
# File 'lib/sup/modes/thread_index_mode.rb', line 387

def toggle_archived
  t = cursor_thread or return
  undo = actually_toggle_archived t
  UndoManager.register "deleting/undeleting thread #{t.first.id}", undo, lambda { update_text_for_line curpos },
                       lambda { Index.save_thread t }
  update_text_for_line curpos
  Index.save_thread t
end

#toggle_deletedObject



471
472
473
474
# File 'lib/sup/modes/thread_index_mode.rb', line 471

def toggle_deleted
  t = cursor_thread or return
  multi_toggle_deleted [t]
end

#toggle_newObject



404
405
406
407
408
409
410
# File 'lib/sup/modes/thread_index_mode.rb', line 404

def toggle_new
  t = cursor_thread or return
  t.toggle_label :unread
  update_text_for_line curpos
  cursor_down
  Index.save_thread t
end

#toggle_spamObject



450
451
452
453
# File 'lib/sup/modes/thread_index_mode.rb', line 450

def toggle_spam
  t = cursor_thread or return
  multi_toggle_spam [t]
end

#toggle_starredObject



300
301
302
303
304
305
306
307
# File 'lib/sup/modes/thread_index_mode.rb', line 300

def toggle_starred
  t = cursor_thread or return
  undo = actually_toggle_starred t
  UndoManager.register "toggling thread starred status", undo, lambda { Index.save_thread t }
  update_text_for_line curpos
  cursor_down
  Index.save_thread t
end

#toggle_taggedObject



543
544
545
546
547
548
# File 'lib/sup/modes/thread_index_mode.rb', line 543

def toggle_tagged
  t = cursor_thread or return
  @mutex.synchronize { @tags.toggle_tag_for t }
  update_text_for_line curpos
  cursor_down
end

#toggle_tagged_allObject



550
551
552
553
# File 'lib/sup/modes/thread_index_mode.rb', line 550

def toggle_tagged_all
  @mutex.synchronize { @threads.each { |t| @tags.toggle_tag_for t } }
  regen_text
end

#undoObject



249
250
251
# File 'lib/sup/modes/thread_index_mode.rb', line 249

def undo
  UndoManager.undo
end

#unsaved?Boolean

Returns:

  • (Boolean)


93
# File 'lib/sup/modes/thread_index_mode.rb', line 93

def unsaved?; dirty? end

#updateObject



253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/sup/modes/thread_index_mode.rb', line 253

def update
  old_cursor_thread = cursor_thread
  @mutex.synchronize do
    ## let's see you do THIS in python
    @threads = @ts.threads.select { |t| !@hidden_threads.member?(t) }.select(&:has_message?).sort_by(&:sort_key)
    @size_widgets = @threads.map { |t| size_widget_for_thread t }
    @size_widget_width = @size_widgets.max_of { |w| w.display_length }
    @date_widgets = @threads.map { |t| date_widget_for_thread t }
    @date_widget_width = @date_widgets.max_of { |w| w.display_length }
  end
  set_cursor_pos @threads.index(old_cursor_thread)||curpos

  regen_text
end