Class: Worlds::Terminal::Runner
- Inherits:
-
Object
- Object
- Worlds::Terminal::Runner
- Defined in:
- lib/worlds/terminal/runner.rb
Overview
A container for the input loop, which is needed because input is read in a non-blocking way, i.e. input is read while new output is displayed.
Constant Summary collapse
- PASTEL =
Pastel.new
- CURSOR =
'█'
- BACKSPACE =
"\x7F"
- CTRL_BACKSPACE =
or Cmd+Backspace on MacOS
"\x17"
- INTERRUPT =
Ctrl+C
"\x03"
Class Method Summary collapse
-
.io_loop ⇒ Object
Loops continuously, reading input and allowing Worlds to update and output.
Class Method Details
.io_loop ⇒ Object
Loops continuously, reading input and allowing Worlds to update and output.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/worlds/terminal/runner.rb', line 17 def self.io_loop loop do # We need our own input buffer here because the terminal input buffer is # disabled due to Helper::io_mode_raw! @input_buffer ||= '' new_input = Helper.read_nonblock if new_input return if new_input.include?(INTERRUPT) # Handle Enter. new_input_has_newline = new_input.include?("\n") || new_input.include?("\r") new_input = new_input.split(/[\n\r]/).first if new_input_has_newline # Add new input to buffer (or add nothing, if no new input). @input_buffer << (new_input || '') # Handle deletion: Ctrl + Backspace (line), or Backspace (character). # In either case, re-print the input buffer with spaces at the end # to cover over the deleted characters. if @input_buffer.include?(CTRL_BACKSPACE) print "#{CURSOR}#{' ' * @input_buffer.length}\r" @input_buffer = '' else backspace_count = @input_buffer.count(BACKSPACE) while @input_buffer.length > 0 && @input_buffer.include?(BACKSPACE) @input_buffer.sub!(/[^#{BACKSPACE}]#{BACKSPACE}/, '') @input_buffer = '' if @input_buffer.chars.uniq == [BACKSPACE] end print "#{@input_buffer}#{CURSOR}#{' ' * backspace_count}\r" end # Echo input. The \r is to make the line replaceable by new output, # while the input line will re-appear below the new output; in effect, # to allow output above the input line. print "#{@input_buffer}#{CURSOR}\r" end # Empty the input buffer if Enter was pressed. if new_input_has_newline input_line = @input_buffer.strip @input_buffer = '' end # If a line was just inputted, set up an input hash as either a number # selection (if a selection menu was just shown) or else a new command. if input_line if @select_for && input_line.match?(/\A\d+\z/) input = { type: :select, command: @select_for, selection: input_line.to_i } @select_for = nil else input = { type: :command, command: input_line } end end # Allow Worlds to loop, and print outputs if any. if outputs = Worlds::Updater.tick(input) outputs.each do |output| case output[:type] when :exit return when :select # selection menu Helper.puts PASTEL.white(output[:heading]) output[:options].each.with_index do |option, i| Helper.puts PASTEL.blue("#{i + 1}. ") + PASTEL.white(option) end @select_for = input_line else # informational Helper.puts PASTEL.send(output[:color], output[:content]) end end end # Reset input, to remain empty until next time Enter is pressed. input_line = nil && input = nil if input_line end end |