X-Git-Url: https://git.cworth.org/git?a=blobdiff_plain;f=lib%2Fsup%2Fbuffer.rb;h=d85090a6db57eab56a8e230a6aeb8e3a925e6339;hb=aef0216d7f988ab87a3430c9a65210f0d55dfc64;hp=782695b7655c2f64beb0249a3671bc13afe2ff11;hpb=9722a770cadd44ddc4af6ddcb56eb8edb35c93b9;p=sup diff --git a/lib/sup/buffer.rb b/lib/sup/buffer.rb index 782695b..d85090a 100644 --- a/lib/sup/buffer.rb +++ b/lib/sup/buffer.rb @@ -25,13 +25,13 @@ module Ncurses def mutex; @mutex ||= Mutex.new; end def sync &b; mutex.synchronize(&b); end - ## magically, this stuff seems to work now. i could swear it didn't - ## before. hm. def nonblocking_getch - if IO.select([$stdin], nil, nil, 1) - Ncurses.getch - else - nil + ## INSANTIY + ## it is NECESSARY to wrap Ncurses.getch in a select() otherwise all + ## background threads will be BLOCKED. (except in very modern versions + ## of libncurses-ruby. the current one on ubuntu seems to work well.) + if IO.select([$stdin], nil, nil, 0.5) + c = Ncurses.getch end end @@ -51,8 +51,8 @@ module Redwood class InputSequenceAborted < StandardError; end class Buffer - attr_reader :mode, :x, :y, :width, :height, :title - bool_reader :dirty + attr_reader :mode, :x, :y, :width, :height, :title, :atime + bool_reader :dirty, :system bool_accessor :force_to_top def initialize window, mode, width, height, opts={} @@ -63,13 +63,14 @@ class Buffer @title = opts[:title] || "" @force_to_top = opts[:force_to_top] || false @x, @y, @width, @height = 0, 0, width, height - @in_x = ENV["TERM"] =~ /(xterm|rxvt)/ + @atime = Time.at 0 + @system = opts[:system] || false end def content_height; @height - 1; end def content_width; @width; end - def resize rows, cols + def resize rows, cols return if cols == @width && rows == @height @width = cols @height = rows @@ -98,6 +99,7 @@ class Buffer @mode.draw draw_status status commit + @atime = Time.now end ## s nil means a blank line! @@ -106,10 +108,16 @@ class Buffer @w.attrset Colormap.color_for(opts[:color] || :none, opts[:highlight]) s ||= "" - maxl = @width - x - @w.mvaddstr y, x, s[0 ... maxl] - unless s.length >= maxl || opts[:no_fill] - @w.mvaddstr(y, x + s.length, " " * (maxl - s.length)) + maxl = @width - x # maximum display width width + stringl = maxl # string "length" + ## the next horribleness is thanks to ruby's lack of widechar support + stringl += 1 while stringl < s.length && s[0 ... stringl].display_length < maxl + @w.mvaddstr y, x, s[0 ... stringl] + unless opts[:no_fill] + l = s.display_length + unless l >= maxl + @w.mvaddstr(y, x + l, " " * (maxl - l)) + end end end @@ -166,6 +174,15 @@ called at least once per keystroke, so excessive computation is discouraged. Variables: the same as status-bar-text hook. Return value: a string to be used as the terminal title. +EOS + + HookManager.register "extra-contact-addresses", <" entries. + +Variables: none +Return value: an array of email address strings. EOS def initialize @@ -178,10 +195,14 @@ EOS @textfields = {} @flash = nil @shelled = @asking = false - - self.class.i_am_the_instance self + @in_x = ENV["TERM"] =~ /(xterm|rxvt|screen)/ + @sigwinch_happened = false + @sigwinch_mutex = Mutex.new end + def sigwinch_happened!; @sigwinch_mutex.synchronize { @sigwinch_happened = true } end + def sigwinch_happened?; @sigwinch_mutex.synchronize { @sigwinch_happened } end + def buffers; @name_map.to_a; end def focus_on buf @@ -212,14 +233,20 @@ EOS ## have to change this. but it's not clear that we will ever actually ## do that. def roll_buffers - @buffers.last.force_to_top = false - raise_to_front @buffers.first + bufs = rollable_buffers + bufs.last.force_to_top = false + raise_to_front bufs.first end def roll_buffers_backwards - return unless @buffers.length > 1 - @buffers.last.force_to_top = false - raise_to_front @buffers[@buffers.length - 2] + bufs = rollable_buffers + return unless bufs.length > 1 + bufs.last.force_to_top = false + raise_to_front bufs[bufs.length - 2] + end + + def rollable_buffers + @buffers.select { |b| !b.system? || @buffers.last == b } end def handle_input c @@ -243,6 +270,14 @@ EOS def completely_redraw_screen return if @shelled + ## this magic makes Ncurses get the new size of the screen + Ncurses.endwin + Ncurses.stdscr.keypad 1 + Ncurses.curs_set 0 + Ncurses.refresh + @sigwinch_mutex.synchronize { @sigwinch_happened = false } + debug "new screen size is #{Ncurses.rows} x #{Ncurses.cols}" + status, title = get_status_and_title(@focus_buf) # must be called outside of the ncurses lock Ncurses.sync do @@ -263,7 +298,8 @@ EOS get_status_and_title @focus_buf # must be called outside of the ncurses lock end - print "\033]2;#{title}\07" if title && @in_x + ## http://rtfm.etla.org/xterm/ctlseq.html (see Operating System Controls) + print "\033]0;#{title}\07" if title && @in_x Ncurses.mutex.lock unless opts[:sync] == false @@ -328,7 +364,7 @@ EOS ## w = Ncurses::WINDOW.new(height, width, (opts[:top] || 0), ## (opts[:left] || 0)) w = Ncurses.stdscr - b = Buffer.new w, mode, width, height, :title => realtitle, :force_to_top => (opts[:force_to_top] || false) + b = Buffer.new w, mode, width, height, :title => realtitle, :force_to_top => opts[:force_to_top], :system => opts[:system] mode.buffer = b @name_map[realtitle] = b @@ -398,7 +434,7 @@ EOS def ask_with_completions domain, question, completions, default=nil ask domain, question, default do |s| - completions.select { |x| x =~ /^#{s}/i }.map { |x| [x, x] } + completions.select { |x| x =~ /^#{Regexp::escape s}/i }.map { |x| [x, x] } end end @@ -414,7 +450,7 @@ EOS raise "william screwed up completion: #{partial.inspect}" end - completions.select { |x| x =~ /^#{target}/i }.map { |x| [prefix + x, x] } + completions.select { |x| x =~ /^#{Regexp::escape target}/i }.map { |x| [prefix + x, x] } end end @@ -423,7 +459,7 @@ EOS prefix, target = partial.split_on_commas_with_remainder target ||= prefix.pop || "" prefix = prefix.join(", ") + (prefix.empty? ? "" : ", ") - completions.select { |x| x =~ /^#{target}/i }.map { |x| [prefix + x, x] } + completions.select { |x| x =~ /^#{Regexp::escape target}/i }.sort_by { |c| [ContactManager.contact_for(c) ? 0 : 1, c] }.map { |x| [prefix + x, x] } end end @@ -436,7 +472,7 @@ EOS if dir [[s.sub(full, dir), "~#{name}"]] else - users.select { |u| u =~ /^#{name}/ }.map do |u| + users.select { |u| u =~ /^#{Regexp::escape name}/ }.map do |u| [s.sub("~#{name}", "~#{u}"), "~#{u}"] end end @@ -449,13 +485,13 @@ EOS end if answer - answer = + answer = if answer.empty? spawn_modal "file browser", FileBrowserMode.new elsif File.directory?(answer) spawn_modal "file browser", FileBrowserMode.new(answer) else - answer + File.expand_path answer end end @@ -465,16 +501,18 @@ EOS ## returns an array of labels def ask_for_labels domain, question, default_labels, forbidden_labels=[] default_labels = default_labels - forbidden_labels - LabelManager::RESERVED_LABELS - default = default_labels.join(" ") + default = default_labels.to_a.join(" ") default += " " unless default.empty? - applyable_labels = (LabelManager.applyable_labels - forbidden_labels).map { |l| LabelManager.string_for l }.sort_by { |s| s.downcase } + # here I would prefer to give more control and allow all_labels instead of + # user_defined_labels only + applyable_labels = (LabelManager.user_defined_labels - forbidden_labels).map { |l| LabelManager.string_for l }.sort_by { |s| s.downcase } answer = ask_many_with_completions domain, question, applyable_labels, default return unless answer - user_labels = answer.split(/\s+/).map { |l| l.intern } + user_labels = answer.to_set_of_symbols user_labels.each do |l| if forbidden_labels.include?(l) || LabelManager::RESERVED_LABELS.include?(l) BufferManager.flash "'#{l}' is a reserved label!" @@ -487,15 +525,16 @@ EOS def ask_for_contacts domain, question, default_contacts=[] default = default_contacts.map { |s| s.to_s }.join(" ") default += " " unless default.empty? - + recent = Index.load_contacts(AccountManager.user_emails, :num => 10).map { |c| [c.full_address, c.email] } contacts = ContactManager.contacts.map { |c| [ContactManager.alias_for(c), c.full_address, c.email] } - completions = (recent + contacts).flatten.uniq.sort + completions = (recent + contacts).flatten.uniq + completions += HookManager.run("extra-contact-addresses") || [] answer = BufferManager.ask_many_emails_with_completions domain, question, completions, default if answer - answer.split_on_commas.map { |x| ContactManager.contact_for(x.downcase) || PersonManager.person_for(x) } + answer.split_on_commas.map { |x| ContactManager.contact_for(x) || Person.from_address(x) } end end @@ -557,7 +596,6 @@ EOS def ask_getch question, accept=nil raise "impossible!" if @asking - @asking = true accept = accept.split(//).map { |x| x[0] } if accept @@ -570,6 +608,7 @@ EOS Ncurses.refresh end + @asking = true ret = nil done = false until done @@ -701,6 +740,7 @@ EOS Ncurses.sync do Ncurses.endwin system command + Ncurses.stdscr.keypad 1 Ncurses.refresh Ncurses.curs_set 0 end