]> git.cworth.org Git - sup/commitdiff
tab completion for label editing
authorwmorgan <wmorgan@5c8cc53c-5e98-4d25-b20a-d8db53a31250>
Mon, 30 Jul 2007 00:26:28 +0000 (00:26 +0000)
committerwmorgan <wmorgan@5c8cc53c-5e98-4d25-b20a-d8db53a31250>
Mon, 30 Jul 2007 00:26:28 +0000 (00:26 +0000)
git-svn-id: svn://rubyforge.org/var/svn/sup/trunk@510 5c8cc53c-5e98-4d25-b20a-d8db53a31250

bin/sup
doc/TODO
lib/sup/buffer.rb
lib/sup/label.rb
lib/sup/modes/label-list-mode.rb
lib/sup/modes/thread-index-mode.rb
lib/sup/textfield.rb
lib/sup/util.rb

diff --git a/bin/sup b/bin/sup
index c2b111ccef937ec889161713e980b16d7a51eba8..47d9ba9c57dd06c2d6f0fe20bd247868071f4a1f 100644 (file)
--- a/bin/sup
+++ b/bin/sup
@@ -217,7 +217,7 @@ begin
           bm.flash "Couldn't parse query."
         end
       when :list_labels
-        labels = LabelManager.listable_label_strings
+        labels = LabelManager.listable_labels.map { |l| LabelManager.string_for l }
         user_label = bm.ask_with_completions :label, "Show threads with label (enter for listing): ", labels
         user_label = bm.spawn_modal("Label list", LabelListMode.new) if user_label && user_label.empty?
         
index 77672fe887ef1ab10b0b07d69166f3cd93ae0ee0..cdbcf56f572fc6b6325debaee97135b38b526952 100644 (file)
--- a/doc/TODO
+++ b/doc/TODO
@@ -3,7 +3,6 @@ for next release
 _ forward attachments
 _ messages as attachments
 _ individual labeling in thread-view-mode
-_ tab completion on labeling
 _ tab completion for to: and cc: in compose-mode
 _ translate aliases in queries on to: and from: fields
 _ use trac or something. this file is getting a little silly.
@@ -11,15 +10,14 @@ _ gpg integration
 _ user-defined hooks
 _ saved searches
 _ bugfix: missing sources should be handled better
-_ tab completion for contacts
-_ bugfix: screwing the headers when editing causes a crash
+_ bugfix: screwing with the headers when editing causes a crash
 _ bugfix: sometimes, when one new message comes into an imap folder,
    we don't catch it until a reload. but we do see a message
    indicating they're loaded to inbox (imap only? hard to reproduce.)
 _ bugfix: need a better way to force an address to a particular name,
    for things like evite addresses
-_ bugfix: ferret flakiness: just added message but can't find it
-_ add new message counts until keypress
+_ bugfix: ferret flakiness: just added message but can't find it (? still relevant ?)
+_ for new message flashes, add new message counts until keypress
 _ bugfix: deadlock (on rubyforge)
 _ bugfix: ferret corrupt index problem at index.c:901. see http://ferret.davebalmain.com/trac/ticket/279
 _ bugfix: read before thread-index has finished loading then hides the
@@ -33,6 +31,7 @@ _ undo
 _ gmail support
 _ warnings: top-posting, missing attachment, ruby-talk:XXXX detection
 _ Net::SMTP support
+x tab completion on labeling
 
 future
 ------
index dc3b44e6e385cfe1bf7709c347542d53a95c402e..a14c319eb3ddc57cae1fe2dd82b4b6a9d050932c 100644 (file)
@@ -14,6 +14,12 @@ module Ncurses
     lamer.first
   end
 
+  def curx
+    lame, lamer = [], []
+    stdscr.getyx lame, lamer
+    lamer.first
+  end
+
   def mutex; @mutex ||= Mutex.new; end
   def sync &b; mutex.synchronize(&b); end
 
@@ -27,7 +33,7 @@ module Ncurses
     end
   end
 
-  module_function :rows, :cols, :nonblocking_getch, :mutex, :sync
+  module_function :rows, :cols, :curx, :nonblocking_getch, :mutex, :sync
 
   KEY_ENTER = 10
   KEY_CANCEL = ?\a # ctrl-g
@@ -332,6 +338,24 @@ class BufferManager
     end
   end
 
+  def ask_many_with_completions domain, question, completions, default=nil, sep=" "
+    ask domain, question, default do |partial|
+      prefix, target = 
+        case partial.gsub(/#{sep}+/, sep)
+        when /^\s*$/
+          ["", ""]
+        when /^(.+#{sep})$/
+          [$1, ""]
+        when /^(.*#{sep})?(.+?)$/
+          [$1 || "", $2]
+        else
+          raise "william screwed up completion: #{partial.inspect}"
+        end
+
+      completions.select { |x| x =~ /^#{target}/i }.map { |x| [prefix + x, x] }
+    end
+  end
+
   ## returns an ARRAY of filenames!
   def ask_for_filenames domain, question, default=nil
     answer = ask domain, question, default do |s|
@@ -409,7 +433,6 @@ class BufferManager
         tf.position_cursor
       elsif tf.roll_completions?
         completion_buf.mode.roll
-
         draw_screen :skip_minibuf => true
         tf.position_cursor
       end
index aef40acf8defd6f42a0b47d638981a6a7df7df43..3288493625c902b93ec7ccbfdb3643152d9079aa 100644 (file)
@@ -28,17 +28,27 @@ class LabelManager
     self.class.i_am_the_instance self
   end
 
-  ## all listable (user-defined and system listable) labels, ordered
+  ## all listable (just user-defined at the moment) labels, ordered
   ## nicely and converted to pretty strings. use #label_for to recover
   ## the original label.
-  def listable_label_strings
-    LISTABLE_RESERVED_LABELS.sort_by { |l| l.to_s }.map { |l| l.to_s.ucfirst } +
-      @labels.keys.map { |l| l.to_s }.sort
+  def listable_labels
+    LISTABLE_RESERVED_LABELS + @labels.keys
+  end
+
+  ## all apply-able (user-defined and system listable) labels, ordered
+  ## nicely and converted to pretty strings. use #label_for to recover
+  ## the original label.
+  def applyable_labels
+    @labels.keys
   end
 
   ## reverse the label->string mapping, for convenience!
-  def label_for string
-    string.downcase.intern
+  def string_for l
+    if RESERVED_LABELS.include? l
+      l.to_s.ucfirst
+    else
+      l.to_s
+    end
   end
   
   def << t
index 22de97f7b2b0e1d00df87685f7dbaebc979694f8..dcee795deb64b06f7f40c941c00af9b3771b0cd7 100644 (file)
@@ -30,16 +30,16 @@ protected
   
   def regen_text
     @text = []
-    @labels = LabelManager.listable_label_strings
+    @labels = LabelManager.listable_labels
 
-    counts = @labels.map do |string|
-      label = LabelManager.label_for string
+    counts = @labels.map do |label|
+      string = LabelManager.string_for label
       total = Index.num_results_for :label => label
       unread = Index.num_results_for :labels => [label, :unread]
       [label, string, total, unread]
-    end      
+    end.sort_by { |l, s, t, u| s.downcase }
 
-    width = @labels.max_of { |string| string.length }
+    width = counts.max_of { |l, s, t, u| s.length }
 
     counts.map do |label, string, total, unread|
       if total == 0 && !LabelManager::RESERVED_LABELS.include?(label)
index f6848a05054f76884fa32a6ca26e06abbaab2c82..8238534d7c0d65ee467d22447e93ab3391b4e668 100644 (file)
@@ -288,10 +288,13 @@ class ThreadIndexMode < LineCursorMode
     thread = @threads[curpos] or return
     speciall = (@hidden_labels + LabelManager::RESERVED_LABELS).uniq
     keepl, modifyl = thread.labels.partition { |t| speciall.member? t }
-    label_string = modifyl.join(" ")
-    label_string += " " unless label_string.empty?
+    cur_label_string = modifyl.join(" ")
+    cur_label_string += " " unless cur_label_string.empty?
+
+    applyable_labels = (LabelManager.applyable_labels - @hidden_labels).map { |l| LabelManager.string_for l }.sort_by { |s| s.downcase }
+
+    answer = BufferManager.ask_many_with_completions :label, "Labels for thread: ", applyable_labels, cur_label_string
 
-    answer = BufferManager.ask :edit_labels, "edit labels: ", label_string
     return unless answer
     user_labels = answer.split(/\s+/).map { |l| l.intern }
     
index ba63638873dee463648394cfffca1df53adabec9..f147785915bc96ab169f476248f3b105a295255d 100644 (file)
@@ -4,12 +4,16 @@ module Redwood
 
 ## a fully-functional text field supporting completions, expansions,
 ## history--everything!
+## 
+## writing this fucking sucked. if you thought ncurses was some 1970s
+## before-people-knew-how-to-program bullshit, wait till you see
+## ncurses forms.
 ##
-## completion is done emacs-style, and mostly depends on outside
-## support, as we merely signal the existence of a new set of
-## completions to show (#new_completions?)  or that the current list
-## of completions should be rolled if they're too large to fill the
-## screen (#roll_completions?).
+## completion comments: completion is done emacs-style, and mostly
+## depends on outside support, as we merely signal the existence of a
+## new set of completions to show (#new_completions?)  or that the
+## current list of completions should be rolled if they're too large
+## to fill the screen (#roll_completions?).
 ##
 ## in sup, completion support is implemented through BufferManager#ask
 ## and CompletionMode.
@@ -27,22 +31,16 @@ class TextField
   bool_reader :new_completions, :roll_completions
   attr_reader :completions
 
-  ## when the user presses enter, we store the value in @value and
-  ## clean up all the ncurses cruft. before @value is set, we can
-  ## get the current value from ncurses.
-  def value; @field ? get_cur_value : @value end
+  def value; @value || get_cursed_value end
 
   def activate question, default=nil, &block
     @question = question
-    @value = nil
     @completion_block = block
     @field = Ncurses::Form.new_field 1, @width - question.length,
                                      @y, @x + question.length, 0, 0
     @form = Ncurses::Form.new_form [@field]
-
-    @history[@i = @history.size] = default || ""
     Ncurses::Form.post_form @form
-    set_cur_value @history[@i]
+    set_cursed_value default if default
   end
 
   def position_cursor
@@ -50,7 +48,7 @@ class TextField
     @w.mvaddstr @y, 0, @question
     Ncurses.curs_set 1
     Ncurses::Form.form_driver @form, Ncurses::Form::REQ_END_FIELD
-    Ncurses::Form.form_driver @form, Ncurses::Form::REQ_NEXT_CHAR if @history[@i] =~ / $/ # fucking RETARDED!!!!
+    Ncurses::Form.form_driver @form, Ncurses::Form::REQ_NEXT_CHAR if @value && @value =~ / $/ # fucking RETARDED!!!!
   end
 
   def deactivate
@@ -66,20 +64,21 @@ class TextField
     ## short-circuit exit paths
     case c
     when Ncurses::KEY_ENTER # submit!
-      @value = @history[@i] = get_cur_value
+      @value = get_cursed_value
+      @history.push @value
       return false
     when Ncurses::KEY_CANCEL # cancel
-      @history.delete_at @i
-      @i = @history.empty? ? nil : (@i - 1) % @history.size 
       @value = nil
       return false
     when Ncurses::KEY_TAB # completion
       return true unless @completion_block
       if @completions.empty?
-        v = get_cur_value
+        v = get_cursed_value
         c = @completion_block.call v
         if c.size > 0
-          set_cur_value c.map { |full, short| full }.shared_prefix
+          @value = c.map { |full, short| full }.shared_prefix(true)
+          set_cursed_value @value
+          position_cursor
         end
         if c.size > 1
           @completions = c
@@ -94,6 +93,7 @@ class TextField
     end
 
     reset_completion_state
+    @value = nil
 
     d =
       case c
@@ -108,13 +108,17 @@ class TextField
       when ?\005
         Ncurses::Form::REQ_END_FIELD
       when Ncurses::KEY_UP
-        @history[@i] = @field.field_buffer 0
+        @i ||= @history.size
+        @history[@i] = get_cursed_value
         @i = (@i - 1) % @history.size
-        set_cur_value @history[@i]
+        @value = @history[@i]
+        set_cursed_value @value
       when Ncurses::KEY_DOWN
-        @history[@i] = @field.field_buffer 0
+        @i ||= @history.size
+        @history[@i] = get_cursed_value
         @i = (@i + 1) % @history.size
-        set_cur_value @history[@i]
+        @value = @history[@i]
+        set_cursed_value @value
       else
         c
       end
@@ -131,16 +135,25 @@ private
   end
 
   ## ncurses inanity wrapper
-  def get_cur_value
+  ##
+  ## DO NOT READ THIS CODE. YOU WILL GO MAD.
+  def get_cursed_value
+    return nil unless @field
+
+    x = Ncurses.curx
     Ncurses::Form.form_driver @form, Ncurses::Form::REQ_VALIDATION
-    @field.field_buffer(0).gsub(/^\s+|\s+$/, "").gsub(/\s+/, " ")
+    v = @field.field_buffer(0).gsub(/^\s+|\s+$/, "")
+
+    ## cursor <= end of text
+    if x - @question.length - v.length <= 0
+      v
+    else # trailing spaces
+      v + (" " * (x - @question.length - v.length))
+    end
   end
-  
-  ## ncurses inanity wrapper
-  def set_cur_value v
+
+  def set_cursed_value v
     @field.set_field_buffer 0, v
-    Ncurses::Form.form_driver @form, Ncurses::Form::REQ_END_FIELD
   end
-
 end
 end
index bdb59e41d8d09954e81ef74453e50c4006836c91..b9563e12a55cec07e334a40efa3fc9b143cb59ac 100644 (file)
@@ -277,14 +277,14 @@ module Enumerable
 
   ## returns the maximum shared prefix of an array of strings
   ## optinally excluding a prefix
-  def shared_prefix exclude=""
+  def shared_prefix caseless=false, exclude=""
     return "" if empty?
     prefix = ""
     (0 ... first.length).each do |i|
-      c = first[i]
-      break unless all? { |s| s[i] == c }
+      c = (caseless ? first.downcase : first)[i]
+      break unless all? { |s| (caseless ? s.downcase : s)[i] == c }
       next if exclude[i] == c
-      prefix += c.chr
+      prefix += first[i].chr
     end
     prefix
   end