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