6 stdscr.getmaxyx lame, lamer
12 stdscr.getmaxyx lame, lamer
16 ## aaahhh, user input. who would have though that such a simple
17 ## idea would be SO FUCKING COMPLICATED?! because apparently
18 ## Ncurses.getch (and Curses.getch), even in cbreak mode, BLOCKS
19 ## ALL THREAD ACTIVITY. as in, no threads anywhere will run while
20 ## it's waiting for input. ok, fine, so we wrap it in a select. Of
21 ## course we also rely on Ncurses.getch to tell us when an xterm
22 ## resize has occurred, which select won't catch, so we won't
23 ## resize outselves after a sigwinch until the user hits a key.
24 ## and installing our own sigwinch handler means that the screen
25 ## size returned by getmaxyx() DOESN'T UPDATE! and Kernel#trap
26 ## RETURNS NIL as the previous handler!
28 ## so basically, resizing with multi-threaded ruby Ncurses
29 ## applications will always be broken.
31 ## i've coined a new word for this: lametarded.
33 if IO.select([$stdin], nil, nil, nil)
40 module_function :rows, :cols, :nonblocking_getch
42 KEY_CANCEL = "\a"[0] # ctrl-g
48 attr_reader :mode, :x, :y, :width, :height, :title
51 def initialize window, mode, width, height, opts={}
56 @title = opts[:title] || ""
57 @x, @y, @width, @height = 0, 0, width, height
60 def content_height; @height - 1; end
61 def content_width; @width; end
66 mode.resize rows, cols
74 def mark_dirty; @dirty = true; end
87 ## s nil means a blank line!
88 def write y, x, s, opts={}
89 return if x >= @width || y >= @height
91 @w.attrset Colormap.color_for(opts[:color] || :none, opts[:highlight])
94 @w.mvaddstr y, x, s[0 ... maxl]
95 unless s.length >= maxl || opts[:no_fill]
96 @w.mvaddstr(y, x + s.length, " " * (maxl - s.length))
105 write @height - 1, 0, " [#{mode.name}] #{title} #{mode.status}",
106 :color => :status_color
125 attr_reader :focus_buf
137 self.class.i_am_the_instance self
140 def buffers; @name_map.to_a; end
143 raise ArgumentError, "buffer not on stack: #{buf.inspect}" unless
145 return if buf == @focus_buf
146 @focus_buf.blur if @focus_buf
151 def raise_to_front buf
152 raise ArgumentError, "buffer not on stack: #{buf.inspect}" unless
161 raise_to_front @buffers.first
164 def roll_buffers_backwards
165 return unless @buffers.length > 1
166 raise_to_front @buffers[@buffers.length - 2]
170 @focus_buf && @focus_buf.mode.handle_input(c)
173 def exists? n; @name_map.member? n; end
174 def [] n; @name_map[n]; end
176 raise ArgumentError, "duplicate buffer name" if b && @name_map.member?(n)
180 def completely_redraw_screen
189 rows, cols = Ncurses.rows, Ncurses.cols
190 @buffers.each { |b| b.resize rows - 1, cols }
191 completely_redraw_screen
192 flash "resized to #{rows}x#{cols}"
195 def draw_screen skip_minibuf=false
198 ## disabling this for the time being, to help with debugging
199 ## (currently we only have one buffer visible at a time).
200 ## TODO: reenable this if we allow multiple buffers
201 false && @buffers.inject(@dirty) do |dirty, buf|
202 dirty ? buf.draw : buf.redraw
206 true && (@dirty ? @buffers.last.draw : @buffers.last.redraw)
208 draw_minibuf unless skip_minibuf
213 ## gets the mode from the block, which is only called if the buffer
214 ## doesn't already exist. this is useful in the case that generating
215 ## the mode is expensive, as it often is.
216 def spawn_unless_exists title, opts={}
217 if @name_map.member? title
218 Redwood::log "buffer '#{title}' already exists, raising to front"
219 raise_to_front @name_map[title]
222 spawn title, mode, opts
227 def spawn title, mode, opts={}
230 while @name_map.member? realtitle
231 realtitle = "#{title} #{num}"
235 Redwood::log "spawning buffer \"#{realtitle}\""
236 width = opts[:width] || Ncurses.cols
237 height = opts[:height] || Ncurses.rows - 1
239 ## since we are currently only doing multiple full-screen modes,
240 ## use stdscr for each window. once we become more sophisticated,
241 ## we may need to use a new Ncurses::WINDOW
243 ## w = Ncurses::WINDOW.new(height, width, (opts[:top] || 0),
244 ## (opts[:left] || 0))
246 raise "nil window" unless w
248 b = Buffer.new w, mode, width, height, :title => realtitle
250 @name_map[realtitle] = b
253 focus_on b unless @focus_buf
262 kill_buffer @buffers.first until @buffers.empty?
266 raise ArgumentError, "buffer not on stack: #{buf.inspect}" unless @buffers.member? buf
267 Redwood::log "killing buffer \"#{buf.title}\""
271 @name_map.delete buf.title
272 @focus_buf = nil if @focus_buf == buf
274 ## TODO: something intelligent here
275 ## for now I will simply prohibit killing the inbox buffer.
277 raise_to_front @buffers.last
281 def ask domain, question, default=nil
282 @textfields[domain] ||= TextField.new Ncurses.stdscr, Ncurses.rows - 1, 0,
284 tf = @textfields[domain]
286 ## this goddamn ncurses form shit is a fucking 1970's
287 ## nightmare. jesus christ. the exact sequence of ncurses events
288 ## that needs to happen in order to display a form and have the
289 ## entire screen not disappear and have the cursor in the right
290 ## place is TOO FUCKING COMPLICATED.
291 tf.activate question, default
299 while tf.handle_input(Ncurses.nonblocking_getch); end
309 ## some pretty lame code in here!
310 def ask_getch question, accept=nil
311 accept = accept.split(//).map { |x| x[0] } if accept
315 Ncurses.move Ncurses.rows - 1, question.length + 1
322 key = Ncurses.nonblocking_getch
323 if key == Ncurses::KEY_CANCEL
325 elsif (accept && accept.member?(key)) || !accept
339 def ask_yes_or_no question
340 [?y, ?Y].member? ask_getch(question, "ynYN")
344 s = @flash || @minibuf_stack.reverse.find { |x| x } || ""
346 Ncurses.attrset Colormap.color_for(:none)
347 Ncurses.mvaddstr Ncurses.rows - 1, 0, s + (" " * [Ncurses.cols - s.length,
352 id ||= @minibuf_stack.length
353 @minibuf_stack[id] = s
361 def erase_flash; @flash = nil; end
372 @minibuf_stack[id] = nil
373 if id == @minibuf_stack.length - 1
375 break unless @minibuf_stack[i].nil?
376 @minibuf_stack.delete_at i
385 def shell_out command