reline 0.3.5 → 0.3.7

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.
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