sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/
commit 9b6c0c66f3e4f6b61a2a9128065e72c9f0829cd4
parent e9d93e2627ac062bbfcf221c54109dd4c82e5ccf
Author: William Morgan <wmorgan-sup@masanjin.net>
Date:   Thu,  9 Apr 2009 13:22:55 -0400

Merge branch 'better-buffer-list' into next

Diffstat:
M bin/sup | 6 +++---
M lib/sup/buffer.rb | 9 ++++++---
M lib/sup/colormap.rb | 5 ++++-
M lib/sup/logger.rb | 2 +-
M lib/sup/mode.rb | 1 +
M lib/sup/modes/buffer-list-mode.rb | 8 ++++++--
M lib/sup/modes/contact-list-mode.rb | 2 +-
M lib/sup/modes/edit-message-mode.rb | 2 ++
M lib/sup/modes/resume-mode.rb | 2 ++
M lib/sup/modes/thread-index-mode.rb | 4 ++--
M lib/sup/poll.rb | 2 +-
11 files changed, 29 insertions(+), 14 deletions(-)
diff --git a/bin/sup b/bin/sup
@@ -67,9 +67,9 @@ global_keymap = Keymap.new do |k|
   k.add :quit_now, "Quit Sup immediately", 'Q'
   k.add :help, "Show help", '?'
   k.add :roll_buffers, "Switch to next buffer", 'b'
-#  k.add :roll_buffers_backwards, "Switch to previous buffer", 'B'
+  k.add :roll_buffers_backwards, "Switch to previous buffer", 'B'
   k.add :kill_buffer, "Kill the current buffer", 'x'
-  k.add :list_buffers, "List all buffers", 'B'
+  k.add :list_buffers, "List all buffers", ';'
   k.add :list_contacts, "List contacts", 'C'
   k.add :redraw, "Redraw screen", :ctrl_l
   k.add :search, "Search all messages", '\\', 'F'
@@ -230,7 +230,7 @@ begin
     when :kill_buffer
       bm.kill_buffer_safely bm.focus_buf
     when :list_buffers
-      bm.spawn_unless_exists("Buffer List") { BufferListMode.new }
+      bm.spawn_unless_exists("buffer list", :system => true) { BufferListMode.new }
     when :list_contacts
       b, new = bm.spawn_unless_exists("Contact List") { ContactListMode.new }
       b.mode.load_in_background if new
diff --git a/lib/sup/buffer.rb b/lib/sup/buffer.rb
@@ -51,8 +51,8 @@ module Redwood
 class InputSequenceAborted < StandardError; end
 
 class Buffer
-  attr_reader :mode, :x, :y, :width, :height, :title
-  bool_reader :dirty
+  attr_reader :mode, :x, :y, :width, :height, :title, :atime
+  bool_reader :dirty, :system
   bool_accessor :force_to_top
 
   def initialize window, mode, width, height, opts={}
@@ -63,6 +63,8 @@ class Buffer
     @title = opts[:title] || ""
     @force_to_top = opts[:force_to_top] || false
     @x, @y, @width, @height = 0, 0, width, height
+    @atime = Time.at 0
+    @system = opts[:system] || false
   end
 
   def content_height; @height - 1; end
@@ -97,6 +99,7 @@ class Buffer
     @mode.draw
     draw_status status
     commit
+    @atime = Time.now
   end
 
   ## s nil means a blank line!
@@ -338,7 +341,7 @@ EOS
     ## w = Ncurses::WINDOW.new(height, width, (opts[:top] || 0),
     ## (opts[:left] || 0))
     w = Ncurses.stdscr
-    b = Buffer.new w, mode, width, height, :title => realtitle, :force_to_top => (opts[:force_to_top] || false)
+    b = Buffer.new w, mode, width, height, :title => realtitle, :force_to_top => opts[:force_to_top], :system => opts[:system]
     mode.buffer = b
     @name_map[realtitle] = b
 
diff --git a/lib/sup/colormap.rb b/lib/sup/colormap.rb
@@ -46,7 +46,10 @@ class Colormap
     :completion_character => { :fg => "white", :bg => "default", :attrs => ["bold"] },
     :horizontal_selector_selected => { :fg => "yellow", :bg => "default", :attrs => ["bold"] },
     :horizontal_selector_unselected => { :fg => "cyan", :bg => "default" },
-    :search_highlight => { :fg => "black", :bg => "yellow", :attrs => ["bold"] }
+    :search_highlight => { :fg => "black", :bg => "yellow", :attrs => ["bold"] },
+    :system_buf => { :fg => "blue", :bg => "default" },
+    :regular_buf => { :fg => "white", :bg => "default" },
+    :modified_buffer => { :fg => "yellow", :bg => "default", :attrs => ["bold"] },
   }
   
   def initialize
diff --git a/lib/sup/logger.rb b/lib/sup/logger.rb
@@ -18,7 +18,7 @@ class Logger
   def make_buf
     return if @mode.buffer || !BufferManager.instantiated? || !@respawn || @spawning
     @spawning = true
-    @mode.buffer = BufferManager.instance.spawn "<log>", @mode, :hidden => true
+    @mode.buffer = BufferManager.instance.spawn "log", @mode, :hidden => true, :system => true
     @spawning = false
   end
 
diff --git a/lib/sup/mode.rb b/lib/sup/mode.rb
@@ -24,6 +24,7 @@ class Mode
   end
 
   def killable?; true; end
+  def unsaved?; false end
   def draw; end
   def focus; end
   def blur; end
diff --git a/lib/sup/modes/buffer-list-mode.rb b/lib/sup/modes/buffer-list-mode.rb
@@ -16,6 +16,7 @@ class BufferListMode < LineCursorMode
 
   def focus
     reload # buffers may have been killed or created since last view
+    set_cursor_pos 0
   end
 
 protected
@@ -26,10 +27,13 @@ protected
   end
 
   def regen_text
-    @bufs = BufferManager.buffers.sort_by { |name, buf| name }
+    @bufs = BufferManager.buffers.reject { |name, buf| buf.mode == self }.sort_by { |name, buf| buf.atime }.reverse
     width = @bufs.max_of { |name, buf| buf.mode.name.length }
     @text = @bufs.map do |name, buf|
-      sprintf "%#{width}s  %s", buf.mode.name, name
+      base_color = buf.system? ? :system_buf_color : :regular_buf_color
+      [[base_color, sprintf("%#{width}s ", buf.mode.name)],
+       [:modified_buffer_color, (buf.mode.unsaved? ? '*' : ' ')],
+       [base_color, " " + name]]
     end
   end
 
diff --git a/lib/sup/modes/contact-list-mode.rb b/lib/sup/modes/contact-list-mode.rb
@@ -23,7 +23,7 @@ class ContactListMode < LineCursorMode
     k.add :reload, "Drop contact list and reload", 'D'
     k.add :alias, "Edit alias/or name for contact", 'a', 'i'
     k.add :toggle_tagged, "Tag/untag current line", 't'
-    k.add :apply_to_tagged, "Apply next command to all tagged items", ';'
+    k.add :apply_to_tagged, "Apply next command to all tagged items", '+'
     k.add :search, "Search for messages from particular people", 'S'
   end
 
diff --git a/lib/sup/modes/edit-message-mode.rb b/lib/sup/modes/edit-message-mode.rb
@@ -145,6 +145,8 @@ EOS
     !edited? || BufferManager.ask_yes_or_no("Discard message?")
   end
 
+  def unsaved?; edited? end
+
   def attach_file
     fn = BufferManager.ask_for_filename :attachment, "File name (enter for browser): "
     return unless fn
diff --git a/lib/sup/modes/resume-mode.rb b/lib/sup/modes/resume-mode.rb
@@ -11,6 +11,8 @@ class ResumeMode < EditMessageMode
     super :header => header, :body => body, :have_signature => true
   end
 
+  def unsaved?; !@safe end
+
   def killable?
     return true if @safe
 
diff --git a/lib/sup/modes/thread-index-mode.rb b/lib/sup/modes/thread-index-mode.rb
@@ -42,7 +42,7 @@ EOS
     k.add :toggle_tagged, "Tag/untag selected thread", 't'
     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 :apply_to_tagged, "Apply next command to all tagged threads", '+'
     k.add :join_threads, "Force tagged threads to be joined into the same thread", '#'
     k.add :undo, "Undo the previous action", 'u'
   end
@@ -80,6 +80,7 @@ EOS
     end
   end
 
+  def unsaved?; dirty? end
   def lines; @text.length; end
   def [] i; @text[i]; end
   def contains_thread? t; @threads.include?(t) end
@@ -849,7 +850,6 @@ protected
       (t.labels - @hidden_labels).map { |label| [:label_color, "+#{label} "] } +
       [[:snippet_color, snippet]
     ]
-
   end
 
   def dirty?; @mutex.synchronize { (@hidden_threads.keys + @threads).any? { |t| t.dirty? } } end
diff --git a/lib/sup/poll.rb b/lib/sup/poll.rb
@@ -40,7 +40,7 @@ EOS
   end
 
   def buffer
-    b, new = BufferManager.spawn_unless_exists("<poll for new messages>", :hidden => true) { PollMode.new }
+    b, new = BufferManager.spawn_unless_exists("poll for new messages", :hidden => true, :system => true) { PollMode.new }
     b
   end