reline 0.3.5 → 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3654e31f9c5d1aa879ae40dfb28b8de68f2e443fac87856f2387a22d61a5b4e7
4
- data.tar.gz: 0fdccab5b7cfd91274e3e4544dcca69a442176cc85e1eba382da3ffd02f9ac30
3
+ metadata.gz: 67ad796ede485308abba9038bb03672537e8cba1409ef61e921d165db277846d
4
+ data.tar.gz: 9692f5e08f6ed30722262d70c89e413b4f7d7651f98698d6253f720aea600125
5
5
  SHA512:
6
- metadata.gz: 9277033707e4a1531e8590ae6a76019c21c60da22f69ea333dcc5c3d3610499303ed7892308b6ae3b495237738344baac7a21c547a19ff77f53fecae29be9a14
7
- data.tar.gz: 5f3f225d7b1ee1f0ab067d8a897366a3d3b922dcedb74ae3ec980e387ad3b520d8ab808f70d89e109d06f7e5017ab6406d30e25ed2b6678b3fc8d6c4d5cf6930
6
+ metadata.gz: 4d69f27ac94aecc2f9628e824d4ded200cf0e0e1939b00d28db99b30f5b21baed78bba0edc7ab68105af327803c0e5f8f03853fb629079ae9017be6016bff0d7
7
+ data.tar.gz: 62af3574af7223c038e3b078708b116af92bcecf1ec9301e32f8653c008fdfea082253f9308aa99067f60aa9c98deecb5382552deee6847966f4e4ceb387f8f3
data/README.md CHANGED
@@ -55,6 +55,29 @@ end
55
55
 
56
56
  See also: [test/reline/yamatanooroti/multiline_repl](https://github.com/ruby/reline/blob/master/test/reline/yamatanooroti/multiline_repl)
57
57
 
58
+ ## Contributing
59
+
60
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/reline.
61
+
62
+ ### Run tests
63
+
64
+ > **Note**
65
+ > Please make sure you have `libvterm` installed for `yamatanooroti` tests (integration tests).
66
+
67
+ If you use Homebrew, you can install it by running `brew install libvterm`.
68
+
69
+ ```bash
70
+ WITH_VTERM=1 bundle install
71
+ WITH_VTERM=1 bundle exec rake test test_yamatanooroti
72
+ ```
73
+
74
+ ## Releasing
75
+
76
+ ```bash
77
+ rake release
78
+ gh release create vX.Y.Z --generate-notes
79
+ ```
80
+
58
81
  ## License
59
82
 
60
83
  The gem is available as open source under the terms of the [Ruby License](https://www.ruby-lang.org/en/about/license.txt).
data/lib/reline/ansi.rb CHANGED
@@ -14,10 +14,21 @@ class Reline::ANSI
14
14
  'kcud1' => :ed_next_history,
15
15
  'kcuf1' => :ed_next_char,
16
16
  'kcub1' => :ed_prev_char,
17
- 'cuu' => :ed_prev_history,
18
- 'cud' => :ed_next_history,
19
- 'cuf' => :ed_next_char,
20
- 'cub' => :ed_prev_char,
17
+ }
18
+
19
+ ANSI_CURSOR_KEY_BINDINGS = {
20
+ # Up
21
+ 'A' => [:ed_prev_history, {}],
22
+ # Down
23
+ 'B' => [:ed_next_history, {}],
24
+ # Right
25
+ 'C' => [:ed_next_char, { ctrl: :em_next_word, meta: :em_next_word }],
26
+ # Left
27
+ 'D' => [:ed_prev_char, { ctrl: :ed_prev_word, meta: :ed_prev_word }],
28
+ # End
29
+ 'F' => [:ed_move_to_end, {}],
30
+ # Home
31
+ 'H' => [:ed_move_to_beg, {}],
21
32
  }
22
33
 
23
34
  if Reline::Terminfo.enabled?
@@ -33,22 +44,12 @@ class Reline::ANSI
33
44
  end
34
45
 
35
46
  def self.set_default_key_bindings(config, allow_terminfo: true)
47
+ set_default_key_bindings_ansi_cursor(config)
36
48
  if allow_terminfo && Reline::Terminfo.enabled?
37
49
  set_default_key_bindings_terminfo(config)
38
50
  else
39
51
  set_default_key_bindings_comprehensive_list(config)
40
52
  end
41
- {
42
- # extended entries of terminfo
43
- [27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→, extended entry
44
- [27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←, extended entry
45
- [27, 91, 49, 59, 51, 67] => :em_next_word, # Meta+→, extended entry
46
- [27, 91, 49, 59, 51, 68] => :ed_prev_word, # Meta+←, extended entry
47
- }.each_pair do |key, func|
48
- config.add_default_key_binding_by_keymap(:emacs, key, func)
49
- config.add_default_key_binding_by_keymap(:vi_insert, key, func)
50
- config.add_default_key_binding_by_keymap(:vi_command, key, func)
51
- end
52
53
  {
53
54
  [27, 91, 90] => :completion_journey_up, # S-Tab
54
55
  }.each_pair do |key, func|
@@ -64,18 +65,33 @@ class Reline::ANSI
64
65
  end
65
66
  end
66
67
 
68
+ def self.set_default_key_bindings_ansi_cursor(config)
69
+ ANSI_CURSOR_KEY_BINDINGS.each do |char, (default_func, modifiers)|
70
+ bindings = [["\e[#{char}", default_func]] # CSI + char
71
+ if modifiers[:ctrl]
72
+ # CSI + ctrl_key_modifier + char
73
+ bindings << ["\e[1;5#{char}", modifiers[:ctrl]]
74
+ end
75
+ if modifiers[:meta]
76
+ # CSI + meta_key_modifier + char
77
+ bindings << ["\e[1;3#{char}", modifiers[:meta]]
78
+ # Meta(ESC) + CSI + char
79
+ bindings << ["\e\e[#{char}", modifiers[:meta]]
80
+ end
81
+ bindings.each do |sequence, func|
82
+ key = sequence.bytes
83
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
84
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
85
+ config.add_default_key_binding_by_keymap(:vi_command, key, func)
86
+ end
87
+ end
88
+ end
89
+
67
90
  def self.set_default_key_bindings_terminfo(config)
68
91
  key_bindings = CAPNAME_KEY_BINDINGS.map do |capname, key_binding|
69
92
  begin
70
93
  key_code = Reline::Terminfo.tigetstr(capname)
71
- case capname
72
- # Escape sequences that omit the move distance and are set to defaults
73
- # value 1 may be sometimes sent by pressing the arrow-key.
74
- when 'cuu', 'cud', 'cuf', 'cub'
75
- [ key_code.sub(/%p1%d/, '').bytes, key_binding ]
76
- else
77
- [ key_code.bytes, key_binding ]
78
- end
94
+ [ key_code.bytes, key_binding ]
79
95
  rescue Reline::Terminfo::TerminfoError
80
96
  # capname is undefined
81
97
  end
@@ -94,14 +110,8 @@ class Reline::ANSI
94
110
  [27, 91, 49, 126] => :ed_move_to_beg, # Home
95
111
  [27, 91, 52, 126] => :ed_move_to_end, # End
96
112
  [27, 91, 51, 126] => :key_delete, # Del
97
- [27, 91, 65] => :ed_prev_history, # ↑
98
- [27, 91, 66] => :ed_next_history, # ↓
99
- [27, 91, 67] => :ed_next_char, # →
100
- [27, 91, 68] => :ed_prev_char, # ←
101
113
 
102
114
  # KDE
103
- [27, 91, 72] => :ed_move_to_beg, # Home
104
- [27, 91, 70] => :ed_move_to_end, # End
105
115
  # Del is 0x08
106
116
  [27, 71, 65] => :ed_prev_history, # ↑
107
117
  [27, 71, 66] => :ed_next_history, # ↓
@@ -118,12 +128,6 @@ class Reline::ANSI
118
128
  # Del is 0x08
119
129
  # Arrow keys are the same of KDE
120
130
 
121
- # iTerm2
122
- [27, 27, 91, 67] => :em_next_word, # Option+→, extended entry
123
- [27, 27, 91, 68] => :ed_prev_word, # Option+←, extended entry
124
- [195, 166] => :em_next_word, # Option+f
125
- [195, 162] => :ed_prev_word, # Option+b
126
-
127
131
  [27, 79, 65] => :ed_prev_history, # ↑
128
132
  [27, 79, 66] => :ed_next_history, # ↓
129
133
  [27, 79, 67] => :ed_next_char, # →
@@ -206,7 +210,7 @@ class Reline::ANSI
206
210
  end
207
211
 
208
212
  def self.in_pasting?
209
- @@in_bracketed_paste_mode or (not Reline::IOGate.empty_buffer?)
213
+ @@in_bracketed_paste_mode or (not empty_buffer?)
210
214
  end
211
215
 
212
216
  def self.empty_buffer?
@@ -331,9 +335,12 @@ class Reline::ANSI
331
335
  @@output.write "\e[K"
332
336
  end
333
337
 
338
+ # This only works when the cursor is at the bottom of the scroll range
339
+ # For more details, see https://github.com/ruby/reline/pull/577#issuecomment-1646679623
334
340
  def self.scroll_down(x)
335
341
  return if x.zero?
336
- @@output.write "\e[#{x}S"
342
+ # We use `\n` instead of CSI + S because CSI + S would cause https://github.com/ruby/reline/issues/576
343
+ @@output.write "\n" * x
337
344
  end
338
345
 
339
346
  def self.clear_screen
@@ -1,4 +1,8 @@
1
1
  class Reline::KeyStroke
2
+ ESC_BYTE = 27
3
+ CSI_PARAMETER_BYTES_RANGE = 0x30..0x3f
4
+ CSI_INTERMEDIATE_BYTES_RANGE = (0x20..0x2f)
5
+
2
6
  def initialize(config)
3
7
  @config = config
4
8
  end
@@ -73,17 +77,26 @@ class Reline::KeyStroke
73
77
  return :matched if it.max_by(&:size)&.size&.< input.size
74
78
  return :matching if it.size > 1
75
79
  }
76
- key_mapping.keys.select { |lhs|
77
- start_with?(input, lhs)
78
- }.tap { |it|
79
- return it.size > 0 ? :matched : :unmatched
80
- }
80
+ if key_mapping.keys.any? { |lhs| start_with?(input, lhs) }
81
+ :matched
82
+ else
83
+ match_unknown_escape_sequence(input).first
84
+ end
81
85
  end
82
86
 
83
87
  def expand(input)
84
- input = compress_meta_key(input)
85
88
  lhs = key_mapping.keys.select { |item| start_with?(input, item) }.sort_by(&:size).last
86
- return input unless lhs
89
+ unless lhs
90
+ status, size = match_unknown_escape_sequence(input)
91
+ case status
92
+ when :matched
93
+ return [:ed_unassigned] + expand(input.drop(size))
94
+ when :matching
95
+ return [:ed_unassigned]
96
+ else
97
+ return input
98
+ end
99
+ end
87
100
  rhs = key_mapping[lhs]
88
101
 
89
102
  case rhs
@@ -99,6 +112,36 @@ class Reline::KeyStroke
99
112
 
100
113
  private
101
114
 
115
+ # returns match status of CSI/SS3 sequence and matched length
116
+ def match_unknown_escape_sequence(input)
117
+ idx = 0
118
+ return [:unmatched, nil] unless input[idx] == ESC_BYTE
119
+ idx += 1
120
+ idx += 1 if input[idx] == ESC_BYTE
121
+
122
+ case input[idx]
123
+ when nil
124
+ return [:matching, nil]
125
+ when 91 # == '['.ord
126
+ # CSI sequence
127
+ idx += 1
128
+ idx += 1 while idx < input.size && CSI_PARAMETER_BYTES_RANGE.cover?(input[idx])
129
+ idx += 1 while idx < input.size && CSI_INTERMEDIATE_BYTES_RANGE.cover?(input[idx])
130
+ input[idx] ? [:matched, idx + 1] : [:matching, nil]
131
+ when 79 # == 'O'.ord
132
+ # SS3 sequence
133
+ input[idx + 1] ? [:matched, idx + 2] : [:matching, nil]
134
+ else
135
+ if idx == 1
136
+ # `ESC char`, make it :unmatched so that it will be handled correctly in `read_2nd_character_of_key_sequence`
137
+ [:unmatched, nil]
138
+ else
139
+ # `ESC ESC char`
140
+ [:matched, idx + 1]
141
+ end
142
+ end
143
+ end
144
+
102
145
  def key_mapping
103
146
  @config.key_bindings
104
147
  end
@@ -48,8 +48,8 @@ class Reline::LineEditor
48
48
  PERFECT_MATCH = :perfect_match
49
49
  end
50
50
 
51
- CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
52
- MenuInfo = Struct.new('MenuInfo', :target, :list)
51
+ CompletionJourneyData = Struct.new(:preposing, :postposing, :list, :pointer)
52
+ MenuInfo = Struct.new(:target, :list)
53
53
 
54
54
  PROMPT_LIST_CACHE_TIMEOUT = 0.5
55
55
  MINIMUM_SCROLLBAR_HEIGHT = 1
@@ -60,6 +60,10 @@ class Reline::LineEditor
60
60
  reset_variables(encoding: encoding)
61
61
  end
62
62
 
63
+ def io_gate
64
+ Reline::IOGate
65
+ end
66
+
63
67
  def set_pasting_state(in_pasting)
64
68
  @in_pasting = in_pasting
65
69
  end
@@ -1510,11 +1514,13 @@ class Reline::LineEditor
1510
1514
  return if key.char >= 128 # maybe, first byte of multi byte
1511
1515
  method_symbol = @config.editing_mode.get_method(key.combined_char)
1512
1516
  if key.with_meta and method_symbol == :ed_unassigned
1513
- # split ESC + key
1514
- method_symbol = @config.editing_mode.get_method("\e".ord)
1515
- process_key("\e".ord, method_symbol)
1516
- method_symbol = @config.editing_mode.get_method(key.char)
1517
- process_key(key.char, method_symbol)
1517
+ if @config.editing_mode_is?(:vi_command, :vi_insert)
1518
+ # split ESC + key in vi mode
1519
+ method_symbol = @config.editing_mode.get_method("\e".ord)
1520
+ process_key("\e".ord, method_symbol)
1521
+ method_symbol = @config.editing_mode.get_method(key.char)
1522
+ process_key(key.char, method_symbol)
1523
+ end
1518
1524
  else
1519
1525
  process_key(key.combined_char, method_symbol)
1520
1526
  end
@@ -1599,7 +1605,7 @@ class Reline::LineEditor
1599
1605
  else
1600
1606
  @just_cursor_moving = false
1601
1607
  end
1602
- if @is_multiline and @auto_indent_proc and not simplified_rendering?
1608
+ if @is_multiline and @auto_indent_proc and not simplified_rendering? and @line
1603
1609
  process_auto_indent
1604
1610
  end
1605
1611
  end
@@ -1645,7 +1651,7 @@ class Reline::LineEditor
1645
1651
  @line = ' ' * new_indent + @line.lstrip
1646
1652
 
1647
1653
  new_indent = nil
1648
- result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, (new_lines[-2].size + 1), false)
1654
+ result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, (new_lines[@line_index - 1].bytesize + 1), false)
1649
1655
  if result
1650
1656
  new_indent = result
1651
1657
  end
@@ -3286,4 +3292,7 @@ class Reline::LineEditor
3286
3292
  @mark_pointer = new_pointer
3287
3293
  end
3288
3294
  alias_method :exchange_point_and_mark, :em_exchange_mark
3295
+
3296
+ private def em_meta_next(key)
3297
+ end
3289
3298
  end
@@ -41,26 +41,6 @@ class Reline::Unicode
41
41
  OSC_REGEXP = /\e\]\d+(?:;[^;\a\e]+)*(?:\a|\e\\)/
42
42
  WIDTH_SCANNER = /\G(?:(#{NON_PRINTING_START})|(#{NON_PRINTING_END})|(#{CSI_REGEXP})|(#{OSC_REGEXP})|(\X))/o
43
43
 
44
- def self.get_mbchar_byte_size_by_first_char(c)
45
- # Checks UTF-8 character byte size
46
- case c.ord
47
- # 0b0xxxxxxx
48
- when ->(code) { (code ^ 0b10000000).allbits?(0b10000000) } then 1
49
- # 0b110xxxxx
50
- when ->(code) { (code ^ 0b00100000).allbits?(0b11100000) } then 2
51
- # 0b1110xxxx
52
- when ->(code) { (code ^ 0b00010000).allbits?(0b11110000) } then 3
53
- # 0b11110xxx
54
- when ->(code) { (code ^ 0b00001000).allbits?(0b11111000) } then 4
55
- # 0b111110xx
56
- when ->(code) { (code ^ 0b00000100).allbits?(0b11111100) } then 5
57
- # 0b1111110x
58
- when ->(code) { (code ^ 0b00000010).allbits?(0b11111110) } then 6
59
- # successor of mbchar
60
- else 0
61
- end
62
- end
63
-
64
44
  def self.escape_for_print(str)
65
45
  str.chars.map! { |gr|
66
46
  escaped = EscapedPairs[gr.ord]
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.3.5'
2
+ VERSION = '0.3.7'
3
3
  end
data/lib/reline.rb CHANGED
@@ -17,7 +17,7 @@ module Reline
17
17
 
18
18
  class ConfigEncodingConversionError < StandardError; end
19
19
 
20
- Key = Struct.new('Key', :char, :combined_char, :with_meta) do
20
+ Key = Struct.new(:char, :combined_char, :with_meta) do
21
21
  def match?(other)
22
22
  case other
23
23
  when Reline::Key
@@ -83,44 +83,48 @@ module Reline
83
83
  @bracketed_paste_finished = false
84
84
  end
85
85
 
86
+ def io_gate
87
+ Reline::IOGate
88
+ end
89
+
86
90
  def encoding
87
- Reline::IOGate.encoding
91
+ io_gate.encoding
88
92
  end
89
93
 
90
94
  def completion_append_character=(val)
91
95
  if val.nil?
92
96
  @completion_append_character = nil
93
97
  elsif val.size == 1
94
- @completion_append_character = val.encode(Reline::IOGate.encoding)
98
+ @completion_append_character = val.encode(encoding)
95
99
  elsif val.size > 1
96
- @completion_append_character = val[0].encode(Reline::IOGate.encoding)
100
+ @completion_append_character = val[0].encode(encoding)
97
101
  else
98
102
  @completion_append_character = nil
99
103
  end
100
104
  end
101
105
 
102
106
  def basic_word_break_characters=(v)
103
- @basic_word_break_characters = v.encode(Reline::IOGate.encoding)
107
+ @basic_word_break_characters = v.encode(encoding)
104
108
  end
105
109
 
106
110
  def completer_word_break_characters=(v)
107
- @completer_word_break_characters = v.encode(Reline::IOGate.encoding)
111
+ @completer_word_break_characters = v.encode(encoding)
108
112
  end
109
113
 
110
114
  def basic_quote_characters=(v)
111
- @basic_quote_characters = v.encode(Reline::IOGate.encoding)
115
+ @basic_quote_characters = v.encode(encoding)
112
116
  end
113
117
 
114
118
  def completer_quote_characters=(v)
115
- @completer_quote_characters = v.encode(Reline::IOGate.encoding)
119
+ @completer_quote_characters = v.encode(encoding)
116
120
  end
117
121
 
118
122
  def filename_quote_characters=(v)
119
- @filename_quote_characters = v.encode(Reline::IOGate.encoding)
123
+ @filename_quote_characters = v.encode(encoding)
120
124
  end
121
125
 
122
126
  def special_prefixes=(v)
123
- @special_prefixes = v.encode(Reline::IOGate.encoding)
127
+ @special_prefixes = v.encode(encoding)
124
128
  end
125
129
 
126
130
  def completion_case_fold=(v)
@@ -181,20 +185,16 @@ module Reline
181
185
 
182
186
  def input=(val)
183
187
  raise TypeError unless val.respond_to?(:getc) or val.nil?
184
- if val.respond_to?(:getc)
185
- if defined?(Reline::ANSI) and Reline::IOGate == Reline::ANSI
186
- Reline::ANSI.input = val
187
- elsif Reline::IOGate == Reline::GeneralIO
188
- Reline::GeneralIO.input = val
189
- end
188
+ if val.respond_to?(:getc) && io_gate.respond_to?(:input=)
189
+ io_gate.input = val
190
190
  end
191
191
  end
192
192
 
193
193
  def output=(val)
194
194
  raise TypeError unless val.respond_to?(:write) or val.nil?
195
195
  @output = val
196
- if defined?(Reline::ANSI) and Reline::IOGate == Reline::ANSI
197
- Reline::ANSI.output = val
196
+ if io_gate.respond_to?(:output=)
197
+ io_gate.output = val
198
198
  end
199
199
  end
200
200
 
@@ -217,7 +217,7 @@ module Reline
217
217
  end
218
218
 
219
219
  def get_screen_size
220
- Reline::IOGate.get_screen_size
220
+ io_gate.get_screen_size
221
221
  end
222
222
 
223
223
  Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE = ->() {
@@ -270,7 +270,8 @@ module Reline
270
270
  Reline::DEFAULT_DIALOG_CONTEXT = Array.new
271
271
 
272
272
  def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
273
- Reline::IOGate.with_raw_input do
273
+ Reline.update_iogate
274
+ io_gate.with_raw_input do
274
275
  unless confirm_multiline_termination
275
276
  raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
276
277
  end
@@ -288,6 +289,7 @@ module Reline
288
289
  end
289
290
 
290
291
  def readline(prompt = '', add_hist = false)
292
+ Reline.update_iogate
291
293
  inner_readline(prompt, add_hist, false)
292
294
 
293
295
  line = line_editor.line.dup
@@ -302,7 +304,7 @@ module Reline
302
304
 
303
305
  private def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
304
306
  if ENV['RELINE_STDERR_TTY']
305
- if Reline::IOGate.win?
307
+ if io_gate.win?
306
308
  $stderr = File.open(ENV['RELINE_STDERR_TTY'], 'a')
307
309
  else
308
310
  $stderr.reopen(ENV['RELINE_STDERR_TTY'], 'w')
@@ -310,10 +312,10 @@ module Reline
310
312
  $stderr.sync = true
311
313
  $stderr.puts "Reline is used by #{Process.pid}"
312
314
  end
313
- otio = Reline::IOGate.prep
315
+ otio = io_gate.prep
314
316
 
315
317
  may_req_ambiguous_char_width
316
- line_editor.reset(prompt, encoding: Reline::IOGate.encoding)
318
+ line_editor.reset(prompt, encoding: encoding)
317
319
  if multiline
318
320
  line_editor.multiline_on
319
321
  if block_given?
@@ -337,7 +339,7 @@ module Reline
337
339
  unless config.test_mode
338
340
  config.read
339
341
  config.reset_default_key_bindings
340
- Reline::IOGate.set_default_key_bindings(config)
342
+ io_gate.set_default_key_bindings(config)
341
343
  end
342
344
 
343
345
  line_editor.rerender
@@ -346,9 +348,9 @@ module Reline
346
348
  line_editor.set_signal_handlers
347
349
  prev_pasting_state = false
348
350
  loop do
349
- prev_pasting_state = Reline::IOGate.in_pasting?
351
+ prev_pasting_state = io_gate.in_pasting?
350
352
  read_io(config.keyseq_timeout) { |inputs|
351
- line_editor.set_pasting_state(Reline::IOGate.in_pasting?)
353
+ line_editor.set_pasting_state(io_gate.in_pasting?)
352
354
  inputs.each { |c|
353
355
  line_editor.input_key(c)
354
356
  line_editor.rerender
@@ -358,29 +360,29 @@ module Reline
358
360
  @bracketed_paste_finished = false
359
361
  end
360
362
  }
361
- if prev_pasting_state == true and not Reline::IOGate.in_pasting? and not line_editor.finished?
363
+ if prev_pasting_state == true and not io_gate.in_pasting? and not line_editor.finished?
362
364
  line_editor.set_pasting_state(false)
363
365
  prev_pasting_state = false
364
366
  line_editor.rerender_all
365
367
  end
366
368
  break if line_editor.finished?
367
369
  end
368
- Reline::IOGate.move_cursor_column(0)
370
+ io_gate.move_cursor_column(0)
369
371
  rescue Errno::EIO
370
372
  # Maybe the I/O has been closed.
371
373
  rescue StandardError => e
372
374
  line_editor.finalize
373
- Reline::IOGate.deprep(otio)
375
+ io_gate.deprep(otio)
374
376
  raise e
375
377
  rescue Exception
376
378
  # Including Interrupt
377
379
  line_editor.finalize
378
- Reline::IOGate.deprep(otio)
380
+ io_gate.deprep(otio)
379
381
  raise
380
382
  end
381
383
 
382
384
  line_editor.finalize
383
- Reline::IOGate.deprep(otio)
385
+ io_gate.deprep(otio)
384
386
  end
385
387
 
386
388
  # GNU Readline waits for "keyseq-timeout" milliseconds to see if the ESC
@@ -395,7 +397,7 @@ module Reline
395
397
  private def read_io(keyseq_timeout, &block)
396
398
  buffer = []
397
399
  loop do
398
- c = Reline::IOGate.getc
400
+ c = io_gate.getc
399
401
  if c == -1
400
402
  result = :unmatched
401
403
  @bracketed_paste_finished = true
@@ -435,7 +437,7 @@ module Reline
435
437
  begin
436
438
  succ_c = nil
437
439
  Timeout.timeout(keyseq_timeout / 1000.0) {
438
- succ_c = Reline::IOGate.getc
440
+ succ_c = io_gate.getc
439
441
  }
440
442
  rescue Timeout::Error # cancel matching only when first byte
441
443
  block.([Reline::Key.new(c, c, false)])
@@ -450,7 +452,7 @@ module Reline
450
452
  end
451
453
  return :break
452
454
  when :matching
453
- Reline::IOGate.ungetc(succ_c)
455
+ io_gate.ungetc(succ_c)
454
456
  return :next
455
457
  when :matched
456
458
  buffer << succ_c
@@ -467,7 +469,7 @@ module Reline
467
469
  begin
468
470
  escaped_c = nil
469
471
  Timeout.timeout(keyseq_timeout / 1000.0) {
470
- escaped_c = Reline::IOGate.getc
472
+ escaped_c = io_gate.getc
471
473
  }
472
474
  rescue Timeout::Error # independent ESC
473
475
  block.([Reline::Key.new(c, c, false)])
@@ -490,19 +492,19 @@ module Reline
490
492
  end
491
493
 
492
494
  private def may_req_ambiguous_char_width
493
- @ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or !STDOUT.tty?
495
+ @ambiguous_width = 2 if io_gate == Reline::GeneralIO or !STDOUT.tty?
494
496
  return if defined? @ambiguous_width
495
- Reline::IOGate.move_cursor_column(0)
497
+ io_gate.move_cursor_column(0)
496
498
  begin
497
499
  output.write "\u{25bd}"
498
500
  rescue Encoding::UndefinedConversionError
499
501
  # LANG=C
500
502
  @ambiguous_width = 1
501
503
  else
502
- @ambiguous_width = Reline::IOGate.cursor_pos.x
504
+ @ambiguous_width = io_gate.cursor_pos.x
503
505
  end
504
- Reline::IOGate.move_cursor_column(0)
505
- Reline::IOGate.erase_after_cursor
506
+ io_gate.move_cursor_column(0)
507
+ io_gate.erase_after_cursor
506
508
  end
507
509
  end
508
510
 
@@ -565,7 +567,7 @@ module Reline
565
567
  @core ||= Core.new { |core|
566
568
  core.config = Reline::Config.new
567
569
  core.key_stroke = Reline::KeyStroke.new(core.config)
568
- core.line_editor = Reline::LineEditor.new(core.config, Reline::IOGate.encoding)
570
+ core.line_editor = Reline::LineEditor.new(core.config, core.encoding)
569
571
 
570
572
  core.basic_word_break_characters = " \t\n`><=;|&{("
571
573
  core.completer_word_break_characters = " \t\n`><=;|&{("
@@ -578,12 +580,24 @@ module Reline
578
580
  end
579
581
 
580
582
  def self.ungetc(c)
581
- Reline::IOGate.ungetc(c)
583
+ core.io_gate.ungetc(c)
582
584
  end
583
585
 
584
586
  def self.line_editor
585
587
  core.line_editor
586
588
  end
589
+
590
+ def self.update_iogate
591
+ return if core.config.test_mode
592
+
593
+ # Need to change IOGate when `$stdout.tty?` change from false to true by `$stdout.reopen`
594
+ # Example: rails/spring boot the application in non-tty, then run console in tty.
595
+ if ENV['TERM'] != 'dumb' && core.io_gate == Reline::GeneralIO && $stdout.tty?
596
+ require 'reline/ansi'
597
+ remove_const(:IOGate)
598
+ const_set(:IOGate, Reline::ANSI)
599
+ end
600
+ end
587
601
  end
588
602
 
589
603
  require 'reline/general_io'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.3.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-03 00:00:00.000000000 Z
11
+ date: 2023-07-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console