]> git.notmuchmail.org Git - sup/commitdiff
Merge branches 'prev-next-improv', 'fix-warnings', 'mime-view', 'charset', 'join...
authorWilliam Morgan <wmorgan-sup@masanjin.net>
Tue, 22 Jan 2008 20:21:53 +0000 (12:21 -0800)
committerWilliam Morgan <wmorgan-sup@masanjin.net>
Tue, 22 Jan 2008 20:21:53 +0000 (12:21 -0800)
lib/sup/maildir.rb
lib/sup/message-chunks.rb
lib/sup/message.rb
lib/sup/modes/reply-mode.rb
lib/sup/modes/thread-index-mode.rb
lib/sup/tagger.rb
lib/sup/thread.rb
lib/sup/util.rb

index ba9da001bc60cfbb9537578378e380d7999ea7d4..7b85383a8b15d694d1651195b81e473435fe7282 100644 (file)
@@ -144,7 +144,7 @@ private
 
   def make_id fn
     # use 7 digits for the size. why 7? seems nice.
-    sprintf("%d%07d", File.mtime(fn), File.size(fn)).to_i
+    sprintf("%d%07d", File.mtime(fn), File.size(fn) % 10000000).to_i
   end
 
   def with_file_for id
index 08dcf27e06c1847ca888569645c78e7be9c744b0..8e5def9eaaeecffabe2f62c90b1418b242460da6 100644 (file)
@@ -53,6 +53,16 @@ Variables:
 Return value:
   The decoded text of the attachment, or nil if not decoded.
 EOS
+
+    HookManager.register "mime-view", <<EOS
+Executes when viewing a MIME attachment, i.e., launching a separate
+viewer program.
+Variables:
+   content_type: the content-type of the attachment
+       filename: the filename of the attachment as saved to disk
+Return value:
+  True if the viewing was successful, false otherwise.
+EOS
 #' stupid ruby-mode
 
     ## raw_content is the post-MIME-decode content. this is used for
@@ -105,12 +115,18 @@ EOS
     def expandable?; !viewable? end
     def initial_state; :open end
     def viewable?; @lines.nil? end
-    def view!
-      path = write_to_disk
+    def view_default! path
       system "/usr/bin/run-mailcap --action=view #{@content_type}:#{path} > /dev/null 2> /dev/null"
       $? == 0
     end
 
+    def view!
+      path = write_to_disk
+      ret = HookManager.run "mime-view", :content_type => @content_type,
+                                         :filename => path
+      view_default! path unless ret
+    end
+
     def write_to_disk
       file = Tempfile.new(@filename || "sup-attachment")
       file.print @raw_content
index ef5e3d1e208b90544ea47625598e4da85a137b8b..853f2fccb3676d4df05759d4b3e81e19df04efd4 100644 (file)
@@ -55,6 +55,10 @@ class Message
     @encrypted = false
     @chunks = nil
 
+    ## we need to initialize this. see comments in parse_header as to
+    ## why.
+    @refs = []
+
     parse_header(opts[:header] || @source.load_header(@source_info))
   end
 
@@ -102,7 +106,13 @@ class Message
     @to = PersonManager.people_for header["to"]
     @cc = PersonManager.people_for header["cc"]
     @bcc = PersonManager.people_for header["bcc"]
-    @refs = (header["references"] || "").scan(/<(.+?)>/).map { |x| sanitize_message_id x.first }
+
+    ## before loading our full header from the source, we can actually
+    ## have some extra refs set by the UI. (this happens when the user
+    ## joins threads manually). so we will merge the current refs values
+    ## in here.
+    refs = (header["references"] || "").scan(/<(.+?)>/).map { |x| sanitize_message_id x.first }
+    @refs = (@refs + refs).uniq
     @replytos = (header["in-reply-to"] || "").scan(/<(.+?)>/).map { |x| sanitize_message_id x.first }
 
     @replyto = PersonManager.person_for header["reply-to"]
@@ -120,6 +130,11 @@ class Message
   end
   private :parse_header
 
+  def add_ref ref
+    @refs << ref
+    @dirty = true
+  end
+
   def snippet; @snippet || (chunks && @snippet); end
   def is_list_message?; !@list_address.nil?; end
   def is_draft?; @source.is_a? DraftLoader; end
index 2d05f15208de168aa7ad1e41dc0a4a75791839cb..3b8c920142fe00c57fc4f7369a14da620949ff25 100644 (file)
@@ -10,10 +10,11 @@ class ReplyMode < EditMessageMode
     :user => "Customized"
   }
 
-  HookManager.register "quoteline", <<EOS
-Generates a quote line "On 1/4/2007, Joe Bloggs wrote:".
+  HookManager.register "attribution", <<EOS
+Generates an attribution ("Excerpts from Joe Bloggs's message of Fri Jan 11 09:54:32 -0500 2008:").
 Variables:
-      message: A message object representing the message being replied to
+  message: a message object representing the message being replied to
+    (useful values include message.from.name and message.date)
 Return value:
   A string containing the text of the quote line (can be multi-line)
 EOS
@@ -123,13 +124,13 @@ protected
   end
 
   def reply_body_lines m
-    quoteline = HookManager.run("quoteline", :message => m) || default_quoteline(m)
-    lines = quoteline.split("\n") + m.quotable_body_lines.map { |l| "> #{l}" }
+    attribution = HookManager.run("attribution", :message => m) || default_attribution(m)
+    lines = attribution.split("\n") + m.quotable_body_lines.map { |l| "> #{l}" }
     lines.pop while lines.last =~ /^\s*$/
     lines
   end
 
-  def default_quoteline m
+  def default_attribution m
     "Excerpts from #{@m.from.name}'s message of #{@m.date}:"
   end
 
index 0d90be48f1ee9b4eae2a6cf8064a2cfed16a4cc8..dec3c1df47ad2db9bb24b2fd660d961955baef91 100644 (file)
@@ -33,6 +33,7 @@ EOS
     k.add :toggle_tagged_all, "Tag/untag all threads", 'T'
     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={}
@@ -278,6 +279,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 } ||
index 6e4bab3c67cc4f8beb49a3f4552f7752c6a9b132..3e72463fc0e90128c5dd39e4c3f3535d9bc2cd7f 100644 (file)
@@ -13,7 +13,7 @@ class Tagger
   def drop_all_tags; @tagged.clear; end
   def drop_tag_for o; @tagged.delete o; end
 
-  def apply_to_tagged
+  def apply_to_tagged action=nil
     targets = @tagged.select_by_value
     num_tagged = targets.size
     if num_tagged == 0
@@ -22,10 +22,14 @@ class Tagger
     end
 
     noun = num_tagged == 1 ? "thread" : "threads"
-    c = BufferManager.ask_getch "apply to #{num_tagged} tagged #{noun}:"
-    return if c.nil? # user cancelled
 
-    if(action = @mode.resolve_input(c))
+    unless action
+      c = BufferManager.ask_getch "apply to #{num_tagged} tagged #{noun}:"
+      return if c.nil? # user cancelled
+      action = @mode.resolve_input c
+    end
+
+    if action
       tagged_sym = "multi_#{action}".intern
       if @mode.respond_to? tagged_sym
         @mode.send tagged_sym, targets
index 83f0db71aa4a08adfda53bd469d234ba684ac338..32002c4062cb0e9d7aabf317375cf22eafdeee65 100644 (file)
@@ -207,7 +207,7 @@ class Container
   def subj; find_attr :subj; end
   def date; find_attr :date; end
 
-  def is_reply?; subj && Message.subject_is_reply?(subj); end
+  def is_reply?; subj && Message.subj_is_reply?(subj); end
 
   def to_s
     [ "<#{id}",
@@ -349,6 +349,32 @@ class ThreadSet
     t.each { |m, *o| add_message m }
   end
 
+  ## merges two threads together. both must be members of this threadset.
+  ## does its best, heuristically, to determine which is the parent.
+  def join_threads threads
+    return if threads.size < 2
+
+    containers = threads.map do |t|
+      c = @messages[t.first.id]
+      raise "not in threadset: #{t.first.id}" unless c && c.message
+      c
+    end
+
+    ## use subject headers heuristically
+    parent = containers.find { |c| !c.is_reply? }
+
+    ## no thread was rooted by a non-reply, so make a fake parent
+    parent ||= @messages["joining-ref-" + containers.map { |c| c.id }.join("-")]
+
+    containers.each do |c|
+      next if c == parent
+      c.message.add_ref parent.id
+      link parent, c
+    end
+
+    true
+  end
+
   def is_relevant? m
     m.refs.any? { |ref_id| @messages.member? ref_id }
   end
index 47bc1c21402a6c8a06f6f54d46c6e0440caa47f8..ceaf0b8dabc8a83ea451005e9b27bc109823caf1 100644 (file)
@@ -62,7 +62,7 @@ module RMail
     end
 
     def charset
-      if header.field?("content-type") && header.fetch("content-type") =~ /charset="?(.*?)"?(;|$)/
+      if header.field?("content-type") && header.fetch("content-type") =~ /charset="?(.*?)"?(;|$)/i
         $1
       end
     end