From 02d2055299d9ae0f067fb735e96889eee247bec8 Mon Sep 17 00:00:00 2001 From: William Morgan Date: Fri, 28 Dec 2007 13:29:53 -0800 Subject: [PATCH] add multi-key sequence support BufferManager is now the main entry point for keystroke resolution, via #resolve_input_with_keymap, as multi-key commands require displaying one or more prompts. --- bin/sup | 112 +++++++++++++++++++++++++--------------------- lib/sup/buffer.rb | 26 ++++++++++- lib/sup/keymap.rb | 35 +++++++++++---- lib/sup/mode.rb | 11 ++--- 4 files changed, 115 insertions(+), 69 deletions(-) diff --git a/bin/sup b/bin/sup index 0e0cc64..637e71d 100644 --- a/bin/sup +++ b/bin/sup @@ -199,63 +199,71 @@ begin next unless c bm.erase_flash - unless bm.handle_input(c) - x = global_keymap.action_for c - case x - when :quit - break if bm.kill_all_buffers_safely - when :help - curmode = bm.focus_buf.mode - bm.spawn_unless_exists("") { HelpMode.new curmode, global_keymap } - when :roll_buffers - bm.roll_buffers - when :roll_buffers_backwards - bm.roll_buffers_backwards - when :kill_buffer - bm.kill_buffer_safely bm.focus_buf - when :list_buffers - bm.spawn_unless_exists("Buffer List") { BufferListMode.new } - when :list_contacts - b, new = bm.spawn_unless_exists("Contact List") { ContactListMode.new } - b.mode.load_in_background if new - when :search - query = BufferManager.ask :search, "search all messages: " - next unless query && query !~ /^\s*$/ - SearchResultsMode.spawn_from_query query - when :list_labels - 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 - unless user_label.nil? - if user_label.empty? - bm.spawn_unless_exists("Label list") { LabelListMode.new } if user_label && user_label.empty? - else - LabelSearchResultsMode.spawn_nicely user_label - end + action = + begin + if bm.handle_input c + :nothing + else + bm.resolve_input_with_keymap c, global_keymap end - when :compose - ComposeMode.spawn_nicely - when :poll - reporting_thread("user-invoked poll") { PollManager.poll } - when :recall_draft - case Index.num_results_for :label => :draft - when 0 - bm.flash "No draft messages." - when 1 - m = nil - Index.each_id_by_date(:label => :draft) { |mid, builder| m = builder.call } - r = ResumeMode.new(m) - BufferManager.spawn "Edit message", r - r.edit_message + rescue InputSequenceAborted + :nothing + end + + case action + when :quit + break if bm.kill_all_buffers_safely + when :help + curmode = bm.focus_buf.mode + bm.spawn_unless_exists("") { HelpMode.new curmode, global_keymap } + when :roll_buffers + bm.roll_buffers + when :roll_buffers_backwards + bm.roll_buffers_backwards + when :kill_buffer + bm.kill_buffer_safely bm.focus_buf + when :list_buffers + bm.spawn_unless_exists("Buffer List") { BufferListMode.new } + when :list_contacts + b, new = bm.spawn_unless_exists("Contact List") { ContactListMode.new } + b.mode.load_in_background if new + when :search + query = BufferManager.ask :search, "search all messages: " + next unless query && query !~ /^\s*$/ + SearchResultsMode.spawn_from_query query + when :list_labels + 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 + unless user_label.nil? + if user_label.empty? + bm.spawn_unless_exists("Label list") { LabelListMode.new } if user_label && user_label.empty? else - b, new = BufferManager.spawn_unless_exists("All drafts") { LabelSearchResultsMode.new [:draft] } - b.mode.load_threads :num => b.content_height if new + LabelSearchResultsMode.spawn_nicely user_label end - when :nothing - when :redraw - bm.completely_redraw_screen + end + when :compose + ComposeMode.spawn_nicely + when :poll + reporting_thread("user-invoked poll") { PollManager.poll } + when :recall_draft + case Index.num_results_for :label => :draft + when 0 + bm.flash "No draft messages." + when 1 + m = nil + Index.each_id_by_date(:label => :draft) { |mid, builder| m = builder.call } + r = ResumeMode.new(m) + BufferManager.spawn "Edit message", r + r.edit_message else - bm.flash "Unknown keypress '#{c.to_character}' for #{bm.focus_buf.mode.name}." + b, new = BufferManager.spawn_unless_exists("All drafts") { LabelSearchResultsMode.new [:draft] } + b.mode.load_threads :num => b.content_height if new end + when :nothing, InputSequenceAborted + when :redraw + bm.completely_redraw_screen + else + bm.flash "Unknown keypress '#{c.to_character}' for #{bm.focus_buf.mode.name}." end bm.draw_screen diff --git a/lib/sup/buffer.rb b/lib/sup/buffer.rb index 522b907..17055a9 100644 --- a/lib/sup/buffer.rb +++ b/lib/sup/buffer.rb @@ -48,6 +48,8 @@ end module Redwood +class InputSequenceAborted < StandardError; end + class Buffer attr_reader :mode, :x, :y, :width, :height, :title bool_reader :dirty @@ -346,7 +348,10 @@ EOS c = Ncurses.nonblocking_getch next unless c # getch timeout break if c == Ncurses::KEY_CANCEL - mode.handle_input c + begin + mode.handle_input c + rescue InputSequenceAborted # do nothing + end draw_screen erase_flash end @@ -597,6 +602,25 @@ EOS end end + ## turns an input keystroke into an action symbol. returns the action + ## if found, nil if not found, and throws InputSequenceAborted if + ## the user aborted a multi-key sequence. (Because each of those cases + ## should be handled differently.) + ## + ## this is in BufferManager because multi-key sequences require prompting. + def resolve_input_with_keymap c, keymap + action, text = keymap.action_for c + while action.is_a? Keymap # multi-key commands, prompt + key = BufferManager.ask_getch text + unless key # user canceled, abort + erase_flash + raise InputSequenceAborted + end + action, text = action.action_for(key) if action.has_key?(key) + end + action + end + def minibuf_lines @minibuf_mutex.synchronize do [(@flash ? 1 : 0) + diff --git a/lib/sup/keymap.rb b/lib/sup/keymap.rb index ce74a48..e14ccbe 100644 --- a/lib/sup/keymap.rb +++ b/lib/sup/keymap.rb @@ -7,7 +7,7 @@ class Keymap yield self if block_given? end - def keysym_to_keycode k + def self.keysym_to_keycode k case k when :down: Curses::KEY_DOWN when :up: Curses::KEY_UP @@ -31,7 +31,7 @@ class Keymap end end - def keysym_to_string k + def self.keysym_to_string k case k when :down: "" when :up: "" @@ -60,25 +60,44 @@ class Keymap entry = [action, help, keys] @order << entry keys.each do |k| - kc = keysym_to_keycode k - raise ArgumentError, "key #{k} already defined (action #{action})" if @map.include? kc + kc = Keymap.keysym_to_keycode k + raise ArgumentError, "key '#{k}' already defined (action #{action})" if @map.include? kc @map[kc] = entry end end + def add_multi prompt, key + submap = Keymap.new + add submap, prompt, key + yield submap + end + def action_for kc action, help, keys = @map[kc] - action + [action, help] end + def has_key? k; @map[k] end + def keysyms; @map.values.map { |action, help, keys| keys }.flatten; end - def help_text except_for={} - lines = @order.map do |action, help, keys| + def help_lines except_for={}, prefix="" + lines = [] # :( + @order.each do |action, help, keys| valid_keys = keys.select { |k| !except_for[k] } next if valid_keys.empty? - [valid_keys.map { |k| keysym_to_string k }.join(", "), help] + case action + when Symbol + lines << [valid_keys.map { |k| prefix + Keymap.keysym_to_string(k) }.join(", "), help] + when Keymap + lines += action.help_lines({}, prefix + Keymap.keysym_to_string(keys.first)) + end end.compact + lines + end + + def help_text except_for={} + lines = help_lines except_for llen = lines.max_of { |a, b| a.length } lines.map { |a, b| sprintf " %#{llen}s : %s", a, b }.join("\n") end diff --git a/lib/sup/mode.rb b/lib/sup/mode.rb index 217a617..9900dc3 100644 --- a/lib/sup/mode.rb +++ b/lib/sup/mode.rb @@ -35,17 +35,12 @@ class Mode @buffer = nil end - ## turns an input keystroke into an action symbol def resolve_input c - ## try all keymaps in order of age - action = nil - klass = self.class - - ancestors.each do |klass| - action = @@keymaps.member?(klass) && @@keymaps[klass].action_for(c) + ancestors.each do |klass| # try all keymaps in order of ancestry + next unless @@keymaps.member?(klass) + action = BufferManager.resolve_input_with_keymap c, @@keymaps[klass] return action if action end - nil end -- 2.45.2