]> git.cworth.org Git - sup/blobdiff - lib/sup/buffer.rb
whoops, add labels accessor to MBox::Loader
[sup] / lib / sup / buffer.rb
index 8cb12986f0b2b7831ff4edf0b987b7146e7d32a1..d40a6267d193cb01905c38efbf38bfad581463ba 100644 (file)
@@ -48,6 +48,8 @@ end
 
 module Redwood
 
+class InputSequenceAborted < StandardError; end
+
 class Buffer
   attr_reader :mode, :x, :y, :width, :height, :title
   bool_reader :dirty
@@ -175,6 +177,7 @@ EOS
     @textfields = {}
     @flash = nil
     @shelled = @asking = false
+    @in_x = ENV["TERM"] =~ /(xterm|rxvt|screen)/
 
     self.class.i_am_the_instance self
   end
@@ -183,7 +186,6 @@ EOS
 
   def focus_on buf
     return unless @buffers.member? buf
-
     return if buf == @focus_buf 
     @focus_buf.blur if @focus_buf
     @focus_buf = buf
@@ -191,15 +193,13 @@ EOS
   end
 
   def raise_to_front buf
-    return unless @buffers.member? buf
-
-    @buffers.delete buf
+    @buffers.delete(buf) or return
     if @buffers.length > 0 && @buffers.last.force_to_top?
       @buffers.insert(-2, buf)
     else
       @buffers.push buf
-      focus_on buf
     end
+    focus_on @buffers.last
     @dirty = true
   end
 
@@ -259,10 +259,11 @@ EOS
       if opts.member? :status
         [opts[:status], opts[:title]]
       else
-        get_status_and_title(@focus_buf) # must be called outside of the ncurses lock
+        raise "status must be supplied if draw_screen is called within a sync" if opts[:sync] == false
+        get_status_and_title @focus_buf # must be called outside of the ncurses lock
       end
 
-    print "\033]2;#{title}\07" if title
+    print "\033]2;#{title}\07" if title && @in_x
 
     Ncurses.mutex.lock unless opts[:sync] == false
 
@@ -349,7 +350,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
@@ -378,7 +382,7 @@ EOS
   end
 
   def kill_buffer buf
-    raise ArgumentError, "buffer not on stack: #{buf.inspect}" unless @buffers.member? buf
+    raise ArgumentError, "buffer not on stack: #{buf}: #{buf.title.inspect}" unless @buffers.member? buf
 
     buf.mode.cleanup
     @buffers.delete buf
@@ -388,15 +392,13 @@ EOS
       ## TODO: something intelligent here
       ## for now I will simply prohibit killing the inbox buffer.
     else
-      last = @buffers.last
-      @focus_buf ||= last
-      raise_to_front last
+      raise_to_front @buffers.last
     end
   end
 
   def ask_with_completions domain, question, completions, default=nil
     ask domain, question, default do |s|
-      completions.select { |x| x =~ /^#{s}/i }.map { |x| [x, x] }
+      completions.select { |x| x =~ /^#{Regexp::escape s}/i }.map { |x| [x, x] }
     end
   end
 
@@ -412,18 +414,16 @@ EOS
           raise "william screwed up completion: #{partial.inspect}"
         end
 
-      completions.select { |x| x =~ /^#{target}/i }.map { |x| [prefix + x, x] }
+      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
-      Redwood::log "before: prefix #{prefix.inspect}, target #{target.inspect}"
       target ||= prefix.pop || ""
       prefix = prefix.join(", ") + (prefix.empty? ? "" : ", ")
-      Redwood::log "after: prefix #{prefix.inspect}, target #{target.inspect}"
-      completions.select { |x| x =~ /^#{target}/i }.map { |x| [prefix + x, x] }
+      completions.select { |x| x =~ /^#{Regexp::escape target}/i }.map { |x| [prefix + x, x] }
     end
   end
 
@@ -436,7 +436,7 @@ EOS
         if dir
           [[s.sub(full, dir), "~#{name}"]]
         else
-          users.select { |u| u =~ /^#{name}/ }.map do |u|
+          users.select { |u| u =~ /^#{Regexp::escape name}/ }.map do |u|
             [s.sub("~#{name}", "~#{u}"), "~#{u}"]
           end
         end
@@ -455,7 +455,7 @@ EOS
         elsif File.directory?(answer)
           spawn_modal "file browser", FileBrowserMode.new(answer)
         else
-          answer
+          File.expand_path answer
         end
     end
 
@@ -499,24 +499,22 @@ EOS
     end
   end
 
-
+  ## for simplicitly, we always place the question at the very bottom of the
+  ## screen
   def ask domain, question, default=nil, &block
     raise "impossible!" if @asking
     @asking = true
 
-    @textfields[domain] ||= TextField.new Ncurses.stdscr, Ncurses.rows - 1, 0, Ncurses.cols
+    @textfields[domain] ||= TextField.new
     tf = @textfields[domain]
     completion_buf = nil
 
-    ## this goddamn ncurses form shit is a fucking 1970's nightmare.
-    ## jesus christ. the exact sequence of ncurses events that needs
-    ## to happen in order to display a form and have the entire screen
-    ## not disappear and have the cursor in the right place is TOO
-    ## FUCKING COMPLICATED.
+    status, title = get_status_and_title @focus_buf
+
     Ncurses.sync do
-      tf.activate question, default, &block
-      @dirty = true
-      draw_screen :skip_minibuf => true, :sync => false
+      tf.activate Ncurses.stdscr, Ncurses.rows - 1, 0, Ncurses.cols, question, default, &block
+      @dirty = true # for some reason that blanks the whole fucking screen
+      draw_screen :sync => false, :status => status, :title => title
       tf.position_cursor
       Ncurses.refresh
     end
@@ -535,56 +533,59 @@ EOS
         mode = CompletionMode.new shorts, :header => "Possible completions for \"#{tf.value}\": ", :prefix_len => prefix_len
         completion_buf = spawn "<completions>", mode, :height => 10
 
-        draw_screen
+        draw_screen :skip_minibuf => true
         tf.position_cursor
       elsif tf.roll_completions?
         completion_buf.mode.roll
-        draw_screen
+        draw_screen :skip_minibuf => true
         tf.position_cursor
       end
 
       Ncurses.sync { Ncurses.refresh }
     end
     
-    Ncurses.sync { tf.deactivate }
     kill_buffer completion_buf if completion_buf
+
     @dirty = true
     @asking = false
-    draw_screen
+    Ncurses.sync do
+      tf.deactivate
+      draw_screen :sync => false, :status => status, :title => title
+    end
     tf.value
   end
 
-  ## some pretty lame code in here!
   def ask_getch question, accept=nil
+    raise "impossible!" if @asking
+
     accept = accept.split(//).map { |x| x[0] } if accept
 
-    flash question
+    status, title = get_status_and_title @focus_buf
     Ncurses.sync do
-      Ncurses.curs_set 1
+      draw_screen :sync => false, :status => status, :title => title
+      Ncurses.mvaddstr Ncurses.rows - 1, 0, question
       Ncurses.move Ncurses.rows - 1, question.length + 1
+      Ncurses.curs_set 1
       Ncurses.refresh
     end
 
+    @asking = true
     ret = nil
     done = false
-    @shelled = true
     until done
       key = Ncurses.nonblocking_getch or next
       if key == Ncurses::KEY_CANCEL
         done = true
-      elsif (accept && accept.member?(key)) || !accept
+      elsif accept.nil? || accept.empty? || accept.member?(key)
         ret = key
         done = true
       end
     end
 
-    @shelled = false
-
+    @asking = false
     Ncurses.sync do
       Ncurses.curs_set 0
-      erase_flash
-      draw_screen :sync => false
-      Ncurses.curs_set 0
+      draw_screen :sync => false, :status => status, :title => title
     end
 
     ret
@@ -602,6 +603,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) + 
@@ -615,7 +635,7 @@ EOS
     @minibuf_mutex.synchronize do
       m = @minibuf_stack.compact
       m << @flash if @flash
-      m << "" if m.empty?
+      m << "" if m.empty? unless @asking # to clear it
     end
 
     Ncurses.mutex.lock unless opts[:sync] == false