class Redwood::ThreadIndexMode

subclasses should implement:

Constants

AUTHOR_LIMIT
DATE_WIDTH
LOAD_MORE_THREAD_NUM
MIN_FROM_WIDTH

Public Class Methods

new(hidden_labels=[], load_thread_opts={}) click to toggle source
Calls superclass method Redwood::LineCursorMode.new
# File lib/sup/modes/thread_index_mode.rb, line 60
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

Public Instance Methods

[](i;) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 96
def [] i; @text[i]; end
actually_toggle_archived(t) click to toggle source

returns an undo lambda

# File lib/sup/modes/thread_index_mode.rb, line 330
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) click to toggle source

returns an undo lambda

# File lib/sup/modes/thread_index_mode.rb, line 377
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) click to toggle source

returns an undo lambda

# File lib/sup/modes/thread_index_mode.rb, line 353
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) click to toggle source

returns an undo lambda

# File lib/sup/modes/thread_index_mode.rb, line 292
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_tagged() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 580
def apply_to_tagged; @tags.apply_to_tagged; end
cleanup() click to toggle source
Calls superclass method Redwood::LineCursorMode#cleanup
# File lib/sup/modes/thread_index_mode.rb, line 541
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;) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 97
def contains_thread? t; @threads.include?(t) end
edit_labels() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 582
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_message() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 280
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_index() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 502
def flush_index
  @flush_id = BufferManager.say "Flushing index..."
  Index.save_index
  BufferManager.clear @flush_id
end
forward() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 659
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) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 199
def handle_added_update sender, m
  add_or_unhide m
  BufferManager.draw_screen
end
handle_deleted_update(sender, m) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 232
def handle_deleted_update sender, m
  t = @ts_mutex.synchronize { @ts.thread_for m }
  return unless t
  hide_thread t
  update
end
handle_killed_update(sender, m) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 239
def handle_killed_update sender, m
  t = @ts_mutex.synchronize { @ts.thread_for m }
  return unless t
  hide_thread t
  update
end
handle_labeled_update(sender, m) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 175
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) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 215
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) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 184
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) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 224
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) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 169
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) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 246
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) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 253
def handle_undeleted_update sender, m
  add_or_unhide m
end
handle_unkilled_update(sender, m) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 257
def handle_unkilled_update sender, m
  add_or_unhide m
end
handle_updated_update(sender, m) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 204
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, sync_back = false
  update_text_for_line l
end
is_relevant?(m;) click to toggle source

overwrite me!

# File lib/sup/modes/thread_index_mode.rb, line 197
def is_relevant? m; false; end
join_threads() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 435
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_new() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 448
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
kill() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 497
def kill
  t = cursor_thread or return
  multi_kill [t]
end
launch_another_thread(thread, direction, &b) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 152
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) click to toggle source

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.

# File lib/sup/modes/thread_index_mode.rb, line 144
def launch_next_thread_after thread, &b
  launch_another_thread thread, 1, &b
end
launch_prev_thread_before(thread, &b) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 148
def launch_prev_thread_before thread, &b
  launch_another_thread thread, -1, &b
end
lines() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 95
def lines; @text.length; end
load_all_threads() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 718
def load_all_threads
  load_threads :num => -1
end
load_n_threads(n=LOAD_MORE_THREAD_NUM, opts={}) click to toggle source

TODO: figure out @ts_mutex in this method

# File lib/sup/modes/thread_index_mode.rb, line 677
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={}) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 667
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={}) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 722
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) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 609
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) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 441
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) click to toggle source

m-m-m-m-MULTI-KILL

# File lib/sup/modes/thread_index_mode.rb, line 509
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_read_and_archive(threads) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 766
def multi_read_and_archive threads
  old_labels = threads.map { |t| t.labels.dup }

  threads.each do |t|
    t.remove_label :unread
    t.remove_label :inbox
    hide_thread t
  end
  regen_text

  UndoManager.register "reading and archiving #{threads.size.pluralize 'thread'}" do
    threads.zip(old_labels).each do |t, l|
      t.labels = l
      add_or_unhide t.first
      Index.save_thread t
    end
    regen_text
  end

  threads.each { |t| Index.save_thread t }
end
multi_select(threads) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 137
def multi_select threads
  threads.each { |t| select t }
end
multi_toggle_archived(threads) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 408
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) click to toggle source

see comment for #multi_toggle_spam

# File lib/sup/modes/thread_index_mode.rb, line 489
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) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 424
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) click to toggle source

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.

# File lib/sup/modes/thread_index_mode.rb, line 474
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) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 321
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) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 430
def multi_toggle_tagged threads
  @mutex.synchronize { @tags.drop_all_tags }
  regen_text
end
read_and_archive() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 747
def read_and_archive
  return unless cursor_thread
  thread = cursor_thread # to make sure lambda only knows about 'old' cursor_thread

  was_unread = thread.labels.member? :unread
  UndoManager.register "reading and archiving thread" do
    thread.apply_label :inbox
    thread.apply_label :unread if was_unread
    add_or_unhide thread.first
    Index.save_thread thread
  end

  cursor_thread.remove_label :unread
  cursor_thread.remove_label :inbox
  hide_thread cursor_thread
  regen_text
  Index.save_thread thread
end
reload() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 99
def reload
  drop_all_threads
  UndoManager.clear
  BufferManager.draw_screen
  load_threads :num => buffer.content_height
end
reply(type_arg=nil) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 648
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_all() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 657
def reply_all; reply :all; end
resize(rows, cols) click to toggle source
Calls superclass method
# File lib/sup/modes/thread_index_mode.rb, line 788
def resize rows, cols
  regen_text
  super
end
select(t=nil, when_done=nil) click to toggle source

open up a thread view window

# File lib/sup/modes/thread_index_mode.rb, line 107
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
status() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 706
def status
  if (l = lines) == 0
    "line 0 of 0"
  else
    "line #{curpos + 1} of #{l}"
  end
end
tag_matching() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 567
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_archived() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 399
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_deleted() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 483
def toggle_deleted
  t = cursor_thread or return
  multi_toggle_deleted [t]
end
toggle_new() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 416
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_spam() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 462
def toggle_spam
  t = cursor_thread or return
  multi_toggle_spam [t]
end
toggle_starred() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 312
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_tagged() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 555
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_all() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 562
def toggle_tagged_all
  @mutex.synchronize { @threads.each { |t| @tags.toggle_tag_for t } }
  regen_text
end
undo() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 261
def undo
  UndoManager.undo
end
unsaved?() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 94
def unsaved?; dirty? end
update() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 265
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

Protected Instance Methods

add_or_unhide(m) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 795
def add_or_unhide m
  @ts_mutex.synchronize do
    if (is_relevant?(m) || @ts.is_relevant?(m)) && !@ts.contains?(m)
      @ts.load_thread_for_message m, @load_thread_opts
    end

    @hidden_threads.delete @ts.thread_for(m)
  end

  update
end
author_names_and_newness_for_thread(t, limit=nil) click to toggle source

preserve author order from the thread

# File lib/sup/modes/thread_index_mode.rb, line 892
def author_names_and_newness_for_thread t, limit=nil
  new = {}
  seen = {}
  authors = t.map do |m, *o|
    next unless m && m.from
    new[m.from] ||= m.has_label?(:unread)
    next if seen[m.from]
    seen[m.from] = true
    m.from
  end.compact

  result = []
  authors.each do |a|
    break if limit && result.size >= limit
    name = if AccountManager.is_account?(a)
      "me"
    elsif t.authors.size == 1
      a.mediumname
    else
      a.shortname
    end

    result << [name, new[a]]
  end

  if result.size == 1 && (author_and_newness = result.assoc("me"))
    unless (recipients = t.participants - t.authors).empty?
      result = recipients.collect do |r|
        break if limit && result.size >= limit
        name = (recipients.size == 1) ? r.mediumname : r.shortname
        ["(#{name})", author_and_newness[1]]
      end
    end
  end

  result
end
authors() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 889
def authors; map { |m, *o| m.from if m }.compact.uniq; end
cursor_thread() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 823
def cursor_thread; @mutex.synchronize { @threads[curpos] }; end
date_widget_for_thread(t) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 819
def date_widget_for_thread t
  HookManager.run("index-mode-date-widget", :thread => t) || default_date_widget_for(t)
end
delete_thread(t) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 831
def delete_thread t
  @mutex.synchronize do
    i = @threads.index(t) or return
    @threads.delete_at i
    @size_widgets.delete_at i
    @date_widgets.delete_at i
    @tags.drop_tag_for t
  end
end
dirty?() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 1011
def dirty?; @mutex.synchronize { (@hidden_threads.keys + @threads).any? { |t| t.dirty? } } end
drop_all_threads() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 825
def drop_all_threads
  @tags.drop_all_tags
  initialize_threads
  update
end
hide_thread(t) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 841
def hide_thread t
  @mutex.synchronize do
    i = @threads.index(t) or return
    raise "already hidden" if @hidden_threads[t]
    @hidden_threads[t] = true
    @threads.delete_at i
    @size_widgets.delete_at i
    @date_widgets.delete_at i
    @tags.drop_tag_for t
  end
end
regen_text() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 882
def regen_text
  threads = @mutex.synchronize { @threads }
  @text = threads.map_with_index { |t, i| text_for_thread_at i }
  @lines = threads.map_with_index { |t, i| [t, i] }.to_h
  buffer.mark_dirty if buffer
end
size_widget_for_thread(t) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 815
def size_widget_for_thread t
  HookManager.run("index-mode-size-widget", :thread => t) || default_size_widget_for(t)
end
text_for_thread_at(line) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 931
def text_for_thread_at line
  t, size_widget, date_widget = @mutex.synchronize do
    [@threads[line], @size_widgets[line], @date_widgets[line]]
  end

  starred = t.has_label? :starred

  ## format the from column
  cur_width = 0
  ann = author_names_and_newness_for_thread t, AUTHOR_LIMIT
  from = []
  ann.each_with_index do |(name, newness), i|
    break if cur_width >= from_width
    last = i == ann.length - 1

    abbrev =
      if cur_width + name.display_length > from_width
        name.slice_by_display_length(from_width - cur_width - 1) + "."
      elsif cur_width + name.display_length == from_width
        name.slice_by_display_length(from_width - cur_width)
      else
        if last
          name.slice_by_display_length(from_width - cur_width)
        else
          name.slice_by_display_length(from_width - cur_width - 1) + ","
        end
      end

    cur_width += abbrev.display_length

    if last && from_width > cur_width
      abbrev += " " * (from_width - cur_width)
    end

    from << [(newness ? :index_new_color : (starred ? :index_starred_color : :index_old_color)), abbrev]
  end

  is_me = AccountManager.method(:is_account?)
  directly_participated = t.direct_participants.any?(&is_me)
  participated = directly_participated || t.participants.any?(&is_me)

  subj_color =
    if t.has_label?(:draft)
      :index_draft_color
    elsif t.has_label?(:unread)
      :index_new_color
    elsif starred
      :index_starred_color
    elsif Colormap.sym_is_defined(:index_subject_color)
      :index_subject_color
    else
      :index_old_color
    end

  size_padding = @size_widget_width - size_widget.display_length
  size_widget_text = sprintf "%#{size_padding}s%s", "", size_widget

  date_padding = @date_widget_width - date_widget.display_length
  date_widget_text = sprintf "%#{date_padding}s%s", "", date_widget

  [
    [:tagged_color, @tags.tagged?(t) ? ">" : " "],
    [:date_color, date_widget_text],
    [:starred_color, (starred ? "*" : " ")],
  ] +
    from +
    [
    [:size_widget_color, size_widget_text],
    [:with_attachment_color , t.labels.member?(:attachment) ? "@" : " "],
    [:to_me_color, directly_participated ? ">" : (participated ? '+' : " ")],
  ] +
    (t.labels - @hidden_labels).sort_by {|x| x.to_s}.map {
          |label| [Colormap.sym_is_defined("label_#{label}_color".to_sym) || :label_color, "#{label} "]
    } +
    [
    [subj_color, t.subj + (t.subj.empty? ? "" : " ")],
    [:snippet_color, t.snippet],
  ]
end
thread_containing(m;) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 807
def thread_containing m; @ts_mutex.synchronize { @ts.thread_for m } end
thread_matches?(t, query) click to toggle source

used to tag threads by query. this can be made a lot more sophisticated, but for right now we'll do the obvious this.

# File lib/sup/modes/thread_index_mode.rb, line 811
def thread_matches? t, query
  t.subj =~ query || t.snippet =~ query || t.participants.any? { |x| x.longname =~ query }
end
update_text_for_line(l) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 853
def update_text_for_line l
  return unless l # not sure why this happens, but it does, occasionally

  need_update = false

  @mutex.synchronize do
    # and certainly not sure why this happens..
    #
    # probably a race condition between thread modification and updating
    # going on.
    return if @threads[l].empty?

    @size_widgets[l] = size_widget_for_thread @threads[l]
    @date_widgets[l] = date_widget_for_thread @threads[l]

    ## if a widget size has increased, we need to redraw everyone
    need_update =
      (@size_widgets[l].size > @size_widget_width) or
      (@date_widgets[l].size > @date_widget_width)
  end

  if need_update
    update
  else
    @text[l] = text_for_thread_at l
    buffer.mark_dirty if buffer
  end
end

Private Instance Methods

default_date_widget_for(t) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 1024
def default_date_widget_for t
  t.date.getlocal.to_nice_s
end
default_size_widget_for(t) click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 1015
def default_size_widget_for t
  case t.size
  when 1
    ""
  else
    "(#{t.size})"
  end
end
from_width() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 1028
def from_width
  [(buffer.content_width.to_f * 0.2).to_i, MIN_FROM_WIDTH].max if buffer else MIN_FROM_WIDTH # not sure why the buffer is gone
end
initialize_threads() click to toggle source
# File lib/sup/modes/thread_index_mode.rb, line 1032
def initialize_threads
  @ts = ThreadSet.new Index.instance, $config[:thread_by_subject]
  @ts_mutex = Mutex.new
  @hidden_threads = {}
end