k.add :list_buffers, "List all buffers", 'B'
   k.add :list_contacts, "List contacts", 'C'
   k.add :redraw, "Redraw screen", :ctrl_l
-  k.add :search, "Search messages", '/'
+  k.add :search, "Search all messages", '\\'
   k.add :list_labels, "List labels", 'L'
   k.add :poll, "Poll for new messages", 'P'
   k.add :compose, "Compose new message", 'm'
+  k.add :nothing, "Do nothing", :ctrl_g
   k.add :recall_draft, "Edit most recent draft message", 'R'
 end
 
     c.add :reply_mode_selected_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK, Ncurses::A_BOLD
     c.add :reply_mode_unselected_color, Ncurses::COLOR_CYAN, Ncurses::COLOR_BLACK
     c.add :reply_mode_label_color, Ncurses::COLOR_CYAN, Ncurses::COLOR_BLACK
+    c.add :search_highlight_color, Ncurses::COLOR_BLACK, Ncurses::COLOR_YELLOW, Ncurses::A_BOLD, :highlight => :search_highlight_color
   end
 
   log "initializing buffer manager"
       when :redraw
         bm.completely_redraw_screen
       else
-        bm.flash "Unknown key press '#{c.to_character}' for #{bm.focus_buf.mode.name}."
+        bm.flash "Unknown keypress '#{c.to_character}' for #{bm.focus_buf.mode.name}."
       end
     end
 
 
     k.add :jump_to_start, "Jump to top", :home, '^', '1'
     k.add :jump_to_end, "Jump to bottom", :end, '$', '0'
     k.add :jump_to_left, "Jump to the left", '['
+    k.add :search_in_buffer, "Search in current buffer", '/'
+    k.add :continue_search_in_buffer, "Jump to next search occurrence in buffer", BufferManager::CONTINUE_IN_BUFFER_SEARCH_KEY
   end
 
   def initialize opts={}
     @slip_rows = opts[:slip_rows] || 0 # when we pgup/pgdown,
                                        # how many lines do we keep?
     @twiddles = opts.member?(:twiddles) ? opts[:twiddles] : true
+    @search_query = nil
+    @search_line = nil
     super()
   end
 
     @status = "lines #{@topline + 1}:#{@botline}/#{lines}"
   end
 
+  def in_search?; @search_line end
+
+  def cancel_search!; @search_line = nil end
+
+  def continue_search_in_buffer
+    unless @search_query
+      BufferManager.flash "No current search!"
+      return
+    end
+
+    if(line = find_text(@search_query, @search_line || search_start_line))
+      @search_line = line + 1
+      search_goto_line line
+      buffer.mark_dirty
+    else
+      BufferManager.flash "Not found!"
+    end
+  end
+
+  def search_in_buffer
+    query = BufferManager.ask(:search, "query: ") or return
+    @search_query = Regexp.escape query
+    continue_search_in_buffer
+  end
+
+  ## subclasses can override these two!
+  def search_goto_line line; jump_to_line line end
+  def search_start_line; @topline end
+
   def col_left
     return unless @leftcol > 0
     @leftcol -= COL_JUMP
 
 protected
 
+  def find_text query, start_line
+    regex = /#{query}/i
+    (start_line ... lines).each do |i|
+      case(s = self[i])
+      when String
+        return i if s =~ regex
+      when Array
+        return i if s.any? { |color, string| string =~ regex }
+      end
+    end
+    nil
+  end
+
   def draw_line ln, opts={}
+    regex = /(#{@search_query})/i
     case(s = self[ln])
     when String
-      buffer.write ln - @topline, 0, s[@leftcol .. -1],
-                   :highlight => opts[:highlight]
+      if in_search?
+        draw_line_from_array ln, matching_text_array(s, regex), opts
+      else
+        draw_line_from_string ln, s, opts
+      end
     when Array
-      xpos = 0
+      if in_search?
+        ## seems like there ought to be a better way of doing this
+        array = []
+        s.each do |color, text| 
+          if text =~ regex
+            array += matching_text_array text, regex, color
+          else
+            array << [color, text]
+          end
+        end
+        draw_line_from_array ln, array, opts
+      else
+        draw_line_from_array ln, s, opts
+      end
+    else
+      raise "unknown drawable object: #{s.inspect}" # good for debugging
+    end
 
       ## speed test
       # str = s.map { |color, text| text }.join
       # buffer.write ln - @topline, 0, str, :color => :none, :highlight => opts[:highlight]
       # return
+  end
 
-      s.each do |color, text|
-        raise "nil text for color '#{color}'" if text.nil? # good for debugging
-        if xpos + text.length < @leftcol
-          buffer.write ln - @topline, 0, "", :color => color,
-                       :highlight => opts[:highlight]
-          xpos += text.length
-        elsif xpos < @leftcol
-          ## partial
-          buffer.write ln - @topline, 0, text[(@leftcol - xpos) .. -1],
-                       :color => color,
-                       :highlight => opts[:highlight]
-          xpos += text.length
-        else
-          buffer.write ln - @topline, xpos - @leftcol, text,
-                       :color => color, :highlight => opts[:highlight]
-          xpos += text.length
-        end
+  def matching_text_array s, regex, oldcolor=:none
+    s.split(regex).map do |text|
+      next if text.empty?
+      if text =~ regex
+        [:search_highlight_color, text]
+      else
+        [oldcolor, text]
+      end
+    end.compact + [[oldcolor, ""]]
+  end
 
+  def draw_line_from_array ln, a, opts
+    xpos = 0
+    a.each do |color, text|
+      raise "nil text for color '#{color}'" if text.nil? # good for debugging
+      
+      if xpos + text.length < @leftcol
+        buffer.write ln - @topline, 0, "", :color => color,
+                     :highlight => opts[:highlight]
+        xpos += text.length
+      elsif xpos < @leftcol
+        ## partial
+        buffer.write ln - @topline, 0, text[(@leftcol - xpos) .. -1],
+                     :color => color,
+                     :highlight => opts[:highlight]
+        xpos += text.length
+      else
+        buffer.write ln - @topline, xpos - @leftcol, text,
+                     :color => color, :highlight => opts[:highlight]
+        xpos += text.length
       end
     end
   end
+
+  def draw_line_from_string ln, s, opts
+    buffer.write ln - @topline, 0, s[@leftcol .. -1], :highlight => opts[:highlight]
+  end
 end
 
 end
+