From 818d27ffd735293668b4fa232af5f665c885c569 Mon Sep 17 00:00:00 2001 From: wmorgan Date: Mon, 30 Jul 2007 00:26:28 +0000 Subject: [PATCH] tab completion for label editing git-svn-id: svn://rubyforge.org/var/svn/sup/trunk@510 5c8cc53c-5e98-4d25-b20a-d8db53a31250 --- bin/sup | 2 +- doc/TODO | 9 ++-- lib/sup/buffer.rb | 27 ++++++++++- lib/sup/label.rb | 22 ++++++--- lib/sup/modes/label-list-mode.rb | 10 ++-- lib/sup/modes/thread-index-mode.rb | 9 ++-- lib/sup/textfield.rb | 73 ++++++++++++++++++------------ lib/sup/util.rb | 8 ++-- 8 files changed, 104 insertions(+), 56 deletions(-) diff --git a/bin/sup b/bin/sup index c2b111c..47d9ba9 100644 --- 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? diff --git a/doc/TODO b/doc/TODO index 77672fe..cdbcf56 100644 --- 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 ------ diff --git a/lib/sup/buffer.rb b/lib/sup/buffer.rb index dc3b44e..a14c319 100644 --- a/lib/sup/buffer.rb +++ b/lib/sup/buffer.rb @@ -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 diff --git a/lib/sup/label.rb b/lib/sup/label.rb index aef40ac..3288493 100644 --- a/lib/sup/label.rb +++ b/lib/sup/label.rb @@ -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 diff --git a/lib/sup/modes/label-list-mode.rb b/lib/sup/modes/label-list-mode.rb index 22de97f..dcee795 100644 --- a/lib/sup/modes/label-list-mode.rb +++ b/lib/sup/modes/label-list-mode.rb @@ -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) diff --git a/lib/sup/modes/thread-index-mode.rb b/lib/sup/modes/thread-index-mode.rb index f6848a0..8238534 100644 --- a/lib/sup/modes/thread-index-mode.rb +++ b/lib/sup/modes/thread-index-mode.rb @@ -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 } diff --git a/lib/sup/textfield.rb b/lib/sup/textfield.rb index ba63638..f147785 100644 --- a/lib/sup/textfield.rb +++ b/lib/sup/textfield.rb @@ -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 diff --git a/lib/sup/util.rb b/lib/sup/util.rb index bdb59e4..b9563e1 100644 --- a/lib/sup/util.rb +++ b/lib/sup/util.rb @@ -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 -- 2.45.2