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