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