]> git.cworth.org Git - sup/blob - lib/sup/modes/scroll-mode.rb
Merge branch 'logging'
[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', "\C-e"
19     k.add :line_up, "Up one line", :up, 'k', 'K', "\C-y"
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, ' ', "\C-f"
23     k.add :page_up, "Up one page", :page_up, 'p', :backspace, "\C-b"
24     k.add :half_page_down, "Down one half page", "\C-d"
25     k.add :half_page_up, "Up one half page", "\C-u"
26     k.add :jump_to_start, "Jump to top", :home, '^', '1'
27     k.add :jump_to_end, "Jump to bottom", :end, '$', '0'
28     k.add :jump_to_left, "Jump to the left", '['
29     k.add :search_in_buffer, "Search in current buffer", '/'
30     k.add :continue_search_in_buffer, "Jump to next search occurrence in buffer", BufferManager::CONTINUE_IN_BUFFER_SEARCH_KEY
31   end
32
33   def initialize opts={}
34     @topline, @botline, @leftcol = 0, 0, 0
35     @slip_rows = opts[:slip_rows] || 0 # when we pgup/pgdown,
36                                        # how many lines do we keep?
37     @twiddles = opts.member?(:twiddles) ? opts[:twiddles] : true
38     @search_query = nil
39     @search_line = nil
40     @status = ""
41     super()
42   end
43
44   def rightcol; @leftcol + buffer.content_width; end
45
46   def draw
47     ensure_mode_validity
48     (@topline ... @botline).each { |ln| draw_line ln }
49     ((@botline - @topline) ... buffer.content_height).each do |ln|
50       if @twiddles
51         buffer.write ln, 0, "~", :color => :twiddle_color
52       else
53         buffer.write ln, 0, ""
54       end
55     end
56     @status = "lines #{@topline + 1}:#{@botline}/#{lines}"
57   end
58
59   def in_search?; @search_line end
60   def cancel_search!; @search_line = nil end
61
62   def continue_search_in_buffer
63     unless @search_query
64       BufferManager.flash "No current search!"
65       return
66     end
67
68     start = @search_line || search_start_line
69     line, col = find_text @search_query, start
70     if line.nil? && (start > 0)
71       line, col = find_text @search_query, 0
72       BufferManager.flash "Search wrapped to top!" if line
73     end
74     if line
75       @search_line = line + 1
76       search_goto_pos line, col, col + @search_query.display_length
77       buffer.mark_dirty
78     else
79       BufferManager.flash "Not found!"
80     end
81   end
82
83   def search_in_buffer
84     query = BufferManager.ask :search, "search in buffer: "
85     return if query.nil? || query.empty?
86     @search_query = Regexp.escape query
87     continue_search_in_buffer
88   end
89
90   ## subclasses can override these three!
91   def search_goto_pos line, leftcol, rightcol
92     search_goto_line line
93
94     if rightcol > self.rightcol # if it's occluded...
95       jump_to_col [rightcol - buffer.content_width + 1, 0].max # move right
96     end
97   end
98   def search_start_line; @topline end
99   def search_goto_line line; jump_to_line line end
100
101   def col_left
102     return unless @leftcol > 0
103     @leftcol -= COL_JUMP
104     buffer.mark_dirty
105   end
106
107   def col_right
108     @leftcol += COL_JUMP
109     buffer.mark_dirty
110   end
111
112   def jump_to_col col
113     col = col - (col % COL_JUMP)
114     buffer.mark_dirty unless @leftcol == col
115     @leftcol = col
116   end
117
118   def jump_to_left; jump_to_col 0; end
119
120   ## set top line to l
121   def jump_to_line l
122     l = l.clamp 0, lines - 1
123     return if @topline == l
124     @topline = l
125     @botline = [l + buffer.content_height, lines].min
126     buffer.mark_dirty
127   end
128
129   def at_top?; @topline == 0 end
130   def at_bottom?; @botline == lines end
131
132   def line_down; jump_to_line @topline + 1; end
133   def line_up;  jump_to_line @topline - 1; end
134   def page_down; jump_to_line @topline + buffer.content_height - @slip_rows; end
135   def page_up; jump_to_line @topline - buffer.content_height + @slip_rows; end
136   def half_page_down; jump_to_line @topline + buffer.content_height / 2; end
137   def half_page_up; jump_to_line @topline - buffer.content_height / 2; end
138   def jump_to_start; jump_to_line 0; end
139   def jump_to_end; jump_to_line lines - buffer.content_height; end
140
141   def ensure_mode_validity
142     @topline = @topline.clamp 0, [lines - 1, 0].max
143     @botline = [@topline + buffer.content_height, lines].min
144   end
145
146   def resize *a
147     super(*a)
148     ensure_mode_validity
149   end
150
151 protected
152
153   def find_text query, start_line
154     regex = /#{query}/i
155     (start_line ... lines).each do |i|
156       case(s = self[i])
157       when String
158         match = s =~ regex
159         return [i, match] if match
160       when Array
161         offset = 0
162         s.each do |color, string|
163           match = string =~ regex
164           if match
165             return [i, offset + match]
166           else
167             offset += string.display_length
168           end
169         end
170       end
171     end
172     nil
173   end
174
175   def draw_line ln, opts={}
176     regex = /(#{@search_query})/i
177     case(s = self[ln])
178     when String
179       if in_search?
180         draw_line_from_array ln, matching_text_array(s, regex), opts
181       else
182         draw_line_from_string ln, s, opts
183       end
184     when Array
185       if in_search?
186         ## seems like there ought to be a better way of doing this
187         array = []
188         s.each do |color, text| 
189           if text =~ regex
190             array += matching_text_array text, regex, color
191           else
192             array << [color, text]
193           end
194         end
195         draw_line_from_array ln, array, opts
196       else
197         draw_line_from_array ln, s, opts
198       end
199     else
200       raise "unknown drawable object: #{s.inspect} in #{self} for line #{ln}" # good for debugging
201     end
202
203       ## speed test
204       # str = s.map { |color, text| text }.join
205       # buffer.write ln - @topline, 0, str, :color => :none, :highlight => opts[:highlight]
206       # return
207   end
208
209   def matching_text_array s, regex, oldcolor=:none
210     s.split(regex).map do |text|
211       next if text.empty?
212       if text =~ regex
213         [:search_highlight_color, text]
214       else
215         [oldcolor, text]
216       end
217     end.compact + [[oldcolor, ""]]
218   end
219
220   def draw_line_from_array ln, a, opts
221     xpos = 0
222     a.each_with_index do |(color, text), i|
223       raise "nil text for color '#{color}'" if text.nil? # good for debugging
224       l = text.display_length
225       no_fill = i != a.size - 1
226
227       if xpos + l < @leftcol
228         buffer.write ln - @topline, 0, "", :color => color,
229                      :highlight => opts[:highlight]
230       elsif xpos < @leftcol
231         ## partial
232         buffer.write ln - @topline, 0, text[(@leftcol - xpos) .. -1],
233                      :color => color,
234                      :highlight => opts[:highlight], :no_fill => no_fill
235       else
236         buffer.write ln - @topline, xpos - @leftcol, text,
237                      :color => color, :highlight => opts[:highlight],
238                      :no_fill => no_fill
239       end
240       xpos += l
241     end
242   end
243
244   def draw_line_from_string ln, s, opts
245     buffer.write ln - @topline, 0, s[@leftcol .. -1], :highlight => opts[:highlight]
246   end
247 end
248
249 end
250