]> git.cworth.org Git - sup/blob - lib/sup/modes/line-cursor-mode.rb
yet another sig pattern :(
[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   def line_down # overwrite scrollmode
59     super
60     call_load_more_callbacks([topline + buffer.content_height - lines, 10].max) if topline + buffer.content_height > lines
61     set_cursor_pos topline if @curpos < topline
62   end
63
64   def line_up # overwrite scrollmode
65     super
66     set_cursor_pos botline - 1 if @curpos > botline - 1
67   end
68
69   def cursor_down
70     call_load_more_callbacks buffer.content_height if @curpos == lines - 1
71     return false unless @curpos < lines - 1
72
73     if @curpos >= botline - 1
74       page_down
75       set_cursor_pos topline
76     else
77       @curpos += 1
78       unless buffer.dirty?
79         draw_line @curpos - 1
80         draw_line @curpos
81         set_status
82         buffer.commit
83       end
84     end
85     true
86   end
87
88   def cursor_up
89     return false unless @curpos > @cursor_top
90     if @curpos == topline
91       old_topline = topline
92       page_up
93       set_cursor_pos [old_topline - 1, topline].max
94     else
95       @curpos -= 1
96       unless buffer.dirty?
97         draw_line @curpos + 1
98         draw_line @curpos
99         set_status
100         buffer.commit
101       end
102     end
103     true
104   end
105
106   def page_up # overwrite
107     if topline <= @cursor_top
108       set_cursor_pos @cursor_top
109     else
110       relpos = @curpos - topline
111       super
112       set_cursor_pos topline + relpos
113     end
114   end
115
116   ## more complicated than one might think. three behaviors.
117   def page_down
118     ## if we're on the last page, and it's not a full page, just move
119     ## the cursor down to the bottom and assume we can't load anything
120     ## else via the callbacks.
121     if topline > lines - buffer.content_height
122       set_cursor_pos(lines - 1)
123
124     ## if we're on the last page, and it's a full page, try and load
125     ## more lines via the callbacks and then shift the page down
126     elsif topline == lines - buffer.content_height
127       call_load_more_callbacks buffer.content_height
128       super
129
130     ## otherwise, just move down
131     else
132       relpos = @curpos - topline
133       super
134       set_cursor_pos [topline + relpos, lines - 1].min
135     end
136   end
137
138   def jump_to_start
139     super
140     set_cursor_pos @cursor_top
141   end
142
143   def jump_to_end
144     super if topline < (lines - buffer.content_height)
145     set_cursor_pos(lines - 1)
146   end
147
148 private
149
150   def set_status
151     l = lines
152     @status = l > 0 ? "line #{@curpos + 1} of #{l}" : ""
153   end
154
155   def call_load_more_callbacks size
156     go = 
157       @load_more_callbacks_m.synchronize do
158         if @load_more_callbacks_active
159           false
160         else
161           @load_more_callbacks_active = true
162         end
163     end
164
165     return unless go
166
167     @load_more_callbacks.each { |c| c.call size }
168     @load_more_callbacks_active = false
169   end    
170
171 end
172
173 end