]> git.cworth.org Git - sup/blob - lib/sup/modes/line-cursor-mode.rb
Merge branch 'master' into next
[sup] / lib / sup / modes / line-cursor-mode.rb
1 module Redwood
2
3 ## extends ScrollMode to have a line-based cursor.
4 class LineCursorMode < ScrollMode
5   register_keymap do |k|
6     ## overwrite scrollmode binding on arrow keys for cursor movement
7     ## but j and k still scroll!
8     k.add :cursor_down, "Move cursor down one line", :down, 'j'
9     k.add :cursor_up, "Move cursor up one line", :up, 'k'
10     k.add :select, "Select this item", :enter
11   end
12
13   attr_reader :curpos
14
15   def initialize opts={}
16     @cursor_top = @curpos = opts.delete(:skip_top_rows) || 0
17     @load_more_callbacks = []
18     @load_more_q = Queue.new
19     @load_more_thread = ::Thread.new do
20       while true
21         e = @load_more_q.pop
22         @load_more_callbacks.each { |c| c.call e }
23       end
24     end
25
26     super opts
27   end
28
29   def cleanup
30     @load_more_thread.kill
31     super
32   end
33
34   def draw
35     super
36     set_status
37   end
38
39 protected
40
41   ## callbacks when the cursor is asked to go beyond the bottom
42   def to_load_more &b
43     @load_more_callbacks << b
44   end
45
46   def draw_line ln, opts={}
47     if ln == @curpos
48       super ln, :highlight => true, :debug => opts[:debug]
49     else
50       super
51     end
52   end
53
54   def ensure_mode_validity
55     super
56     raise @curpos.inspect unless @curpos.is_a?(Integer)
57     c = @curpos.clamp topline, botline - 1
58     c = @cursor_top if c < @cursor_top
59     buffer.mark_dirty unless c == @curpos
60     @curpos = c
61   end
62
63   def set_cursor_pos p
64     return if @curpos == p
65     @curpos = p.clamp @cursor_top, lines
66     buffer.mark_dirty
67   end
68
69   ## override search behavior to be cursor-based. this is a stupid
70   ## implementation and should be made better. TODO: improve.
71   def search_goto_line line
72     page_down while line >= botline
73     page_up while line < topline
74     set_cursor_pos line
75   end
76
77   def search_start_line; @curpos end
78  
79   def line_down # overwrite scrollmode
80     super
81     call_load_more_callbacks([topline + buffer.content_height - lines, 10].max) if topline + buffer.content_height > lines
82     set_cursor_pos topline if @curpos < topline
83   end
84
85   def line_up # overwrite scrollmode
86     super
87     set_cursor_pos botline - 1 if @curpos > botline - 1
88   end
89
90   def cursor_down
91     call_load_more_callbacks buffer.content_height if @curpos >= lines - [buffer.content_height/2,1].max
92     return false unless @curpos < lines - 1
93
94     if @curpos >= botline - 1
95       page_down
96       set_cursor_pos topline
97     else
98       @curpos += 1
99       unless buffer.dirty?
100         draw_line @curpos - 1
101         draw_line @curpos
102         set_status
103         buffer.commit
104       end
105     end
106     true
107   end
108
109   def cursor_up
110     return false unless @curpos > @cursor_top
111     if @curpos == topline
112       old_topline = topline
113       page_up
114       set_cursor_pos [old_topline - 1, topline].max
115     else
116       @curpos -= 1
117       unless buffer.dirty?
118         draw_line @curpos + 1
119         draw_line @curpos
120         set_status
121         buffer.commit
122       end
123     end
124     true
125   end
126
127   def page_up # overwrite
128     if topline <= @cursor_top
129       set_cursor_pos @cursor_top
130     else
131       relpos = @curpos - topline
132       super
133       set_cursor_pos topline + relpos
134     end
135   end
136
137   ## more complicated than one might think. three behaviors.
138   def page_down
139     ## if we're on the last page, and it's not a full page, just move
140     ## the cursor down to the bottom and assume we can't load anything
141     ## else via the callbacks.
142     if topline > lines - buffer.content_height
143       set_cursor_pos(lines - 1)
144
145     ## if we're on the last page, and it's a full page, try and load
146     ## more lines via the callbacks and then shift the page down
147     elsif topline == lines - buffer.content_height
148       call_load_more_callbacks buffer.content_height
149       super
150
151     ## otherwise, just move down
152     else
153       relpos = @curpos - topline
154       super
155       set_cursor_pos [topline + relpos, lines - 1].min
156     end
157   end
158
159   def jump_to_start
160     super
161     set_cursor_pos @cursor_top
162   end
163
164   def jump_to_end
165     super if topline < (lines - buffer.content_height)
166     set_cursor_pos(lines - 1)
167   end
168
169 private
170
171   def set_status
172     l = lines
173     @status = l > 0 ? "line #{@curpos + 1} of #{l}" : ""
174   end
175
176   def call_load_more_callbacks size
177     @load_more_q.push size
178   end
179 end
180
181 end