]> git.cworth.org Git - sup/blob - lib/sup/modes/scroll-mode.rb
add buffer search with '/' and 'n', and change index search to '\'
[sup] / lib / sup / modes / scroll-mode.rb
1 module Redwood
2
3 class ScrollMode < Mode
4   ## we define topline and botline as the top and bottom lines of any
5   ## content in the currentview.
6   
7   ## we left leftcol and rightcol as the left and right columns of any
8   ## content in the current view. but since we're operating in a
9   ## line-centric fashion, rightcol is always leftcol + the buffer
10   ## width. (whereas botline is topline + at most the buffer height,
11   ## and can be == to topline in the case that there's no content.)
12
13   attr_reader :status, :topline, :botline, :leftcol
14
15   COL_JUMP = 2
16
17   register_keymap do |k|
18     k.add :line_down, "Down one line", :down, 'j', 'J'
19     k.add :line_up, "Up one line", :up, 'k', 'K'
20     k.add :col_left, "Left one column", :left, 'h'
21     k.add :col_right, "Right one column", :right, 'l'
22     k.add :page_down, "Down one page", :page_down, 'n', ' '
23     k.add :page_up, "Up one page", :page_up, 'p', :backspace
24     k.add :jump_to_start, "Jump to top", :home, '^', '1'
25     k.add :jump_to_end, "Jump to bottom", :end, '$', '0'
26     k.add :jump_to_left, "Jump to the left", '['
27     k.add :search_in_buffer, "Search in current buffer", '/'
28     k.add :continue_search_in_buffer, "Jump to next search occurrence in buffer", BufferManager::CONTINUE_IN_BUFFER_SEARCH_KEY
29   end
30
31   def initialize opts={}
32     @topline, @botline, @leftcol = 0, 0, 0
33     @slip_rows = opts[:slip_rows] || 0 # when we pgup/pgdown,
34                                        # how many lines do we keep?
35     @twiddles = opts.member?(:twiddles) ? opts[:twiddles] : true
36     @search_query = nil
37     @search_line = nil
38     super()
39   end
40
41   def rightcol; @leftcol + buffer.content_width; end
42
43   def draw
44     ensure_mode_validity
45     (@topline ... @botline).each { |ln| draw_line ln }
46     ((@botline - @topline) ... buffer.content_height).each do |ln|
47       if @twiddles
48         buffer.write ln, 0, "~", :color => :twiddle_color
49       else
50         buffer.write ln, 0, ""
51       end
52     end
53     @status = "lines #{@topline + 1}:#{@botline}/#{lines}"
54   end
55
56   def in_search?; @search_line end
57
58   def cancel_search!; @search_line = nil end
59
60   def continue_search_in_buffer
61     unless @search_query
62       BufferManager.flash "No current search!"
63       return
64     end
65
66     if(line = find_text(@search_query, @search_line || search_start_line))
67       @search_line = line + 1
68       search_goto_line line
69       buffer.mark_dirty
70     else
71       BufferManager.flash "Not found!"
72     end
73   end
74
75   def search_in_buffer
76     query = BufferManager.ask(:search, "query: ") or return
77     @search_query = Regexp.escape query
78     continue_search_in_buffer
79   end
80
81   ## subclasses can override these two!
82   def search_goto_line line; jump_to_line line end
83   def search_start_line; @topline end
84
85   def col_left
86     return unless @leftcol > 0
87     @leftcol -= COL_JUMP
88     buffer.mark_dirty
89   end
90
91   def col_right
92     @leftcol += COL_JUMP
93     buffer.mark_dirty
94   end
95
96   def jump_to_col col
97     buffer.mark_dirty unless @leftcol == col
98     @leftcol = col
99   end
100
101   def jump_to_left; jump_to_col 0; end
102
103   ## set top line to l
104   def jump_to_line l
105     l = l.clamp 0, lines - 1
106     return if @topline == l
107     @topline = l
108     @botline = [l + buffer.content_height, lines].min
109     buffer.mark_dirty
110   end
111
112   def at_top?; @topline == 0 end
113   def at_bottom?; @botline == lines end
114
115   def line_down; jump_to_line @topline + 1; end
116   def line_up;  jump_to_line @topline - 1; end
117   def page_down; jump_to_line @topline + buffer.content_height - @slip_rows; end
118   def page_up; jump_to_line @topline - buffer.content_height + @slip_rows; end
119   def jump_to_start; jump_to_line 0; end
120   def jump_to_end; jump_to_line lines - buffer.content_height; end
121
122   def ensure_mode_validity
123     @topline = @topline.clamp 0, [lines - 1, 0].max
124     @botline = [@topline + buffer.content_height, lines].min
125   end
126
127   def resize *a
128     super(*a)
129     ensure_mode_validity
130   end
131
132 protected
133
134   def find_text query, start_line
135     regex = /#{query}/i
136     (start_line ... lines).each do |i|
137       case(s = self[i])
138       when String
139         return i if s =~ regex
140       when Array
141         return i if s.any? { |color, string| string =~ regex }
142       end
143     end
144     nil
145   end
146
147   def draw_line ln, opts={}
148     regex = /(#{@search_query})/i
149     case(s = self[ln])
150     when String
151       if in_search?
152         draw_line_from_array ln, matching_text_array(s, regex), opts
153       else
154         draw_line_from_string ln, s, opts
155       end
156     when Array
157       if in_search?
158         ## seems like there ought to be a better way of doing this
159         array = []
160         s.each do |color, text| 
161           if text =~ regex
162             array += matching_text_array text, regex, color
163           else
164             array << [color, text]
165           end
166         end
167         draw_line_from_array ln, array, opts
168       else
169         draw_line_from_array ln, s, opts
170       end
171     else
172       raise "unknown drawable object: #{s.inspect}" # good for debugging
173     end
174
175       ## speed test
176       # str = s.map { |color, text| text }.join
177       # buffer.write ln - @topline, 0, str, :color => :none, :highlight => opts[:highlight]
178       # return
179   end
180
181   def matching_text_array s, regex, oldcolor=:none
182     s.split(regex).map do |text|
183       next if text.empty?
184       if text =~ regex
185         [:search_highlight_color, text]
186       else
187         [oldcolor, text]
188       end
189     end.compact + [[oldcolor, ""]]
190   end
191
192   def draw_line_from_array ln, a, opts
193     xpos = 0
194     a.each do |color, text|
195       raise "nil text for color '#{color}'" if text.nil? # good for debugging
196       
197       if xpos + text.length < @leftcol
198         buffer.write ln - @topline, 0, "", :color => color,
199                      :highlight => opts[:highlight]
200         xpos += text.length
201       elsif xpos < @leftcol
202         ## partial
203         buffer.write ln - @topline, 0, text[(@leftcol - xpos) .. -1],
204                      :color => color,
205                      :highlight => opts[:highlight]
206         xpos += text.length
207       else
208         buffer.write ln - @topline, xpos - @leftcol, text,
209                      :color => color, :highlight => opts[:highlight]
210         xpos += text.length
211       end
212     end
213   end
214
215   def draw_line_from_string ln, s, opts
216     buffer.write ln - @topline, 0, s[@leftcol .. -1], :highlight => opts[:highlight]
217   end
218 end
219
220 end
221