Class: TTY::Reader Private

Inherits:
Object
  • Object
show all
Includes:
Wisper::Publisher
Defined in:
lib/tty/reader.rb,
lib/tty/reader/line.rb,
lib/tty/reader/mode.rb,
lib/tty/reader/codes.rb,
lib/tty/reader/console.rb,
lib/tty/reader/history.rb,
lib/tty/reader/version.rb,
lib/tty/reader/win_api.rb,
lib/tty/reader/key_event.rb,
lib/tty/reader/win_console.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

A class responsible for reading character input from STDIN

Used internally to provide key and line reading functionality

Defined Under Namespace

Modules: Codes, WinAPI Classes: Console, History, Key, KeyEvent, Line, Mode, WinConsole

Constant Summary collapse

InputInterrupt =

Raised when the user hits the interrupt key(Control-C)

Class.new(StandardError)
CARRIAGE_RETURN =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Key codes

13
NEWLINE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

10
BACKSPACE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

127
DELETE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

8
VERSION =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

"0.1.0"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input = $stdin, output = $stdout, options = {}) ⇒ Reader

Initialize a Reader

Parameters:

  • input (IO) (defaults to: $stdin)

    the input stream

  • output (IO) (defaults to: $stdout)

    the output stream

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :interrupt (Symbol)

    handling of Ctrl+C key out of :signal, :exit, :noop

  • :track_history (Boolean)

    disable line history tracking, true by default



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/tty/reader.rb', line 57

def initialize(input = $stdin, output = $stdout, options = {})
  @input     = input
  @output    = output
  @interrupt = options.fetch(:interrupt) { :error }
  @env       = options.fetch(:env) { ENV }
  @track_history = options.fetch(:track_history) { true }
  @console   = select_console(input)
  @history   = History.new do |h|
    h.duplicates = false
    h.exclude = proc { |line| line.strip == '' }
  end
  @stop = false # gathering input

  subscribe(self)
end

Instance Attribute Details

#consoleObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



36
37
38
# File 'lib/tty/reader.rb', line 36

def console
  @console
end

#envObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



31
32
33
# File 'lib/tty/reader.rb', line 31

def env
  @env
end

#inputObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



27
28
29
# File 'lib/tty/reader.rb', line 27

def input
  @input
end

#outputObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



29
30
31
# File 'lib/tty/reader.rb', line 29

def output
  @output
end

#track_historyObject (readonly) Also known as: track_history?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



33
34
35
# File 'lib/tty/reader.rb', line 33

def track_history
  @track_history
end

Instance Method Details

#add_to_history(line) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



274
275
276
# File 'lib/tty/reader.rb', line 274

def add_to_history(line)
  @history.push(line)
end

#get_codes(options = {}, codes = []) ⇒ Array[Integer]

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Get input code points

Parameters:

  • options (Hash[Symbol]) (defaults to: {})
  • codes (Array[Integer]) (defaults to: [])

Returns:

  • (Array[Integer])


133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/tty/reader.rb', line 133

def get_codes(options = {}, codes = [])
  opts = { echo: true, raw: false }.merge(options)
  char = console.get_char(opts)
  handle_interrupt if char == console.keys[:ctrl_c]
  return if char.nil?
  codes << char.ord

  condition = proc { |escape|
    (codes - escape).empty? ||
    (escape - codes).empty? &&
    !(64..126).include?(codes.last)
  }

  while console.escape_codes.any?(&condition)
    get_codes(options, codes)
  end
  codes
end

#history_nextObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



282
283
284
285
# File 'lib/tty/reader.rb', line 282

def history_next
  @history.next
  @history.get
end

#history_next?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


278
279
280
# File 'lib/tty/reader.rb', line 278

def history_next?
  @history.next?
end

#history_previousObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



291
292
293
294
295
# File 'lib/tty/reader.rb', line 291

def history_previous
  line = @history.get
  @history.previous
  line
end

#history_previous?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


287
288
289
# File 'lib/tty/reader.rb', line 287

def history_previous?
  @history.previous?
end

#inspectString

Inspect class name and public attributes

Returns:

  • (String)


301
302
303
# File 'lib/tty/reader.rb', line 301

def inspect
  "#<#{self.class}: @input=#{input}, @output=#{output}>"
end

#keyctrl_dObject Also known as: keyctrl_z

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Capture Ctrl+d and Ctrl+z key events



269
270
271
# File 'lib/tty/reader.rb', line 269

def keyctrl_d(*)
  @stop = true
end

#read_keypress(options = {}) ⇒ String Also known as: read_char

Read a keypress including invisible multibyte codes and return a character as a string. Nothing is echoed to the console. This call will block for a single keypress, but will not wait for Enter to be pressed.

Parameters:

  • options (Hash[Symbol]) (defaults to: {})

Options Hash (options):

  • echo (Boolean)

    whether to echo chars back or not, defaults to false

  • raw (Boolean)

    whenther raw mode enabled, defaults to true

Returns:

  • (String)


115
116
117
118
119
120
121
122
# File 'lib/tty/reader.rb', line 115

def read_keypress(options = {})
  opts  = { echo: false, raw: true }.merge(options)
  codes = unbufferred { get_codes(opts) }
  char  = codes ? codes.pack('U*') : nil

  trigger_key_event(char) if char
  char
end

#read_line(*args) ⇒ String

Get a single line from STDIN. Each key pressed is echoed back to the shell. The input terminates when enter or return key is pressed.

Parameters:

  • prompt (String)

    the prompt to display before input

  • echo (Boolean)

    if true echo back characters, output nothing otherwise

Returns:

  • (String)


165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/tty/reader.rb', line 165

def read_line(*args)
  options = args.last.respond_to?(:to_hash) ? args.pop : {}
  prompt = args.empty? ? '' : args.pop
  opts = { echo: true, raw: true }.merge(options)
  line = Line.new('')
  ctrls = console.keys.keys.grep(/ctrl/)
  clear_line = "\e[2K\e[1G"

  while (codes = unbufferred { get_codes(opts) }) && (code = codes[0])
    char = codes.pack('U*')
    trigger_key_event(char)

    if console.keys[:backspace] == char || BACKSPACE == code
      next if line.start?
      line.left
      line.delete
    elsif console.keys[:delete] == char || DELETE == code
      line.delete
    elsif [console.keys[:ctrl_d],
            console.keys[:ctrl_z]].include?(char)
      break
    elsif ctrls.include?(console.keys.key(char))
      # skip
    elsif console.keys[:up] == char
      next unless history_previous?
      line.replace(history_previous)
    elsif console.keys[:down] == char
      line.replace(history_next? ? history_next : '')
    elsif console.keys[:left] == char
      line.left
    elsif console.keys[:right] == char
      line.right
    else
      if opts[:raw] && code == CARRIAGE_RETURN
        char = "\n"
        line.move_to_end
      end
      line.insert(char)
    end

    if opts[:raw] && opts[:echo]
      output.print(clear_line)
      output.print(prompt + line.to_s)
      if char == "\n"
        line.move_to_start
      elsif !line.end?
        output.print("\e[#{line.size - line.cursor}D")
      end
    end

    break if (code == CARRIAGE_RETURN || code == NEWLINE)

    if (console.keys[:backspace] == char || BACKSPACE == code) && opts[:echo]
      if opts[:raw]
        output.print("\e[1X") unless line.start?
      else
        output.print(?\s + (line.start? ? '' :  ?\b))
      end
    end
  end
  add_to_history(line.to_s.rstrip) if track_history?
  line.to_s
end

#read_multiline(prompt = '') {|String| ... } ⇒ Array[String] Also known as: read_lines

Read multiple lines and return them in an array. Skip empty lines in the returned lines array. The input gathering is terminated by Ctrl+d or Ctrl+z.

Parameters:

  • prompt (String) (defaults to: '')

    the prompt displayed before the input

Yields:

  • (String)

    line

Returns:

  • (Array[String])


241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/tty/reader.rb', line 241

def read_multiline(prompt = '')
  @stop = false
  lines = []
  loop do
    line = read_line(prompt)
    break if !line || line == ''
    next  if line !~ /\S/ && !@stop
    if block_given?
      yield(line) unless line.to_s.empty?
    else
      lines << line unless line.to_s.empty?
    end
    break if @stop
  end
  lines
end

#select_console(input) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Select appropriate console



76
77
78
79
80
81
82
# File 'lib/tty/reader.rb', line 76

def select_console(input)
  if windows? && !env['TTY_TEST']
    WinConsole.new(input)
  else
    Console.new(input)
  end
end

#trigger(event, *args) ⇒ Object

Expose event broadcasting



262
263
264
# File 'lib/tty/reader.rb', line 262

def trigger(event, *args)
  publish(event, *args)
end

#unbufferred(&block) ⇒ Object

Get input in unbuffered mode.

Examples:

unbufferred do
  ...
end


92
93
94
95
96
97
98
99
# File 'lib/tty/reader.rb', line 92

def unbufferred(&block)
  bufferring = output.sync
  # Immediately flush output
  output.sync = true
  block[] if block_given?
ensure
  output.sync = bufferring
end