class Listpager::ClientTerminal

Attributes

list[R]
locked[R]
self_pipe[R]
tty[R]

Public Class Methods

new() click to toggle source
# File lib/listpager/client_terminal.rb, line 18
def initialize
  @tty = File.open('/dev/tty', 'r+')
  @self_pipe = IO.pipe
  @locked = false

  [@tty, *self_pipe].each do |io|
    io.sync = true
  end

  initialize_curses

  @list = List.new(Ncurses.stdscr)
  connect_list

  @buffer = ''
  @locked_buffer = []
end

Public Instance Methods

cmd!(*args, observe_lock: false) click to toggle source
# File lib/listpager/client_terminal.rb, line 92
def cmd!(*args, observe_lock: false)
  line!('%' + Shellwords.join(args.map(&:to_s)),
        observe_lock: observe_lock)
end
connect_list() click to toggle source
# File lib/listpager/client_terminal.rb, line 49
def connect_list
  cterm = self
  list.define_singleton_method :on_select_change do
    cterm.cmd!('selected-is', selected, selected_value, observe_lock: true)
  end

  list.define_singleton_method :on_key_press do |k|
    cterm.cmd!('key-pressed', cterm.key_name(k),
               selected, selected_value, observe_lock: true)
  end
end
consume_self_pipe(handles, fd) click to toggle source

We get a character from self_pipe here telling us the window has resized.

# File lib/listpager/client_terminal.rb, line 202
def consume_self_pipe(handles, fd)
  code = fd.read(1)
  case code
    when 'R'
      new_size = IO.console.winsize
      Ncurses.resizeterm(*new_size)
      Ncurses.stdscr.clear
      list.dirty!
      list.render
      Ncurses.refresh
  end
end
consume_stdin(handles, fd) click to toggle source
# File lib/listpager/client_terminal.rb, line 168
def consume_stdin(handles, fd)
  loop do
    begin
      @buffer << fd.read_nonblock(512)
    rescue EOFError
      handles.delete(fd)
      break
    rescue IO::WaitReadable
      break
    end
  end

  unless @buffer.empty?
    used = 0
    StringIO.new(@buffer).each_line do |line|
      if line[-1] == "\n"
        process_line(line.chomp)
        used += line.size
      else
        break
      end
    end
    @buffer = @buffer[used...-1]
  end
end
consume_tty(handles, fd) click to toggle source
# File lib/listpager/client_terminal.rb, line 194
def consume_tty(handles, fd)
  while (ch = Ncurses.getch) != -1
    list.key_input(ch)
  end
end
deinitialize() click to toggle source
# File lib/listpager/client_terminal.rb, line 75
def deinitialize
  Ncurses.echo
  Ncurses.nocbreak
  Ncurses.nl
  Ncurses.endwin
  @tty.close
end
dispatch_fd(handles, fd) click to toggle source
# File lib/listpager/client_terminal.rb, line 215
def dispatch_fd(handles, fd)
  case fd
    when $stdin
      consume_stdin(@handles, fd)
    when tty
      consume_tty(@handles, fd)
    when self_pipe[0]
      consume_self_pipe(@handles, fd)
  end
end
initialize_curses() click to toggle source
# File lib/listpager/client_terminal.rb, line 61
def initialize_curses
  screen = Ncurses.newterm(nil, @tty, @tty)
  Ncurses.set_term(screen)
  Ncurses.start_color
  Color.init
  Ncurses.use_default_colors
  Ncurses.cbreak
  Ncurses.stdscr.scrollok(false)
  Ncurses.stdscr.keypad(true)
  Ncurses.curs_set(0)
  Ncurses.noecho
  Ncurses.timeout(0)
end
key_name(v) click to toggle source
# File lib/listpager/client_terminal.rb, line 36
def key_name(v)
  @m ||= {
    27  => 'esc',
    10  => 'enter',
    260 => 'left',
    261 => 'right',
    127 => 'backspace',
    330 => 'delete',
    ' ' => 'space',
  }
  @m[v] || (v < 255 && v.chr.match(/[[:print:]]/) ? v.chr : "\##{v}")
end
line!(line, observe_lock: false) click to toggle source
# File lib/listpager/client_terminal.rb, line 83
def line!(line, observe_lock: false)
  if observe_lock && @locked
    @locked_buffer.push(line)
  else
    $stdout.puts line
    $stdout.flush
  end
end
process_command(argv) click to toggle source
# File lib/listpager/client_terminal.rb, line 97
def process_command(argv)
  cmd, *args = argv
  case cmd
    # TODO: This to be refactored into CommandProcessor
    when 'quit'
      raise Interrupt

    when 'clear'
      list.values = []
      list.selected = 0
      list.dirty!
      wake_up

    when 'append'
      list.values.push(args.fetch(0))
      list.dirty!

    when 'lock'
      @locked = true
      cmd! 'lock'

    when 'unlock'
      @locked = false
      cmd! 'unlock'
      @locked_buffer.each do |line|
        line!(line)
      end
      @locked_buffer = []

    when 'get-title'
      cmd! 'title-is', @list.title
    when 'set-title'
      @list.title = args[0]
      cmd! 'title-is', @list.title

    when 'get-selected'
      cmd! 'selected-is', list.selected, list.selected_value
    when 'set-selected'
      list.selected = args.fetch(0).to_i

    when 'get-item'
      cmd! 'item-is', args.fetch(0), list.values[args.fetch(0).to_i]
    when 'set-item'
      list.values[args.fetch(0).to_i] = args.fetch(1)
      list.dirty!
      cmd! 'item-is', args.fetch(0), list.values[args.fetch(0).to_i]
  end
end
process_events() click to toggle source
# File lib/listpager/client_terminal.rb, line 226
def process_events
  @handles ||= [$stdin, tty, self_pipe[0]]

  return if @handles.empty?

  res = IO.select(@handles)
  if res && (readers = res[0])
    readers.each do |fd|
      dispatch_fd(@handles, fd)
    end
  end
end
process_line(line) click to toggle source
# File lib/listpager/client_terminal.rb, line 155
def process_line(line)
  if line[0] == '%' && line[1] != '%'
    cmd = Shellwords.split(line[1..-1])
    process_command(cmd)
  else
    if line[0] == '%'
      line = line[1..-1]
    end
    list.values.push(line)
    list.dirty!
  end
end
run() click to toggle source
# File lib/listpager/client_terminal.rb, line 239
def run
  trap 'WINCH' do
    wake_up
  end

  begin
    loop do
      process_events
      if list.render
        Ncurses.redrawwin(list.window)
        Ncurses.refresh
      end
    end
  ensure
    deinitialize
  end
end
wake_up() click to toggle source
# File lib/listpager/client_terminal.rb, line 146
def wake_up
  self_pipe[1].tap do |fd|
    fd.nonblock do
      fd.write('R')
      fd.flush
    end
  end
end