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