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
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
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
# File lib/reterm/init.rb, line 118 def cursor? !(@@cursor_disabled ||= false) end
# 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 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
# 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
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
Flushes all input queues
# File lib/reterm/util.rb, line 118 def flush_input Ncurses.flushinp end
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
# File lib/reterm/loader.rb, line 123 def load_reterm(str) Loader.new(str).window end
Set the global callback to be invoked on resize
# File lib/reterm/resize.rb, line 6 def on_resize(&bl) @on_resize = bl end
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
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
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 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
Use to halt all operation and cleanup.
# File lib/reterm/init.rb, line 213 def shutdown! @@shutdown = true end
Boolean indicating if app in being halted
# File lib/reterm/init.rb, line 218 def shutdown? !!@@shutdown end
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
Boonean indicating if component syncronization is enabled
# File lib/reterm/init.rb, line 195 def sync_enabled? defined?(@@sync_activated) && !!@@sync_activated end
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
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