+ def ask_with_completions domain, question, completions, default=nil
+ ask domain, question, default do |s|
+ completions.select { |x| x =~ /^#{Regexp::escape s}/i }.map { |x| [x, x] }
+ end
+ end
+
+ def ask_many_with_completions domain, question, completions, default=nil
+ ask domain, question, default do |partial|
+ prefix, target =
+ case partial
+ when /^\s*$/
+ ["", ""]
+ when /^(.*\s+)?(.*?)$/
+ [$1 || "", $2]
+ else
+ raise "william screwed up completion: #{partial.inspect}"
+ end
+
+ completions.select { |x| x =~ /^#{Regexp::escape target}/i }.map { |x| [prefix + x, x] }
+ end
+ end
+
+ def ask_many_emails_with_completions domain, question, completions, default=nil
+ ask domain, question, default do |partial|
+ prefix, target = partial.split_on_commas_with_remainder
+ target ||= prefix.pop || ""
+ prefix = prefix.join(", ") + (prefix.empty? ? "" : ", ")
+ completions.select { |x| x =~ /^#{Regexp::escape target}/i }.sort_by { |c| [ContactManager.contact_for(c) ? 0 : 1, c] }.map { |x| [prefix + x, x] }
+ end
+ end
+
+ def ask_for_filename domain, question, default=nil
+ answer = ask domain, question, default do |s|
+ if s =~ /(~([^\s\/]*))/ # twiddle directory expansion
+ full = $1
+ name = $2.empty? ? Etc.getlogin : $2
+ dir = Etc.getpwnam(name).dir rescue nil
+ if dir
+ [[s.sub(full, dir), "~#{name}"]]
+ else
+ users.select { |u| u =~ /^#{Regexp::escape name}/ }.map do |u|
+ [s.sub("~#{name}", "~#{u}"), "~#{u}"]
+ end
+ end
+ else # regular filename completion
+ Dir["#{s}*"].sort.map do |fn|
+ suffix = File.directory?(fn) ? "/" : ""
+ [fn + suffix, File.basename(fn) + suffix]
+ end
+ end
+ end
+
+ if answer
+ answer =
+ if answer.empty?
+ spawn_modal "file browser", FileBrowserMode.new
+ elsif File.directory?(answer)
+ spawn_modal "file browser", FileBrowserMode.new(answer)
+ else
+ File.expand_path answer
+ end
+ end
+
+ answer
+ end
+
+ ## returns an array of labels
+ def ask_for_labels domain, question, default_labels, forbidden_labels=[]
+ default_labels = default_labels - forbidden_labels - LabelManager::RESERVED_LABELS
+ default = default_labels.join(" ")
+ default += " " unless default.empty?
+
+ # here I would prefer to give more control and allow all_labels instead of
+ # user_defined_labels only
+ applyable_labels = (LabelManager.user_defined_labels - forbidden_labels).map { |l| LabelManager.string_for l }.sort_by { |s| s.downcase }
+
+ answer = ask_many_with_completions domain, question, applyable_labels, default
+
+ return unless answer
+
+ user_labels = answer.to_set_of_symbols
+ user_labels.each do |l|
+ if forbidden_labels.include?(l) || LabelManager::RESERVED_LABELS.include?(l)
+ BufferManager.flash "'#{l}' is a reserved label!"
+ return
+ end
+ end
+ user_labels
+ end
+
+ def ask_for_contacts domain, question, default_contacts=[]
+ default = default_contacts.map { |s| s.to_s }.join(" ")
+ default += " " unless default.empty?
+
+ recent = Index.load_contacts(AccountManager.user_emails, :num => 10).map { |c| [c.full_address, c.email] }
+ contacts = ContactManager.contacts.map { |c| [ContactManager.alias_for(c), c.full_address, c.email] }
+
+ completions = (recent + contacts).flatten.uniq
+ completions += HookManager.run("extra-contact-addresses") || []
+ answer = BufferManager.ask_many_emails_with_completions domain, question, completions, default
+
+ if answer
+ answer.split_on_commas.map { |x| ContactManager.contact_for(x) || Person.from_address(x) }
+ end
+ end
+
+ ## for simplicitly, we always place the question at the very bottom of the
+ ## screen
+ def ask domain, question, default=nil, &block