sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/

lib/sup/util/ncurses.rb (8729B) - raw

      1 require 'ncursesw'
      2 require 'sup/util'
      3 
      4 if defined? Ncurses
      5 module Ncurses
      6 
      7   ## Helper class for storing keycodes
      8   ## and multibyte characters.
      9   class CharCode < String
     10     ## Status code allows us to detect
     11     ## printable characters and control codes.
     12     attr_reader :status
     13 
     14     ## Reads character from user input.
     15     def self.nonblocking_getwch
     16       # If we get input while we're shelled, we'll ignore it for the
     17       # moment and use Ncurses.sync to wait until the shell_out is done.
     18       begin
     19         s, c = Redwood::BufferManager.shelled? ? Ncurses.sync { nil } : Ncurses.get_wch
     20         break if s != Ncurses::ERR
     21       end until IO.select([$stdin], nil, nil, 2)
     22       [s, c]
     23     end
     24 
     25     ## Returns empty singleton.
     26     def self.empty
     27       Empty.instance
     28     end
     29 
     30     ## Creates new instance of CharCode
     31     ## that keeps a given keycode.
     32     def self.keycode(c)
     33       generate c, Ncurses::KEY_CODE_YES
     34     end
     35 
     36     ## Creates new instance of CharCode
     37     ## that keeps a printable character.
     38     def self.character(c)
     39       generate c, Ncurses::OK
     40     end
     41 
     42     ## Generates new object like new
     43     ## but for empty or erroneous objects
     44     ## it returns empty singleton.
     45     def self.generate(c = nil, status = Ncurses::OK)
     46       if status == Ncurses::ERR || c.nil? || c === Ncurses::ERR
     47         empty
     48       else
     49         new(c, status)
     50       end
     51     end
     52 
     53     ## Gets character from input.
     54     ## Pretends ctrl-c's are ctrl-g's.
     55     def self.get handle_interrupt=true
     56       begin
     57         status, code = nonblocking_getwch
     58         generate code, status
     59       rescue Interrupt => e
     60         raise e unless handle_interrupt
     61         keycode Ncurses::KEY_CANCEL
     62       end
     63     end
     64 
     65     ## Enables dumb mode for any new instance.
     66     def self.dumb!
     67       @dumb = true
     68     end
     69 
     70     ## Asks if dumb mode was set
     71     def self.dumb?
     72       defined?(@dumb) && @dumb
     73     end
     74 
     75     def initialize(c = "", status = Ncurses::OK)
     76       @status = status
     77       c = "" if c.nil?
     78       return super("") if status == Ncurses::ERR
     79       c = enc_char(c) if c.is_a?(Integer)
     80       super c.length > 1 ? c[0,1] : c
     81     end
     82 
     83     ## Proxy method for String's replace
     84     def replace(c)
     85       return self if c.object_id == object_id
     86       if c.is_a?(self.class)
     87         @status = c.status
     88         super(c)
     89       else
     90         @status = Ncurses::OK
     91         c = "" if c.nil?
     92         c = enc_char(c) if c.is_a?(Integer)
     93         super c.length > 1 ? c[0,1] : c
     94       end
     95     end
     96 
     97     def to_character    ; character? ? self : "<#{code}>"           end  ## Returns character or code as a string
     98     def to_keycode      ; keycode?   ? code : Ncurses::ERR          end  ## Returns keycode or ERR if it's not a keycode
     99     def to_sequence     ; bytes.to_a                                end  ## Returns unpacked sequence of bytes for a character
    100     def code            ; ord                                       end  ## Returns decimal representation of a character
    101     def is_keycode?(c)  ; keycode?   &&  code == c                  end  ## Tests if keycode matches
    102     def is_character?(c); character? &&  self == c                  end  ## Tests if character matches
    103     def try_keycode     ; keycode?   ? code : nil                   end  ## Returns dec. code if keycode, nil otherwise
    104     def try_character   ; character? ? self : nil                   end  ## Returns character if character, nil otherwise
    105     def keycode         ; try_keycode                               end  ## Alias for try_keycode
    106     def character       ; try_character                             end  ## Alias for try_character
    107     def character?      ; dumb? || @status == Ncurses::OK           end  ## Returns true if character
    108     def character!      ; @status  = Ncurses::OK ; self             end  ## Sets character flag
    109     def keycode?        ; dumb? || @status == Ncurses::KEY_CODE_YES end  ## Returns true if keycode
    110     def keycode!        ; @status  = Ncurses::KEY_CODE_YES ; self   end  ## Sets keycode flag
    111     def keycode=(c)     ; replace(c); keycode! ; self               end  ## Sets keycode    
    112     def present?        ; not empty?                                end  ## Proxy method
    113     def printable?      ; character?                                end  ## Alias for character?
    114     def dumb?           ; self.class.dumb?                          end  ## True if we cannot distinguish keycodes from characters
    115 
    116     # Empty singleton that
    117     # keeps GC from going crazy.
    118     class Empty < CharCode
    119       include Redwood::Singleton
    120 
    121       ## Wrap methods that may change us
    122       ## and generate new object instead.
    123       [ :"[]=", :"<<", :replace, :insert, :prepend, :append, :concat, :force_encoding, :setbyte ].
    124       select{ |m| public_method_defined?(m) }.
    125       concat(public_instance_methods.grep(/!\z/)).
    126       each do |m|
    127         class_eval <<-EVAL
    128           def #{m}(*args)
    129             CharCode.new.#{m}(*args)
    130           end
    131         EVAL
    132       end
    133 
    134       ## proxy with class-level instance variable delegation
    135       def self.dumb?
    136         superclass.dumb? or !!@dumb
    137       end
    138 
    139       def self.empty
    140         instance
    141       end
    142 
    143       def initialize
    144         super("", Ncurses::ERR)
    145       end
    146 
    147       def empty?    ; true  end   ## always true
    148       def present?  ; false end   ## always false
    149       def clear     ; self  end   ## always self
    150 
    151       self
    152     end.init # CharCode::Empty
    153 
    154     private
    155 
    156     ## Tries to make external character right.
    157     def enc_char(c)
    158       begin
    159         character = c.chr($encoding)
    160       rescue RangeError, ArgumentError
    161         begin
    162           character = [c].pack('U')
    163         rescue RangeError
    164           begin
    165             character = c.chr
    166           rescue
    167             begin
    168               character = [c].pack('C')
    169             rescue
    170               character = ""
    171               @status = Ncurses::ERR
    172             end
    173           end
    174         end
    175         character.fix_encoding!
    176       end
    177     end
    178   end # class CharCode
    179 
    180   def rows
    181     lame, lamer = [], []
    182     stdscr.getmaxyx lame, lamer
    183     lame.first
    184   end
    185 
    186   def cols
    187     lame, lamer = [], []
    188     stdscr.getmaxyx lame, lamer
    189     lamer.first
    190   end
    191 
    192   def curx
    193     lame, lamer = [], []
    194     stdscr.getyx lame, lamer
    195     lamer.first
    196   end
    197 
    198   ## Create replacement wrapper for form_driver_w (), which is not (yet) a standard
    199   ## function in ncurses. Some systems (Mac OS X) does not have a working
    200   ## form_driver that accepts wide chars. We are just falling back to form_driver, expect problems.
    201   def prepare_form_driver
    202     if not defined? Form.form_driver_w
    203       warn "Your Ncursesw does not have a form_driver_w function (wide char aware), " \
    204            "non-ASCII chars may not work on your system."
    205       Form.module_eval <<-FRM_DRV, __FILE__, __LINE__ + 1
    206         def form_driver_w form, status, c
    207           form_driver form, c
    208         end
    209         module_function :form_driver_w
    210         module DriverHelpers
    211           def form_driver c
    212             if !c.dumb? && c.printable?
    213               c.each_byte do |code|
    214                 Ncurses::Form.form_driver @form, code
    215               end
    216             else
    217               Ncurses::Form.form_driver @form, c.code
    218             end
    219           end
    220         end
    221       FRM_DRV
    222     end # if not defined? Form.form_driver_w
    223     if not defined? Ncurses.get_wch
    224       warn "Your Ncursesw does not have a get_wch function (wide char aware), " \
    225            "non-ASCII chars may not work on your system."
    226       Ncurses.module_eval <<-GET_WCH, __FILE__, __LINE__ + 1
    227         def get_wch
    228           c = getch
    229           c == Ncurses::ERR ? [c, 0] : [Ncurses::OK, c]
    230         end
    231         module_function :get_wch
    232       GET_WCH
    233       CharCode.dumb!
    234     end # if not defined? Ncurses.get_wch
    235   end
    236 
    237   def mutex; @mutex ||= Mutex.new; end
    238   def sync &b; mutex.synchronize(&b); end
    239 
    240   module_function :rows, :cols, :curx, :mutex, :sync, :prepare_form_driver
    241 
    242   remove_const :KEY_ENTER
    243   remove_const :KEY_CANCEL
    244 
    245   KEY_ENTER = 10
    246   KEY_CANCEL = 7 # ctrl-g
    247   KEY_TAB = 9
    248 
    249   module Form
    250     ## This module contains helpers that ease
    251     ## using form_driver_ methods when @form is present.
    252     module DriverHelpers
    253       private
    254 
    255       ## Ncurses::Form.form_driver_w wrapper for keycodes and control characters.
    256       def form_driver_key c
    257         form_driver CharCode.keycode(c)
    258       end
    259 
    260       ## Ncurses::Form.form_driver_w wrapper for printable characters.
    261       def form_driver_char c
    262         form_driver CharCode.character(c)
    263         #c.is_a?(Integer) ? c : c.ord
    264       end
    265 
    266       ## Ncurses::Form.form_driver_w wrapper for charcodes.
    267       def form_driver c
    268         Ncurses::Form.form_driver_w @form, c.status, c.code
    269       end
    270     end # module DriverHelpers
    271   end # module Form
    272 
    273 end # module Ncurses
    274 end # if defined? Ncurses