k.add :forward, "Forward latest message in a thread", 'f'
k.add :toggle_tagged, "Tag/untag selected thread", 't'
k.add :toggle_tagged_all, "Tag/untag all threads", 'T'
- k.add :tag_matching, "Tag/untag all threads", 'g'
+ k.add :tag_matching, "Tag matching threads", 'g'
k.add :apply_to_tagged, "Apply next command to all tagged threads", ';'
+ k.add :join_threads, "Force tagged threads to be joined into the same thread", '#'
end
def initialize hidden_labels=[], load_thread_opts={}
end
## open up a thread view window
- def select t=nil
+ def select t=nil, when_done=nil
t ||= cursor_thread or return
Redwood::reporting_thread("load messages for thread-view-mode") do
m.load_from_source!
end
end
- mode = ThreadViewMode.new t, @hidden_labels
+ mode = ThreadViewMode.new t, @hidden_labels, self
BufferManager.spawn t.subj, mode
BufferManager.draw_screen
- mode.jump_to_first_open
+ mode.jump_to_first_open true
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
update_text_for_line curpos
- UpdateManager.relay self, :read, t
+ UpdateManager.relay self, :read, t.first
+ when_done.call if when_done
end
end
def multi_select threads
threads.each { |t| select t }
end
-
- def handle_label_update sender, m
- t = @ts_mutex.synchronize { @ts.thread_for(m) } or return
- handle_label_thread_update sender, t
- end
- def handle_label_thread_update sender, t
- l = @lines[t] or return
- update_text_for_line l
- BufferManager.draw_screen
+ ## this is called by thread-view-modes when the user wants to view
+ ## the next thread without going to index-mode. we update the cursor
+ ## as a convenience.
+ def launch_next_thread_after thread, &b
+ l = @lines[thread] or return
+ t = @mutex.synchronize do
+ if l < @threads.length - 1
+ set_cursor_pos l + 1 # move out of mutex?
+ @threads[l + 1]
+ end
+ end or return
+
+ select t, b
+ end
+
+ 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
+
+ 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
- def handle_read_update sender, t
+ def handle_simple_update sender, m
+ t = thread_containing(m) or return
l = @lines[t] or return
update_text_for_line l
- BufferManager.draw_screen
end
- def handle_archived_update *a; handle_read_update(*a); end
-
- def handle_deleted_update sender, t
- handle_read_update sender, t
- hide_thread t
- regen_text
+ %w(read unread archived starred unstarred).each do |state|
+ define_method "handle_#{state}_update" do |*a|
+ handle_simple_update(*a)
+ end
end
## overwrite me!
def is_relevant? m; false; end
- def handle_add_update sender, m
- @ts_mutex.synchronize do
- return unless is_relevant?(m) || @ts.is_relevant?(m)
- @ts.load_thread_for_message m
- end
- update
+ def handle_added_update sender, m
+ add_or_unhide m
BufferManager.draw_screen
end
- def handle_delete_update sender, mid
+ def handle_single_message_deleted_update sender, m
@ts_mutex.synchronize do
- return unless @ts.contains_id? mid
- @ts.remove mid
+ return unless @ts.contains? m
+ @ts.remove_id m.id
end
update
- BufferManager.draw_screen
+ end
+
+ def handle_deleted_update sender, m
+ t = @ts_mutex.synchronize { @ts.thread_for m }
+ return unless t
+ hide_thread t
+ update
+ end
+
+ def handle_undeleted_update sender, m
+ add_or_unhide m
end
def update
@mutex.synchronize do
## let's see you do THIS in python
- @threads = @ts.threads.select { |t| !@hidden_threads[t] }.sort_by { |t| t.date }.reverse
+ @threads = @ts.threads.select { |t| !@hidden_threads[t] }.sort_by { |t| [t.date, t.first.id] }.reverse
@size_widgets = @threads.map { |t| size_widget_for_thread t }
@size_widget_width = @size_widgets.max_of { |w| w.length }
end
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
+ UpdateManager.relay self, :unstarred, t.first
else
t.first.add_label :starred # add only to first
- UpdateManager.relay self, :starred, t
+ UpdateManager.relay self, :starred, t.first
end
end
def actually_toggle_archived t
if t.has_label? :inbox
t.remove_label :inbox
- UpdateManager.relay self, :archived, t
+ UpdateManager.relay self, :archived, t.first
else
t.apply_label :inbox
- UpdateManager.relay self, :unarchived, t
+ UpdateManager.relay self, :unarchived, t.first
end
end
def actually_toggle_spammed t
if t.has_label? :spam
t.remove_label :spam
- UpdateManager.relay self, :unspammed, t
+ UpdateManager.relay self, :unspammed, t.first
else
t.apply_label :spam
- UpdateManager.relay self, :spammed, t
+ UpdateManager.relay self, :spammed, t.first
end
end
def actually_toggle_deleted t
if t.has_label? :deleted
t.remove_label :deleted
- UpdateManager.relay self, :undeleted, t
+ UpdateManager.relay self, :undeleted, t.first
else
t.apply_label :deleted
- UpdateManager.relay self, :deleted, t
+ UpdateManager.relay self, :deleted, t.first
end
end
regen_text
end
+ 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
+
+ def multi_join_threads threads
+ @ts.join_threads threads or return
+ @tags.drop_all_tags # otherwise we have tag pointers to invalid threads!
+ update
+ end
+
def jump_to_next_new
n = @mutex.synchronize do
((curpos + 1) ... lines).find { |i| @threads[i].has_label? :unread } ||
query = BufferManager.ask :search, "tag threads matching: "
return if query.nil? || query.empty?
query = /#{query}/i
- @mutex.synchronize { @threads.each { |t| @tags.tag t if thread_match?(t, query) } }
+ @mutex.synchronize { @threads.each { |t| @tags.tag t if thread_matches?(t, query) } }
regen_text
end
return unless user_labels
thread.labels = keepl + user_labels
user_labels.each { |l| LabelManager << l }
- update_text_for_line curpos
+ UpdateManager.relay self, :labeled, thread.first
end
def multi_edit_labels threads
m = t.latest_message
return if m.nil? # probably won't happen
m.load_from_source!
- ForwardMode.spawn_nicely m
+ ForwardMode.spawn_nicely :message => m
end
def load_n_threads_background n=LOAD_MORE_THREAD_NUM, opts={}
protected
+ 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
+ end
+
+ @hidden_threads.delete @ts.thread_for(m)
+ end
+
+ update
+ end
+
+ def thread_containing m; @ts_mutex.synchronize { @ts.thread_for m } end
+
## used to tag threads by query. this can be made a lot more sophisticated,
## but for right now we'll do the obvious this.
- def thread_match? t, query
- t.snippet =~ query || t.participants.any? { |x| x.longname =~ query }
+ def thread_matches? t, query
+ t.subj =~ query || t.snippet =~ query || t.participants.any? { |x| x.longname =~ query }
end
def size_widget_for_thread t