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?
_ 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.
_ 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
_ gmail support
_ warnings: top-posting, missing attachment, ruby-talk:XXXX detection
_ Net::SMTP support
+x tab completion on labeling
future
------
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
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
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|
tf.position_cursor
elsif tf.roll_completions?
completion_buf.mode.roll
-
draw_screen :skip_minibuf => true
tf.position_cursor
end
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
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)
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 }
## 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.
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
@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
## 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
end
reset_completion_state
+ @value = nil
d =
case c
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
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
## 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