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