tty-reader 0.4.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,17 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
- require_relative 'keys'
5
- require_relative 'mode'
3
+ require "io/wait"
4
+
5
+ require_relative "keys"
6
+ require_relative "mode"
6
7
 
7
8
  module TTY
8
9
  class Reader
9
10
  class Console
10
- ESC = "\e".freeze
11
- CSI = "\e[".freeze
11
+ ESC = "\e"
12
+ CSI = "\e["
13
+
14
+ TIMEOUT = 0.1
12
15
 
13
16
  # Key codes
14
17
  #
@@ -33,16 +36,25 @@ module TTY
33
36
 
34
37
  # Get a character from console with echo
35
38
  #
36
- # @param [Hash[Symbol]] options
37
- # @option options [Symbol] :echo
38
- # the echo toggle
39
+ # @param [Boolean] echo
40
+ # whether to echo input back or not, defaults to true
41
+ # @param [Boolean] raw
42
+ # whether to use raw mode or not, defaults to false
43
+ # @param [Boolean] nonblock
44
+ # whether to wait for input or not, defaults to false
39
45
  #
40
46
  # @return [String]
41
47
  #
42
48
  # @api private
43
- def get_char(options)
44
- mode.raw(options[:raw]) do
45
- mode.echo(options[:echo]) { input.getc }
49
+ def get_char(echo: true, raw: false, nonblock: false)
50
+ mode.raw(raw) do
51
+ mode.echo(echo) do
52
+ if nonblock
53
+ input.wait_readable(TIMEOUT) ? input.getc : nil
54
+ else
55
+ input.getc
56
+ end
57
+ end
46
58
  end
47
59
  end
48
60
 
@@ -1,7 +1,6 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
- require 'forwardable'
3
+ require "forwardable"
5
4
 
6
5
  module TTY
7
6
  class Reader
@@ -16,49 +15,72 @@ module TTY
16
15
  # Default maximum size
17
16
  DEFAULT_SIZE = 32 << 4
18
17
 
18
+ # Default exclude
19
+ DEFAULT_EXCLUDE = ->(line) { line.chomp == "" }
20
+
19
21
  def_delegators :@history, :size, :length, :to_s, :inspect
20
22
 
21
23
  # Set and retrieve the maximum size of the buffer
22
24
  attr_accessor :max_size
23
25
 
26
+ # The current index
27
+ #
28
+ # @return [Integer]
29
+ #
30
+ # @api private
24
31
  attr_reader :index
25
32
 
33
+ # Decides whether or not to allow cycling through stored lines.
34
+ #
35
+ # @return [Boolean]
36
+ #
37
+ # @api public
26
38
  attr_accessor :cycle
27
39
 
40
+ # Decides wether or not duplicate lines are stored.
41
+ #
42
+ # @return [Boolean]
43
+ #
44
+ # @api public
28
45
  attr_accessor :duplicates
29
46
 
47
+ # Dictates which lines are stored.
48
+ #
49
+ # @return [Proc]
50
+ #
51
+ # @public
30
52
  attr_accessor :exclude
31
53
 
32
54
  # Create a History buffer
33
55
  #
34
- # param [Integer] max_size
56
+ # @param [Integer] max_size
35
57
  # the maximum size for history buffer
36
- #
37
- # param [Hash[Symbol]] options
38
- # @option options [Boolean] :cycle
58
+ # @param [Boolean] cycle
39
59
  # whether or not the history should cycle, false by default
40
- # @option options [Boolean] :duplicates
60
+ # @param [Boolean] duplicates
41
61
  # whether or not to store duplicates, true by default
42
- # @option options [Boolean] :exclude
62
+ # @param [Boolean] exclude
43
63
  # a Proc to exclude items from storing in history
44
64
  #
45
65
  # @api public
46
- def initialize(max_size = DEFAULT_SIZE, **options)
66
+ def initialize(max_size = DEFAULT_SIZE, duplicates: true, cycle: false,
67
+ exclude: DEFAULT_EXCLUDE)
47
68
  @max_size = max_size
48
69
  @index = nil
49
70
  @history = []
50
- @duplicates = options.fetch(:duplicates) { true }
51
- @exclude = options.fetch(:exclude) { proc {} }
52
- @cycle = options.fetch(:cycle) { false }
71
+ @duplicates = duplicates
72
+ @exclude = exclude
73
+ @cycle = cycle
74
+
53
75
  yield self if block_given?
54
76
  end
55
77
 
56
78
  # Iterates over history lines
57
79
  #
58
80
  # @api public
59
- def each
81
+ def each(&block)
60
82
  if block_given?
61
- @history.each { |line| yield line }
83
+ @history.each(&block)
62
84
  else
63
85
  @history.to_enum
64
86
  end
@@ -86,6 +108,7 @@ module TTY
86
108
  # @api public
87
109
  def next
88
110
  return if size.zero?
111
+
89
112
  if @index == size - 1
90
113
  @index = 0 if @cycle
91
114
  else
@@ -100,6 +123,7 @@ module TTY
100
123
  # Move the pointer to the previous line in the history
101
124
  def previous
102
125
  return if size.zero?
126
+
103
127
  if @index.zero?
104
128
  @index = size - 1 if @cycle
105
129
  else
@@ -122,7 +146,7 @@ module TTY
122
146
  end
123
147
  line = @history[index]
124
148
  if line.nil?
125
- raise IndexError, 'invalid index'
149
+ raise IndexError, "invalid index"
126
150
  end
127
151
  line.dup
128
152
  end
@@ -1,7 +1,6 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
- require_relative 'keys'
3
+ require_relative "keys"
5
4
 
6
5
  module TTY
7
6
  class Reader
@@ -17,7 +16,7 @@ module TTY
17
16
  # Represents key event emitted during keyboard press
18
17
  #
19
18
  # @api public
20
- class KeyEvent < Struct.new(:value, :key)
19
+ class KeyEvent < Struct.new(:key, :value, :line)
21
20
  # Create key event from read input codes
22
21
  #
23
22
  # @param [Hash[Symbol]] keys
@@ -27,7 +26,7 @@ module TTY
27
26
  # @return [KeyEvent]
28
27
  #
29
28
  # @api public
30
- def self.from(keys, char)
29
+ def self.from(keys, char, line = "")
31
30
  key = Key.new
32
31
  key.name = (name = keys[char]) ? name : :ignore
33
32
 
@@ -43,7 +42,7 @@ module TTY
43
42
  key.ctrl = true
44
43
  end
45
44
 
46
- new(char, key)
45
+ new(key, char, line)
47
46
  end
48
47
 
49
48
  # Check if key event can be triggered
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module TTY
@@ -14,12 +13,12 @@ module TTY
14
13
  ?\C-e => :ctrl_e,
15
14
  ?\C-f => :ctrl_f,
16
15
  ?\C-g => :ctrl_g,
17
- ?\C-h => :ctrl_h, # identical to '\b'
18
- ?\C-i => :ctrl_i, # identical to '\t'
19
- ?\C-j => :ctrl_j, # identical to '\n'
16
+ ?\C-h => :ctrl_h, # identical to "\b"
17
+ ?\C-i => :ctrl_i, # identical to "\t"
18
+ ?\C-j => :ctrl_j, # identical to "\n"
20
19
  ?\C-k => :ctrl_k,
21
20
  ?\C-l => :ctrl_l,
22
- ?\C-m => :ctrl_m, # identical to '\r'
21
+ ?\C-m => :ctrl_m, # identical to "\r"
23
22
  ?\C-n => :ctrl_n,
24
23
  ?\C-o => :ctrl_o,
25
24
  ?\C-p => :ctrl_p,
@@ -134,18 +133,18 @@ module TTY
134
133
  "\e" => :escape,
135
134
  " " => :space,
136
135
  "\b" => :backspace,
137
- [224, 71].pack('U*') => :home,
138
- [224, 79].pack('U*') => :end,
139
- [224, 82].pack('U*') => :insert,
140
- [224, 83].pack('U*') => :delete,
141
- [224, 73].pack('U*') => :page_up,
142
- [224, 81].pack('U*') => :page_down,
136
+ [224, 71].pack("U*") => :home,
137
+ [224, 79].pack("U*") => :end,
138
+ [224, 82].pack("U*") => :insert,
139
+ [224, 83].pack("U*") => :delete,
140
+ [224, 73].pack("U*") => :page_up,
141
+ [224, 81].pack("U*") => :page_down,
143
142
 
144
- [224, 72].pack('U*') => :up,
145
- [224, 80].pack('U*') => :down,
146
- [224, 77].pack('U*') => :right,
147
- [224, 75].pack('U*') => :left,
148
- [224, 83].pack('U*') => :clear,
143
+ [224, 72].pack("U*") => :up,
144
+ [224, 80].pack("U*") => :down,
145
+ [224, 77].pack("U*") => :right,
146
+ [224, 75].pack("U*") => :left,
147
+ [224, 83].pack("U*") => :clear,
149
148
 
150
149
  "\x00;" => :f1,
151
150
  "\x00<" => :f2,
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
3
+ require "forwardable"
4
4
 
5
5
  module TTY
6
6
  class Reader
@@ -15,7 +15,7 @@ module TTY
15
15
  #
16
16
  # @api public
17
17
  def self.sanitize(text)
18
- text.dup.gsub(ANSI_MATCHER, '')
18
+ text.dup.gsub(ANSI_MATCHER, "")
19
19
  end
20
20
 
21
21
  # The editable text
@@ -30,13 +30,19 @@ module TTY
30
30
  # @api public
31
31
  attr_reader :mode
32
32
 
33
+ # The prompt displayed before input
34
+ # @api public
33
35
  attr_reader :prompt
34
36
 
35
- def initialize(prompt, text = '')
37
+ # Create a Line instance
38
+ #
39
+ # @api private
40
+ def initialize(text = "", prompt: "")
36
41
  @prompt = prompt.dup
37
42
  @text = text.dup
38
43
  @cursor = [0, @text.length].max
39
44
  @mode = :edit
45
+
40
46
  yield self if block_given?
41
47
  end
42
48
 
@@ -128,14 +134,18 @@ module TTY
128
134
  # @param [Integer] i
129
135
  # the index to insert at
130
136
  #
137
+ # @param [String] chars
138
+ # the characters to insert
139
+ #
131
140
  # @example
132
- # text = 'aaa'
133
- # line[5]= 'b'
134
- # => 'aaa b'
141
+ # text = "aaa"
142
+ # line[5]= "b"
143
+ # => "aaa b"
135
144
  #
136
145
  # @api public
137
146
  def []=(i, chars)
138
147
  edit_mode
148
+
139
149
  if i.is_a?(Range)
140
150
  @text[i] = chars
141
151
  @cursor += chars.length
@@ -143,12 +153,9 @@ module TTY
143
153
  end
144
154
 
145
155
  if i <= 0
146
- before_text = ''
156
+ before_text = ""
147
157
  after_text = @text.dup
148
- elsif i == @text.length - 1
149
- before_text = @text.dup
150
- after_text = ''
151
- elsif i > @text.length - 1
158
+ elsif i > @text.length - 1 # insert outside of line input
152
159
  before_text = @text.dup
153
160
  after_text = ?\s * (i - @text.length)
154
161
  @cursor += after_text.length
@@ -202,16 +209,19 @@ module TTY
202
209
  # Remove char from the line at current position
203
210
  #
204
211
  # @api public
205
- def delete
206
- @text.slice!(@cursor, 1)
212
+ def delete(n = 1)
213
+ @text.slice!(@cursor, n)
207
214
  end
208
215
 
209
216
  # Remove char from the line in front of the cursor
210
217
  #
218
+ # @param [Integer] n
219
+ # the number of chars to remove
220
+ #
211
221
  # @api public
212
- def remove
213
- left
214
- @text.slice!(@cursor, 1)
222
+ def remove(n = 1)
223
+ left(n)
224
+ @text.slice!(@cursor, n)
215
225
  end
216
226
 
217
227
  # Full line with prompt as string
@@ -226,7 +236,10 @@ module TTY
226
236
  #
227
237
  # @api public
228
238
  def prompt_size
229
- self.class.sanitize(@prompt).size
239
+ p = self.class.sanitize(@prompt).split(/\r?\n/)
240
+ # return the length of each line + screen width for every line past the first
241
+ # which accounts for multi-line prompts
242
+ p.join.length + ((p.length - 1) * TTY::Screen.width )
230
243
  end
231
244
 
232
245
  # Text size
@@ -1,7 +1,6 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
- require 'io/console'
3
+ require "io/console"
5
4
 
6
5
  module TTY
7
6
  class Reader
@@ -1,7 +1,7 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module TTY
4
4
  class Reader
5
- VERSION = '0.4.0'.freeze
5
+ VERSION = "0.9.0"
6
6
  end # Reader
7
7
  end # TTY
@@ -1,16 +1,13 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
- require 'fiddle'
3
+ require "fiddle"
5
4
 
6
5
  module TTY
7
6
  class Reader
8
7
  module WinAPI
9
8
  include Fiddle
10
9
 
11
- Handle = RUBY_VERSION >= "2.0.0" ? Fiddle::Handle : DL::Handle
12
-
13
- CRT_HANDLE = Handle.new("msvcrt") rescue Handle.new("crtdll")
10
+ CRT_HANDLE = Fiddle::Handle.new("msvcrt") rescue Fiddle::Handle.new("crtdll")
14
11
 
15
12
  # Get a character from the console without echo.
16
13
  #
@@ -1,14 +1,13 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
- require_relative 'keys'
3
+ require_relative "keys"
5
4
 
6
5
  module TTY
7
6
  class Reader
8
7
  class WinConsole
9
- ESC = "\e".freeze
10
- NUL_HEX = "\x00".freeze
11
- EXT_HEX = "\xE0".freeze
8
+ ESC = "\e"
9
+ NUL_HEX = "\x00"
10
+ EXT_HEX = "\xE0"
12
11
 
13
12
  # Key codes
14
13
  #
@@ -25,7 +24,7 @@ module TTY
25
24
  attr_reader :escape_codes
26
25
 
27
26
  def initialize(input)
28
- require_relative 'win_api'
27
+ require_relative "win_api"
29
28
  @input = input
30
29
  @keys = Keys.ctrl_keys.merge(Keys.win_keys)
31
30
  @escape_codes = [[NUL_HEX.ord], [ESC.ord], EXT_HEX.bytes.to_a]
@@ -33,26 +32,27 @@ module TTY
33
32
 
34
33
  # Get a character from console blocking for input
35
34
  #
36
- # @param [Hash[Symbol]] options
37
- # @option options [Symbol] :echo
38
- # the echo mode toggle
39
- # @option options [Symbol] :raw
40
- # the raw mode toggle
35
+ # @param [Boolean] echo
36
+ # whether to echo input back or not, defaults to true
37
+ # @param [Boolean] raw
38
+ # whether to use raw mode or not, defaults to false
39
+ # @param [Boolean] nonblock
40
+ # whether to wait for input or not, defaults to false
41
41
  #
42
42
  # @return [String]
43
43
  #
44
44
  # @api private
45
- def get_char(options)
46
- if options[:raw] && options[:echo]
47
- if options[:nonblock]
45
+ def get_char(echo: true, raw: false, nonblock: false)
46
+ if raw && echo
47
+ if nonblock
48
48
  get_char_echo_non_blocking
49
49
  else
50
50
  get_char_echo_blocking
51
51
  end
52
- elsif options[:raw] && !options[:echo]
53
- options[:nonblock] ? get_char_non_blocking : get_char_blocking
54
- elsif !options[:raw] && !options[:echo]
55
- options[:nonblock] ? get_char_non_blocking : get_char_blocking
52
+ elsif raw && !echo
53
+ nonblock ? get_char_non_blocking : get_char_blocking
54
+ elsif !raw && !echo
55
+ nonblock ? get_char_non_blocking : get_char_blocking
56
56
  else
57
57
  @input.getc
58
58
  end