module RETerm

The Ruby Enhanced Terminal interactive framework facilitating dynmaic/feature rich terminal applications.

Constants

ANSI_COLORS
NC

XXX Ncurses is exposed so that users may employ any constants if desired. This should not be needed though and is discouraged to maintain portability

RESIZE_TIME

Seconds between terminal size syncs

SYNC_TIMEOUT
VERSION

Public Instance Methods

activate_sync!() click to toggle source

Enables the input timeout and component syncronization.

Used internally by components that need to periodically be updated outside of user input.

# File lib/reterm/init.rb, line 186
def activate_sync!
  @@sync_activated = true
  Window.all.each { |w|
    w.win.timeout(SYNC_TIMEOUT)
  }
end
cdk_enabled?() click to toggle source

Return boolean indicating if the CDK subsystem is enabled. CDK is a library used by various components providing many various ready to use ncurses widgets.

# File lib/reterm/init.rb, line 176
def cdk_enabled?
  !!@cdk_enabled
end
cursor?() click to toggle source

Cursor

# File lib/reterm/init.rb, line 118
def cursor?
  !(@@cursor_disabled ||= false)
end
disable_cursor!() { || ... } click to toggle source
# File lib/reterm/init.rb, line 122
def disable_cursor!
  # store cursor state
  o = cursor?

  # set cursor state
  @@cursor_disabled = true
  Ncurses::curs_set(0)

  # invoke block if given
  return unless block_given?
  yield

  # restore cursor state after block
  enable_cursor! if o
end
enable_cdk!() click to toggle source

Enable the CDK subsystem. Used by CDKbased components

@see cdk_enabled?

# File lib/reterm/init.rb, line 160
def enable_cdk!
  return if @cdk_enabled

  @cdk_enabled = true
  require 'cdk'

  # XXX defines standard color pairs, but we use our own
  # for standarization across components, but we need
  # to set cdk components to the correct color scheme
  # manually
  # CDK::Draw.initCDKColor
end
enable_cursor!() { || ... } click to toggle source
# File lib/reterm/init.rb, line 138
def enable_cursor!
  # store cursor state
  o = cursor?

  # set cursor state
  @@cursor_disabled = false
  Ncurses::curs_set(1)

  # invoke block if given
  return unless block_given?
  yield

  # restore cursor state after block
  disable_cursor! unless o
end
file_append(f, t) click to toggle source

Helper appending string to debug file

# File lib/reterm/util.rb, line 113
def file_append(f, t)
  File.open(f, "a") { |f| f.write t }
end
flush_input() click to toggle source

Flushes all input queues

# File lib/reterm/util.rb, line 118
def flush_input
  Ncurses.flushinp
end
init_reterm(opts={}, &bl) click to toggle source

Initializes the RETerm subsystem before invoking block. After block is finished regardless of execution state (return, exception, etc) this will cleanup the terminal environment before returning control to the TTY.

This method is the first method the user should invoke after requiring the reterm library.

@example

init_reterm {
  win = Window.new :rows => 20, :cols => 40
  # ... more UI logic
}
# File lib/reterm/init.rb, line 23
def init_reterm(opts={}, &bl)
  @@shutdown = false
  @@reterm_opts = opts

  err = nil

  min = opts[:min_size]

  begin
    # XXX: must load terminal info before changing terminal settings
    #      (else causes crash... not sure why)
    # But we also check min dimensions here
    Terminal.load(min)

    # shorten up the esc delay time
    ENV["ESCDELAY"] = 100.to_s

    stdscr = Ncurses::initscr
    initialized = true
    Ncurses::start_color
    Ncurses::noecho
    Ncurses::cbreak
    disable_cursor! unless !!opts[:cursor]
    Ncurses::keypad(stdscr, true)

    no_mouse = opts[:nomouse] || (opts.key?(:mouse) && !opts[:mouse])
    Ncurses::mousemask(MouseInput::ALL_EVENTS, []) unless no_mouse

    track_resize if opts[:resize]

    bl.call

  rescue => e
    err = e

  ensure
    stop_track_resize
    Ncurses.curs_set(1) if !!initialized
    Window.top.each { |w| w.finalize! }
    CDK::SCREEN.endCDK if cdk_enabled?
    Ncurses.endwin if !!initialized
    #`reset -Q` # XXX only way to guarantee a full reset (see above)
  end

  if err
    puts "Unhandled error:"
    puts "  " + err.to_s
    puts err.backtrace
            .collect { |b|
              "    " + b
            }.join("\n")
  end
end
load_reterm(str) click to toggle source
# File lib/reterm/loader.rb, line 123
def load_reterm(str)
  Loader.new(str).window
end
on_resize(&bl) click to toggle source

Set the global callback to be invoked on resize

# File lib/reterm/resize.rb, line 6
def on_resize(&bl)
  @on_resize = bl
end
parse_ansi(str) click to toggle source

Converts specified ansi string to array of character data & corresponding control logic

# File lib/reterm/util.rb, line 37
def parse_ansi(str)
  require 'strscan'

  r = []
  f = []
  t = []

  a = nil
  s = StringScanner.new(str)

  while(!s.eos?)
    # end of formatting
    if s.scan(/(\e|\[)\[0m/)
      t << f.pop
      t.compact!
      if f.empty?
        r << [a, t]
        t = []
        a = nil
      end

    # basic formatter
    elsif s.scan(/\e\[(3[0-7]|90|1)m/)
      # FIXME need to register formatting for 'a'
      # up to this point (and reset 'a') (and below)
      f << ANSI_COLORS[s[1].to_i]

    # sgr
    # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
    elsif s.scan(/\e\[(([0-9]+;?)+)m/)
      sgr = s[1].split(";").collect { |s| s.to_i }
      f << if (30..37).include?(sgr[0])
             ANSI_COLORS[sgr[1]]

           elsif sgr[0] == 38
             if sgr[1] == 5
              if sgr[2] < 8
                ANSI_COLORS[sgr[2]]

              elsif sgr[2] < 16
                ANSI_COLORS[sgr[2]]

              elsif sgr[2] < 232
                # TODO verify:
                # https://stackoverflow.com/questions/12338015/converting-8-bit-color-into-rgb-value
                re = (sgr[2] >> 5) * 32
                gr = ((sgr[2] & 28) >> 2) * 32
                bl = (sgr[2] & 3) * 64
                [re, gr, bl]

              else # if srg[2] < 256
                # TODO
              end

             else # if sgr[1] == 2
               # TODO
             end

             # TODO other sgr commands
           end

    else
      a  = "" if a.nil?
      a += s.scan(/./m)

    end
  end


  # handle remaining / lingering data
  r << [a, (t + f).compact] unless f.empty?

  r
end
process_alive?(pid) click to toggle source

Helper returning boolean indicating if specified process is alive

# File lib/reterm/util.rb, line 3
def process_alive?(pid)
  begin
    Process.getpgid( pid )
    true
  rescue Errno::ESRCH
    false
  end
end
reterm_opts() click to toggle source

Return copy of options specified to init_reterm

# File lib/reterm/init.rb, line 104
def reterm_opts
  @@_reterm_opts ||= Hash[@@reterm_opts]
end
run_sync!() click to toggle source

Run the sync process, used internally

# File lib/reterm/init.rb, line 200
def run_sync!
  return unless sync_enabled?

  Window.all.each { |w|
    w.sync!
  }

  update_reterm
end
shutdown!() click to toggle source

Use to halt all operation and cleanup.

# File lib/reterm/init.rb, line 213
def shutdown!
  @@shutdown = true
end
shutdown?() click to toggle source

Boolean indicating if app in being halted

# File lib/reterm/init.rb, line 218
def shutdown?
  !!@@shutdown
end
stop_track_resize() click to toggle source

Terminate resize tracking thread (if running)

# File lib/reterm/resize.rb, line 34
def stop_track_resize
  @track_resize = false
  @resize_thread.join if @resize_thread
end
sync_enabled?() click to toggle source

Boonean indicating if component syncronization is enabled

# File lib/reterm/init.rb, line 195
def sync_enabled?
  defined?(@@sync_activated) && !!@@sync_activated
end
track_resize() click to toggle source

Internal helper to track size of terminal. This is a simple mechanism that launches a worker thread to periodically poll terminal size.

# File lib/reterm/resize.rb, line 13
def track_resize
  @track_resize = true
  d = Terminal.dimensions

  @resize_thread = Thread.new {
    while @track_resize
      Terminal.reset!
      t = Terminal.dimensions

      if t != d && !shutdown?
        Terminal.resize!
        @on_resize.call if !!@on_resize
      end

      d = Terminal.dimensions
      sleep(RESIZE_TIME)
    end
  }
end
update_reterm(force_refresh=false) click to toggle source

This method resyncs the visible terminal with the internal RETerm environment. It should be called any time the user needs to update the UI after making changes.

@example

init_reterm {
  win = Window.new
  win.border! # draw window border
  update_rti  # resync terminal ouput
  win.getch   # wait for input
}
# File lib/reterm/init.rb, line 89
def update_reterm(force_refresh=false)
  if force_refresh
    Window.top.each { |w|
      w.erase
      w.draw!
    }
  else
    Window.top.each { |w| w.noutrefresh }
  end

  #Ncurses::Panel.update_panels
  Ncurses.doupdate
end