]> git.notmuchmail.org Git - sup/blobdiff - lib/sup/modes/thread-index-mode.rb
ThreadIndexMode: handle deleted updates correctly
[sup] / lib / sup / modes / thread-index-mode.rb
index 8727e2428f0fd0e99f2fd9aa8ddf3412e8527a89..5985cae25deb3998d8569c6e1d3266244ebd81bc 100644 (file)
@@ -31,8 +31,9 @@ EOS
     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={}
@@ -75,7 +76,7 @@ EOS
   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
@@ -88,73 +89,97 @@ EOS
           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
@@ -176,10 +201,10 @@ EOS
   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  
 
@@ -198,30 +223,30 @@ EOS
   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
 
@@ -253,6 +278,18 @@ EOS
     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 } ||
@@ -356,7 +393,7 @@ EOS
     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
 
@@ -372,7 +409,7 @@ EOS
     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
@@ -404,7 +441,7 @@ EOS
     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={}
@@ -474,10 +511,24 @@ EOS
 
 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