sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/
commit 0f762e1951fe5de16015a3ae64b33da85ac3a33d
parent e36285adffeb0f27a330bfd02a366f5e87ace84e
Author: William Morgan <wmorgan-sup@masanjin.net>
Date:   Mon, 30 Nov 2009 08:42:05 -0500

Merge branch 'master' into next

Diffstat:
M lib/sup.rb | 1 +
M lib/sup/account.rb | 11 +++++++++++
M lib/sup/message.rb | 2 ++
M lib/sup/modes/compose-mode.rb | 2 +-
M lib/sup/modes/forward-mode.rb | 6 +++---
M lib/sup/modes/thread-view-mode.rb | 28 ++++++++++++++++++++--------
M lib/sup/poll.rb | 9 ++++++---
M lib/sup/textfield.rb | 27 ++++++++++++++++++++++++---
M lib/sup/xapian_index.rb | 2 +-
9 files changed, 69 insertions(+), 19 deletions(-)
diff --git a/lib/sup.rb b/lib/sup.rb
@@ -221,6 +221,7 @@ else
     :editor => ENV["EDITOR"] || "/usr/bin/vim -f -c 'setlocal spell spelllang=en_us' -c 'set filetype=mail'",
     :thread_by_subject => false,
     :edit_signature => false,
+    :ask_for_to => true,
     :ask_for_cc => true,
     :ask_for_bcc => false,
     :ask_for_subject => true,
diff --git a/lib/sup/account.rb b/lib/sup/account.rb
@@ -10,6 +10,17 @@ class Account < Person
     @sendmail = h[:sendmail]
     @signature = h[:signature]
   end
+
+  # Default sendmail command for bouncing mail,
+  # deduced from #sendmail
+  def bounce_sendmail
+    sendmail.sub(/\s(\-(ti|it|t))\b/) do |match|
+      case $1
+      when '-t' then ''
+      else ' -i'
+      end
+    end
+  end
 end
 
 class AccountManager
diff --git a/lib/sup/message.rb b/lib/sup/message.rb
@@ -494,6 +494,8 @@ private
 
       ## if there's a filename, we'll treat it as an attachment.
       if filename
+        ## filename could be 2047 encoded
+        filename = Rfc2047.decode_to $encoding, filename
         # add this to the attachments list if its not a generated html
         # attachment (should we allow images with generated names?).
         # Lowercase the filename because searches are easier that way 
diff --git a/lib/sup/modes/compose-mode.rb b/lib/sup/modes/compose-mode.rb
@@ -21,7 +21,7 @@ class ComposeMode < EditMessageMode
   end
 
   def self.spawn_nicely opts={}
-    to = opts[:to] || BufferManager.ask_for_contacts(:people, "To: ", [opts[:to_default]]) or return
+    to = opts[:to] || (BufferManager.ask_for_contacts(:people, "To: ", [opts[:to_default]]) or return if ($config[:ask_for_to] != false))
     cc = opts[:cc] || (BufferManager.ask_for_contacts(:people, "Cc: ") or return if $config[:ask_for_cc])
     bcc = opts[:bcc] || (BufferManager.ask_for_contacts(:people, "Bcc: ") or return if $config[:ask_for_bcc])
     subj = opts[:subj] || (BufferManager.ask(:subject, "Subject: ") or return if $config[:ask_for_subject])
diff --git a/lib/sup/modes/forward-mode.rb b/lib/sup/modes/forward-mode.rb
@@ -29,9 +29,9 @@ class ForwardMode < EditMessageMode
   end
 
   def self.spawn_nicely opts={}
-    to = opts[:to] || BufferManager.ask_for_contacts(:people, "To: ") or return
-    cc = opts[:cc] || BufferManager.ask_for_contacts(:people, "Cc: ") or return if $config[:ask_for_cc]
-    bcc = opts[:bcc] || BufferManager.ask_for_contacts(:people, "Bcc: ") or return if $config[:ask_for_bcc]
+    to = opts[:to] || (BufferManager.ask_for_contacts(:people, "To: ") or return if ($config[:ask_for_to] != false))
+    cc = opts[:cc] || (BufferManager.ask_for_contacts(:people, "Cc: ") or return if $config[:ask_for_cc])
+    bcc = opts[:bcc] || (BufferManager.ask_for_contacts(:people, "Bcc: ") or return if $config[:ask_for_bcc])
     
     attachment_hash = {}
     attachments = opts[:attachments] || []
diff --git a/lib/sup/modes/thread-view-mode.rb b/lib/sup/modes/thread-view-mode.rb
@@ -10,7 +10,7 @@ class ThreadViewMode < LineCursorMode
     attr_accessor :state
   end
 
-  DATE_FORMAT = "%B %e %Y %l:%M%P"
+  DATE_FORMAT = "%B %e %Y %l:%M%p"
   INDENT_SPACES = 2 # how many spaces to indent child messages
 
   HookManager.register "detailed-headers", <<EOS
@@ -203,12 +203,7 @@ EOS
     m = @message_lines[curpos] or return
     to = BufferManager.ask_for_contacts(:people, "Bounce To: ") or return
 
-    defcmd = AccountManager.default_account.sendmail.sub(/\s(\-(ti|it|t))\b/) do |match|
-      case "$1"
-        when '-t' then ''
-        else ' -i'
-      end
-    end
+    defcmd = AccountManager.default_account.bounce_sendmail
 
     cmd = case (hookcmd = HookManager.run "bounce-command", :from => m.from, :to => to)
           when nil, /^$/ then defcmd
@@ -255,7 +250,8 @@ EOS
   end    
 
   def edit_labels
-    reserved_labels = @thread.labels.select { |l| LabelManager::RESERVED_LABELS.include? l }
+    old_labels = @thread.labels
+    reserved_labels = old_labels.select { |l| LabelManager::RESERVED_LABELS.include? l }
     new_labels = BufferManager.ask_for_labels :label, "Labels for thread: ", @thread.labels
 
     return unless new_labels
@@ -263,6 +259,10 @@ EOS
     new_labels.each { |l| LabelManager << l }
     update
     UpdateManager.relay self, :labeled, @thread.first
+    UndoManager.register "labeling thread" do
+      @thread.labels = old_labels
+      UpdateManager.relay self, :labeled, @thread.first
+    end
   end
 
   def toggle_starred
@@ -503,6 +503,10 @@ EOS
     dispatch op do
       @thread.remove_label :inbox
       UpdateManager.relay self, :archived, @thread.first
+      UndoManager.register "archiving 1 thread" do
+        @thread.apply_label :inbox
+        UpdateManager.relay self, :unarchived, @thread.first
+      end
     end
   end
 
@@ -510,6 +514,10 @@ EOS
     dispatch op do
       @thread.apply_label :spam
       UpdateManager.relay self, :spammed, @thread.first
+      UndoManager.register "marking 1 thread as spam" do
+        @thread.remove_label :spam
+        UpdateManager.relay self, :unspammed, @thread.first
+      end
     end
   end
 
@@ -517,6 +525,10 @@ EOS
     dispatch op do
       @thread.apply_label :deleted
       UpdateManager.relay self, :deleted, @thread.first
+      UndoManager.register "deleting 1 thread" do
+        @thread.remove_label :deleted
+        UpdateManager.relay self, :undeleted, @thread.first
+      end
     end
   end
 
diff --git a/lib/sup/poll.rb b/lib/sup/poll.rb
@@ -44,9 +44,9 @@ EOS
     HookManager.run "before-poll"
 
     BufferManager.flash "Polling for new messages..."
-    num, numi, from_and_subj, from_and_subj_inbox = @mode.poll
+    num, numi, from_and_subj, from_and_subj_inbox, loaded_labels = @mode.poll
     if num > 0
-      BufferManager.flash "Loaded #{num.pluralize 'new message'}, #{numi} to inbox." 
+      BufferManager.flash "Loaded #{num.pluralize 'new message'}, #{numi} to inbox. Labels: #{loaded_labels.map{|l| l.to_s}.join(', ')}"
     else
       BufferManager.flash "No new messages." 
     end
@@ -91,6 +91,7 @@ EOS
     total_num = total_numi = 0
     from_and_subj = []
     from_and_subj_inbox = []
+    loaded_labels = Set.new
 
     @mutex.synchronize do
       @poll_sources.each do |source|
@@ -122,6 +123,7 @@ EOS
           else
             yield "Found new message at #{m.source_info} with labels #{m.labels.to_a * ','}"
             add_new_message m
+            loaded_labels.merge m.labels
             num += 1
             from_and_subj << [m.from && m.from.longname, m.subj]
             if (m.labels & [:inbox, :spam, :deleted, :killed]) == Set.new([:inbox])
@@ -136,11 +138,12 @@ EOS
         total_numi += numi
       end
 
+      loaded_labels = loaded_labels - LabelManager::HIDDEN_RESERVED_LABELS - [:inbox, :killed]
       yield "Done polling; loaded #{total_num} new messages total"
       @last_poll = Time.now
       @polling = false
     end
-    [total_num, total_numi, from_and_subj, from_and_subj_inbox]
+    [total_num, total_numi, from_and_subj, from_and_subj_inbox, loaded_labels]
   end
 
   ## like Source#each, but yields successive Message objects, which have their
diff --git a/lib/sup/textfield.rb b/lib/sup/textfield.rb
@@ -102,12 +102,21 @@ class TextField
         Ncurses::Form::REQ_DEL_CHAR
       when Ncurses::KEY_BACKSPACE, 127 # 127 is also a backspace keysym
         Ncurses::Form::REQ_DEL_PREV
-      when 1 #ctrl-a
+      when ?\C-a
+        nop
         Ncurses::Form::REQ_BEG_FIELD
-      when 5 #ctrl-e
+      when ?\C-e
         Ncurses::Form::REQ_END_FIELD
-      when 11 # ctrl-k
+      when ?\C-k
         Ncurses::Form::REQ_CLR_EOF
+      when ?\C-u
+        set_cursed_value cursed_value_after_point
+        Ncurses::Form.form_driver @form, Ncurses::Form::REQ_END_FIELD
+        nop
+        Ncurses::Form::REQ_BEG_FIELD
+      when ?\C-w
+        Ncurses::Form.form_driver @form, Ncurses::Form::REQ_PREV_CHAR
+        Ncurses::Form.form_driver @form, Ncurses::Form::REQ_DEL_WORD
       when Ncurses::KEY_UP, Ncurses::KEY_DOWN
         unless @history.empty?
           value = get_cursed_value
@@ -156,5 +165,17 @@ private
   def set_cursed_value v
     @field.set_field_buffer 0, v
   end
+
+  def cursed_value_after_point
+    point = Ncurses.curx - @question.length
+    get_cursed_value[point..-1]
+  end
+
+  ## this is almost certainly unnecessary, but it's the only way
+  ## i could get ncurses to remember my form's value
+  def nop
+    Ncurses::Form.form_driver @form, " "[0]
+    Ncurses::Form.form_driver @form, Ncurses::Form::REQ_DEL_PREV
+  end
 end
 end
diff --git a/lib/sup/xapian_index.rb b/lib/sup/xapian_index.rb
@@ -3,7 +3,7 @@ require 'set'
 
 module Redwood
 
-# This index implementation uses Xapian for searching and GDBM for storage. It
+# This index implementation uses Xapian for searching and storage. It
 # tends to be slightly faster than Ferret for indexing and significantly faster
 # for searching due to precomputing thread membership.
 class XapianIndex < BaseIndex