Class: TTY::Prompt::Reader Private

Inherits:
Object
  • Object
show all
Includes:
Wisper::Publisher
Defined in:
lib/tty/prompt/reader.rb,
lib/tty/prompt/reader/line.rb,
lib/tty/prompt/reader/mode.rb,
lib/tty/prompt/reader/codes.rb,
lib/tty/prompt/reader/console.rb,
lib/tty/prompt/reader/history.rb,
lib/tty/prompt/reader/win_api.rb,
lib/tty/prompt/reader/key_event.rb,
lib/tty/prompt/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

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



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

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.



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

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.



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

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.



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

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.



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

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.



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

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.



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

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])


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

def get_codes(options = {}, codes = [])
  opts = { echo: true, raw: false }.merge(options)
  char = console.get_char(opts)
  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.



285
286
287
288
# File 'lib/tty/prompt/reader.rb', line 285

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)


281
282
283
# File 'lib/tty/prompt/reader.rb', line 281

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.



294
295
296
297
298
# File 'lib/tty/prompt/reader.rb', line 294

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)


290
291
292
# File 'lib/tty/prompt/reader.rb', line 290

def history_previous?
  @history.previous?
end

#inspectString

Inspect class name and public attributes

Returns:

  • (String)


304
305
306
# File 'lib/tty/prompt/reader.rb', line 304

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



272
273
274
# File 'lib/tty/prompt/reader.rb', line 272

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)


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

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
  handle_interrupt if char == console.keys[:ctrl_c]
  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)


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
228
229
230
# File 'lib/tty/prompt/reader.rb', line 166

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 console.keys[:ctrl_c] == char
      handle_interrupt
    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])


244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/tty/prompt/reader.rb', line 244

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



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

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



265
266
267
# File 'lib/tty/prompt/reader.rb', line 265

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

#unbufferred(&block) ⇒ Object

Get input in unbuffered mode.

Examples:

unbufferred do
  ...
end


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

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