sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/
commit 44aaf4c9e353c5334c8426f36c79cfc2ff8727ba
parent 5ddd953119fd506ac575f3d2bc8fffc01d531b1b
Author: William Morgan <wmorgan-sup@masanjin.net>
Date:   Sat, 16 Feb 2008 06:04:52 -0800

Merge branches 'tag-nouns', 'initial-view-fix', 'id-reordering', 'file-attachment-fix', 'ask-getch-fix' and 'startup-compose'

Diffstat:
M bin/sup | 11 ++++++++---
M lib/sup/buffer.rb | 4 ++--
M lib/sup/index.rb | 12 ++++++++----
M lib/sup/modes/compose-mode.rb | 2 +-
M lib/sup/modes/contact-list-mode.rb | 2 +-
M lib/sup/modes/edit-message-mode.rb | 10 +++++++---
M lib/sup/modes/scroll-mode.rb | 2 +-
M lib/sup/modes/thread-view-mode.rb | 19 +++++++++++--------
M lib/sup/tagger.rb | 6 ++++--
9 files changed, 43 insertions(+), 25 deletions(-)
diff --git a/bin/sup b/bin/sup
@@ -32,10 +32,11 @@ Usage:
 
 Options are:
 EOS
-  opt :list_hooks, "List all hooks and descriptions thereof, and quit."
-  opt :no_threads, "Turn of threading. Helps with debugging. (Necessarily disables background polling for new messages.)"
+  opt :list_hooks, "List all hooks and descriptions, and quit."
+  opt :no_threads, "Turn off threading. Helps with debugging. (Necessarily disables background polling for new messages.)"
   opt :no_initial_poll, "Don't poll for new messages when starting."
-  opt :search, "Search for threads ", :type => String
+  opt :search, "Search for this query upon startup", :type => String
+  opt :compose, "Compose message to this recipient upon startup", :type => String
 end
 
 if $opts[:list_hooks]
@@ -198,6 +199,10 @@ begin
   
   imode.load_threads :num => ibuf.content_height, :when_done => lambda { reporting_thread("poll after loading inbox") { sleep 1; PollManager.poll } unless $opts[:no_threads] || $opts[:no_initial_poll] }
 
+  if $opts[:compose]
+    ComposeMode.spawn_nicely :to_default => $opts[:compose]
+  end
+
   unless $opts[:no_threads]
     PollManager.start
     SuicideManager.start
diff --git a/lib/sup/buffer.rb b/lib/sup/buffer.rb
@@ -455,7 +455,7 @@ EOS
         elsif File.directory?(answer)
           spawn_modal "file browser", FileBrowserMode.new(answer)
         else
-          answer
+          File.expand_path answer
         end
     end
 
@@ -557,7 +557,6 @@ EOS
 
   def ask_getch question, accept=nil
     raise "impossible!" if @asking
-    @asking = true
 
     accept = accept.split(//).map { |x| x[0] } if accept
 
@@ -570,6 +569,7 @@ EOS
       Ncurses.refresh
     end
 
+    @asking = true
     ret = nil
     done = false
     until done
diff --git a/lib/sup/index.rb b/lib/sup/index.rb
@@ -125,9 +125,13 @@ EOS
     @sources[source.id] = source
   end
 
-  def source_for uri; @sources.values.find { |s| s.is_source_for? uri }; end
-  def usual_sources; @sources.values.find_all { |s| s.usual? }; end
-  def sources; @sources.values; end
+  def sources
+    ## favour the inbox by listing non-archived sources first
+    @sources.values.sort_by { |s| s.id }.partition { |s| !s.archived? }.flatten
+  end
+
+  def source_for uri; sources.find { |s| s.is_source_for? uri }; end
+  def usual_sources; sources.find_all { |s| s.usual? }; end
 
   def load_index dir=File.join(@dir, "ferret")
     if File.exists? dir
@@ -512,7 +516,7 @@ protected
         File.chmod 0600, fn
         FileUtils.mv fn, bakfn, :force => true unless File.exists?(bakfn) && File.size(fn) == 0
       end
-      Redwood::save_yaml_obj @sources.values.sort_by { |s| s.id.to_i }, fn, true
+      Redwood::save_yaml_obj sources.sort_by { |s| s.id.to_i }, fn, true
       File.chmod 0600, fn
     end
     @sources_dirty = false
diff --git a/lib/sup/modes/compose-mode.rb b/lib/sup/modes/compose-mode.rb
@@ -20,7 +20,7 @@ class ComposeMode < EditMessageMode
   end
 
   def self.spawn_nicely opts={}
-    to = opts[:to] || BufferManager.ask_for_contacts(:people, "To: ") or return
+    to = opts[:to] || BufferManager.ask_for_contacts(:people, "To: ", [opts[:to_default]]) 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])
     subj = opts[:subj] || (BufferManager.ask(:subject, "Subject: ") or return if $config[:ask_for_subject])
diff --git a/lib/sup/modes/contact-list-mode.rb b/lib/sup/modes/contact-list-mode.rb
@@ -29,7 +29,7 @@ class ContactListMode < LineCursorMode
 
   def initialize mode=:regular
     @mode = mode
-    @tags = Tagger.new self
+    @tags = Tagger.new self, "contact"
     @num = nil
     @text = []
     super()
diff --git a/lib/sup/modes/edit-message-mode.rb b/lib/sup/modes/edit-message-mode.rb
@@ -148,9 +148,13 @@ EOS
   def attach_file
     fn = BufferManager.ask_for_filename :attachment, "File name (enter for browser): "
     return unless fn
-    @attachments << RMail::Message.make_file_attachment(fn)
-    @attachment_names << fn
-    update
+    begin
+      @attachments << RMail::Message.make_file_attachment(fn)
+      @attachment_names << fn
+      update
+    rescue SystemCallError => e
+      BufferManager.flash "Can't read #{fn}: #{e.message}"
+    end
   end
 
   def delete_attachment
diff --git a/lib/sup/modes/scroll-mode.rb b/lib/sup/modes/scroll-mode.rb
@@ -12,7 +12,7 @@ class ScrollMode < Mode
 
   attr_reader :status, :topline, :botline, :leftcol
 
-  COL_JUMP = 4
+  COL_JUMP = 2
 
   register_keymap do |k|
     k.add :line_down, "Down one line", :down, 'j', 'J'
diff --git a/lib/sup/modes/thread-view-mode.rb b/lib/sup/modes/thread-view-mode.rb
@@ -318,6 +318,8 @@ EOS
     end
   end
 
+  IDEAL_TOP_CONTEXT = 3 # try and give 3 rows of top context
+  IDEAL_LEFT_CONTEXT = 4 # try and give 4 columns of left context
   def jump_to_message m, loose_alignment=false
     l = @layout[m]
     left = l.depth * INDENT_SPACES
@@ -325,19 +327,20 @@ EOS
 
     ## jump to the top line
     if loose_alignment
-      jump_to_line [l.top - 3, 0].max # give 3 lines of top context
+      jump_to_line [l.top - IDEAL_TOP_CONTEXT, 0].max # give 3 lines of top context
     else
       jump_to_line l.top
     end
 
     ## jump to the left column
-    if loose_alignment
-      ## try and give 4 columns of left context, but not if it means that
-      ## the right of the message is truncated.
-      jump_to_col [[left - 4, rightcol - l.width - 1].min, 0].max
-    else
-      jump_to_col left
-    end
+    ideal_left = left +
+      if loose_alignment
+        -IDEAL_LEFT_CONTEXT + (l.width - buffer.content_width + IDEAL_LEFT_CONTEXT + 1).clamp(0, IDEAL_LEFT_CONTEXT)
+      else
+        0
+      end
+
+    jump_to_col [ideal_left, 0].max
 
     ## either way, move the cursor to the first line
     set_cursor_pos l.top
diff --git a/lib/sup/tagger.rb b/lib/sup/tagger.rb
@@ -1,9 +1,11 @@
 module Redwood
 
 class Tagger
-  def initialize mode
+  def initialize mode, noun="thread", plural_noun=nil
     @mode = mode
     @tagged = {}
+    @noun = noun
+    @plural_noun = plural_noun || (@noun + "s")
   end
 
   def tagged? o; @tagged[o]; end
@@ -21,7 +23,7 @@ class Tagger
       return
     end
 
-    noun = num_tagged == 1 ? "thread" : "threads"
+    noun = num_tagged == 1 ? @noun : @plural_noun
 
     unless action
       c = BufferManager.ask_getch "apply to #{num_tagged} tagged #{noun}:"