Ansi_again

class Ansi
  require 'io/console' # for stdin.raw (cur_pos?)

  def initialize
    {
      cls:      proc {  '2J' }, #  clear entire screen
      cls_l:    proc {  '1J' }, #  clear screen left of cursor
      cls_r:    proc {  '0J' }, #  clear screen right of cursor
      cl_l:     proc {  '1K' }, #  clear line left of cursor
      cl_r:     proc {  '0K' }, #  clear line right of cursor
      cl_ln:    proc {  '2K' }, #  clear line
     
      #                             hide or show cursor
      cur:      proc {|show=true|  show ? '?25h' : '?25l' },
     
      cur_n:    proc {|step=1|     '%sA' % [step] }, # cursor North
      cur_s:    proc {|step=1|     '%sB' % [step] }, # cursor South
      cur_e:    proc {|step=1|     '%sC' % [step] }, # cursor East
      cur_w:    proc {|step=1|     '%sD' % [step] }, # cursor West
      
      cur_u:    proc {|step=1|    X.cur_n(step) }, # aliases  Up
      cur_d:    proc {|step=1|    X.cur_s(step) }, #          Down
      cur_r:    proc {|step=1|    X.cur_e(step) }, #          Right
      cur_l:    proc {|step=1|    X.cur_w(step) }, #          Left
      
      cur_col:  proc {|col=0|      '%sG' % [col] }, # move to column
      cur_at:   proc {|ln, col|    '%s;%sH' % [ln, col] },
      
      # print at
      pat:      proc {|ln, col, str|  '%s;%sH' % [ln, col]; print str },
      
      cur_pos?: proc { # get cursor position
        res = ''
        $stdin.raw {|stdin|
          $stdout << "\e[6n"; $stdout.flush
          while (c = stdin.getc) != 'R' do; res << c if c; end
        }
        m = res.match /(?<ln>\d+);(?<col>\d+)/
        Struct.new(:ln, :col).new(*m.to_a[1..].map(&:to_i))
      }, #             :bar, :line, :block or default (not given)
      
      # shape can be :block, :line or :bar. blink: true or FALSE
      cur_shape: proc {|shape, blink: true| # cursor shape and blink
        val = shape ?
          (%i(block line bar).index(shape) * 2 + (blink ? 1 : 2)) : 0
         '%s q' % [val]
      },
      
      cur_color: proc {|col| # cursor color
        print "\e]12;#{col}\a" if col[/^#[0-9A-F]{6}$/i]
      },
      
      cur_sto:  proc { system('tput sc') }, # store cursor position
      cur_rec:  proc { system('tput rc') }, # recall cursor position
      
      scr_sto:  proc { system('tput smcup') }, # store screen
      scr_rec:  proc { system('tput rmcup') }, # recall screen
     
      # columns and lines of terminal window
      scr_size: proc { [`tput cols`.to_i, `tput lines`.to_i] },

      # init and de-init (not required)
      init:     proc { X.scr_sto; X.cls; X.cur(false) },
      de_init:  proc {
        X.scr_rec; X.cur;
        X.cur_shape; X.cur_color('#FFFFFF')
      },

  }.each {|k,v|
        define_singleton_method(k) {|*args, **kw| v.call(*args, **kw) }
  }
  end
 
  # print ESC method
  def (s) = ( print "\e[#{s}" )
end

# =============================================================================
# Usage:

X = Ansi.new

X.init #                        save screen, clear screen, hide cursor

cols, lines = X.scr_size #      get screen size (columns, lines)

X.cur_at(10,10); print 'HEJ' #  position cursor
X.pat(12,10, 'HEJ!!') #         position cursor and print

pos = X.cur_pos? #              get cursor position
print ' (%s, %s)' %
  [pos.ln, pos.col]
sleep(2)


print ' red, blinking bar cursor'
X.cur(true) #                   show cursor
X.cur_color('#FF0000') #        set cursor color
X.cur_shape(:line, blink: false)# set cursor to steady bar
sleep(2)

X.de_init #                     restore screen, standard cursor visible