irb 1.15.2 → 1.17.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.
data/lib/irb/color.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require 'reline'
3
- require 'ripper'
3
+ require 'prism'
4
4
  require_relative 'ruby-lex'
5
5
 
6
6
  module IRB # :nodoc:
@@ -18,65 +18,100 @@ module IRB # :nodoc:
18
18
  CYAN = 36
19
19
  WHITE = 37
20
20
 
21
- TOKEN_KEYWORDS = {
22
- on_kw: ['nil', 'self', 'true', 'false', '__FILE__', '__LINE__', '__ENCODING__'],
23
- on_const: ['ENV'],
24
- }
25
- private_constant :TOKEN_KEYWORDS
26
-
27
- # A constant of all-bit 1 to match any Ripper's state in #dispatch_seq
28
- ALL = -1
29
- private_constant :ALL
30
-
31
- begin
32
- # Following pry's colors where possible, but sometimes having a compromise like making
33
- # backtick and regexp as red (string's color, because they're sharing tokens).
34
- TOKEN_SEQ_EXPRS = {
35
- on_CHAR: [[BLUE, BOLD], ALL],
36
- on_backtick: [[RED, BOLD], ALL],
37
- on_comment: [[BLUE, BOLD], ALL],
38
- on_const: [[BLUE, BOLD, UNDERLINE], ALL],
39
- on_embexpr_beg: [[RED], ALL],
40
- on_embexpr_end: [[RED], ALL],
41
- on_embvar: [[RED], ALL],
42
- on_float: [[MAGENTA, BOLD], ALL],
43
- on_gvar: [[GREEN, BOLD], ALL],
44
- on_backref: [[GREEN, BOLD], ALL],
45
- on_heredoc_beg: [[RED], ALL],
46
- on_heredoc_end: [[RED], ALL],
47
- on_ident: [[BLUE, BOLD], Ripper::EXPR_ENDFN],
48
- on_imaginary: [[BLUE, BOLD], ALL],
49
- on_int: [[BLUE, BOLD], ALL],
50
- on_kw: [[GREEN], ALL],
51
- on_label: [[MAGENTA], ALL],
52
- on_label_end: [[RED, BOLD], ALL],
53
- on_qsymbols_beg: [[RED, BOLD], ALL],
54
- on_qwords_beg: [[RED, BOLD], ALL],
55
- on_rational: [[BLUE, BOLD], ALL],
56
- on_regexp_beg: [[RED, BOLD], ALL],
57
- on_regexp_end: [[RED, BOLD], ALL],
58
- on_symbeg: [[YELLOW], ALL],
59
- on_symbols_beg: [[RED, BOLD], ALL],
60
- on_tstring_beg: [[RED, BOLD], ALL],
61
- on_tstring_content: [[RED], ALL],
62
- on_tstring_end: [[RED, BOLD], ALL],
63
- on_words_beg: [[RED, BOLD], ALL],
64
- on_parse_error: [[RED, REVERSE], ALL],
65
- compile_error: [[RED, REVERSE], ALL],
66
- on_assign_error: [[RED, REVERSE], ALL],
67
- on_alias_error: [[RED, REVERSE], ALL],
68
- on_class_name_error:[[RED, REVERSE], ALL],
69
- on_param_error: [[RED, REVERSE], ALL],
70
- on___end__: [[GREEN], ALL],
71
- }
72
- rescue NameError
73
- # Give up highlighting Ripper-incompatible older Ruby
74
- TOKEN_SEQ_EXPRS = {}
21
+ # Following pry's colors where possible
22
+ TOKEN_SEQS = {
23
+ KEYWORD_NIL: [CYAN, BOLD],
24
+ KEYWORD_SELF: [CYAN, BOLD],
25
+ KEYWORD_TRUE: [CYAN, BOLD],
26
+ KEYWORD_FALSE: [CYAN, BOLD],
27
+ KEYWORD___FILE__: [CYAN, BOLD],
28
+ KEYWORD___LINE__: [CYAN, BOLD],
29
+ KEYWORD___ENCODING__: [CYAN, BOLD],
30
+ CHARACTER_LITERAL: [BLUE, BOLD],
31
+ BACK_REFERENCE: [GREEN, BOLD],
32
+ BACKTICK: [RED, BOLD],
33
+ COMMENT: [BLUE, BOLD],
34
+ EMBDOC_BEGIN: [BLUE, BOLD],
35
+ EMBDOC_LINE: [BLUE, BOLD],
36
+ EMBDOC_END: [BLUE, BOLD],
37
+ CONSTANT: [BLUE, BOLD, UNDERLINE],
38
+ EMBEXPR_BEGIN: [RED],
39
+ EMBEXPR_END: [RED],
40
+ EMBVAR: [RED],
41
+ FLOAT: [MAGENTA, BOLD],
42
+ GLOBAL_VARIABLE: [GREEN, BOLD],
43
+ HEREDOC_START: [RED],
44
+ HEREDOC_END: [RED],
45
+ FLOAT_IMAGINARY: [BLUE, BOLD],
46
+ INTEGER_IMAGINARY: [BLUE, BOLD],
47
+ FLOAT_RATIONAL_IMAGINARY: [BLUE, BOLD],
48
+ INTEGER_RATIONAL_IMAGINARY: [BLUE, BOLD],
49
+ INTEGER: [BLUE, BOLD],
50
+ INTEGER_RATIONAL: [BLUE, BOLD],
51
+ FLOAT_RATIONAL: [BLUE, BOLD],
52
+ KEYWORD_END: [GREEN],
53
+ KEYWORD_CLASS: [GREEN],
54
+ KEYWORD_MODULE: [GREEN],
55
+ KEYWORD_IF: [GREEN],
56
+ KEYWORD_IF_MODIFIER: [GREEN],
57
+ KEYWORD_UNLESS_MODIFIER: [GREEN],
58
+ KEYWORD_WHILE_MODIFIER: [GREEN],
59
+ KEYWORD_UNTIL_MODIFIER: [GREEN],
60
+ KEYWORD_RESCUE_MODIFIER: [GREEN],
61
+ KEYWORD_THEN: [GREEN],
62
+ KEYWORD_UNLESS: [GREEN],
63
+ KEYWORD_ELSE: [GREEN],
64
+ KEYWORD_ELSIF: [GREEN],
65
+ KEYWORD_WHILE: [GREEN],
66
+ KEYWORD_UNTIL: [GREEN],
67
+ KEYWORD_CASE: [GREEN],
68
+ KEYWORD_WHEN: [GREEN],
69
+ KEYWORD_IN: [GREEN],
70
+ KEYWORD_DEF: [GREEN],
71
+ KEYWORD_DO: [GREEN],
72
+ KEYWORD_DO_LOOP: [GREEN],
73
+ KEYWORD_FOR: [GREEN],
74
+ KEYWORD_BEGIN: [GREEN],
75
+ KEYWORD_RESCUE: [GREEN],
76
+ KEYWORD_ENSURE: [GREEN],
77
+ KEYWORD_ALIAS: [GREEN],
78
+ KEYWORD_UNDEF: [GREEN],
79
+ KEYWORD_BEGIN_UPCASE: [GREEN],
80
+ KEYWORD_END_UPCASE: [GREEN],
81
+ KEYWORD_YIELD: [GREEN],
82
+ KEYWORD_REDO: [GREEN],
83
+ KEYWORD_RETRY: [GREEN],
84
+ KEYWORD_NEXT: [GREEN],
85
+ KEYWORD_BREAK: [GREEN],
86
+ KEYWORD_SUPER: [GREEN],
87
+ KEYWORD_RETURN: [GREEN],
88
+ KEYWORD_DEFINED: [GREEN],
89
+ KEYWORD_NOT: [GREEN],
90
+ KEYWORD_AND: [GREEN],
91
+ KEYWORD_OR: [GREEN],
92
+ LABEL: [MAGENTA],
93
+ LABEL_END: [RED, BOLD],
94
+ NUMBERED_REFERENCE: [GREEN, BOLD],
95
+ PERCENT_UPPER_W: [RED, BOLD],
96
+ PERCENT_LOWER_W: [RED, BOLD],
97
+ PERCENT_LOWER_X: [RED, BOLD],
98
+ REGEXP_BEGIN: [RED, BOLD],
99
+ REGEXP_END: [RED, BOLD],
100
+ STRING_BEGIN: [RED, BOLD],
101
+ STRING_CONTENT: [RED],
102
+ STRING_END: [RED, BOLD],
103
+ __END__: [GREEN],
104
+ # tokens from syntax tree traversal
105
+ method_name: [BLUE, BOLD],
106
+ symbol: [YELLOW],
107
+ # special colorization
108
+ error: [RED, REVERSE],
109
+ const_env: [CYAN, BOLD],
110
+ }.transform_values do |styles|
111
+ styles.map { |style| "\e[#{style}m" }.join
75
112
  end
76
- private_constant :TOKEN_SEQ_EXPRS
77
-
78
- ERROR_TOKENS = TOKEN_SEQ_EXPRS.keys.select { |k| k.to_s.end_with?('error') }
79
- private_constant :ERROR_TOKENS
113
+ CLEAR_SEQ = "\e[#{CLEAR}m"
114
+ private_constant :TOKEN_SEQS, :CLEAR_SEQ
80
115
 
81
116
  class << self
82
117
  def colorable?
@@ -113,14 +148,13 @@ module IRB # :nodoc:
113
148
  end
114
149
 
115
150
  def clear(colorable: colorable?)
116
- return '' unless colorable
117
- "\e[#{CLEAR}m"
151
+ colorable ? CLEAR_SEQ : ''
118
152
  end
119
153
 
120
154
  def colorize(text, seq, colorable: colorable?)
121
155
  return text unless colorable
122
156
  seq = seq.map { |s| "\e[#{const_get(s)}m" }.join('')
123
- "#{seq}#{text}#{clear(colorable: colorable)}"
157
+ "#{seq}#{text}#{CLEAR_SEQ}"
124
158
  end
125
159
 
126
160
  # If `complete` is false (code is incomplete), this does not warn compile_error.
@@ -129,135 +163,132 @@ module IRB # :nodoc:
129
163
  def colorize_code(code, complete: true, ignore_error: false, colorable: colorable?, local_variables: [])
130
164
  return code unless colorable
131
165
 
132
- symbol_state = SymbolState.new
133
- colored = +''
134
- lvars_code = RubyLex.generate_local_variables_assign_code(local_variables)
135
- code_with_lvars = lvars_code ? "#{lvars_code}\n#{code}" : code
166
+ result = Prism.parse_lex(code, scopes: [local_variables])
136
167
 
137
- scan(code_with_lvars, allow_last_error: !complete) do |token, str, expr|
138
- # handle uncolorable code
139
- if token.nil?
140
- colored << Reline::Unicode.escape_for_print(str)
141
- next
142
- end
168
+ # IRB::ColorPrinter skips colorizing syntax invalid fragments
169
+ return Reline::Unicode.escape_for_print(code) if ignore_error && !result.success?
170
+
171
+ errors = result.errors
172
+ unless complete
173
+ errors = errors.reject { |error| error.message =~ /\Aexpected a|unexpected end-of-input|unterminated/ }
174
+ end
175
+
176
+ prism_node, prism_tokens = result.value
177
+ visitor = ColorizeVisitor.new
178
+ prism_node.accept(visitor)
179
+
180
+ error_tokens = errors.map { |e| [e.location.start_line, e.location.start_column, 0, e.location.end_line, e.location.end_column, :error, e.location.slice] }
181
+ error_tokens.reject! { |t| t.last.match?(/\A\s*\z/) }
182
+ tokens = prism_tokens.map { |t,| [t.location.start_line, t.location.start_column, 2, t.location.end_line, t.location.end_column, t.type, t.value] }
183
+ tokens.pop if tokens.last&.[](5) == :EOF
143
184
 
144
- # IRB::ColorPrinter skips colorizing fragments with any invalid token
145
- if ignore_error && ERROR_TOKENS.include?(token)
146
- return Reline::Unicode.escape_for_print(code)
185
+ colored = +''
186
+ line_index = 0
187
+ col = 0
188
+ lines = code.lines
189
+ flush = -> next_line_index, next_col {
190
+ return if next_line_index == line_index && next_col == col
191
+ (line_index...[next_line_index, lines.size].min).each do |ln|
192
+ colored << Reline::Unicode.escape_for_print(lines[line_index].byteslice(col..))
193
+ line_index = ln + 1
194
+ col = 0
195
+ end
196
+ unless col == next_col
197
+ colored << Reline::Unicode.escape_for_print(lines[next_line_index].byteslice(col..next_col - 1))
147
198
  end
199
+ }
200
+
201
+ (visitor.tokens + tokens + error_tokens).sort.each do |start_line, start_column, _priority, end_line, end_column, type, value|
202
+ next if start_line - 1 < line_index || (start_line - 1 == line_index && start_column < col)
148
203
 
149
- in_symbol = symbol_state.scan_token(token)
150
- str.each_line do |line|
151
- line = Reline::Unicode.escape_for_print(line)
152
- if seq = dispatch_seq(token, expr, line, in_symbol: in_symbol)
153
- colored << seq.map { |s| "\e[#{s}m" }.join('')
154
- colored << line.sub(/\Z/, clear(colorable: colorable))
155
- else
156
- colored << line
204
+ flush.call(start_line - 1, start_column)
205
+ if type == :CONSTANT && value == 'ENV'
206
+ color = TOKEN_SEQS[:const_env]
207
+ elsif type == :__END__
208
+ color = TOKEN_SEQS[type]
209
+ end_line = start_line
210
+ value = '__END__'
211
+ end_column = start_column + 7
212
+ else
213
+ color = TOKEN_SEQS[type]
214
+ end
215
+ if color
216
+ value.split(/(\n)/).each do |s|
217
+ colored << (s == "\n" ? s : "#{color}#{Reline::Unicode.escape_for_print(s)}#{CLEAR_SEQ}")
157
218
  end
219
+ else
220
+ colored << value
158
221
  end
222
+ line_index = end_line - 1
223
+ col = end_column
159
224
  end
160
-
161
- if lvars_code
162
- raise "#{lvars_code.dump} should have no \\n" if lvars_code.include?("\n")
163
- colored.sub!(/\A.+\n/, '') # delete_prefix lvars_code with colors
164
- end
225
+ flush.call lines.size, 0
165
226
  colored
166
227
  end
167
228
 
168
- private
169
-
170
- def without_circular_ref(obj, seen:, &block)
171
- return false if seen.key?(obj)
172
- seen[obj] = true
173
- block.call
174
- ensure
175
- seen.delete(obj)
176
- end
229
+ class ColorizeVisitor < Prism::Visitor
230
+ attr_reader :tokens
231
+ def initialize
232
+ @tokens = []
233
+ end
177
234
 
178
- def scan(code, allow_last_error:)
179
- verbose, $VERBOSE = $VERBOSE, nil
180
- RubyLex.compile_with_errors_suppressed(code) do |inner_code, line_no|
181
- lexer = Ripper::Lexer.new(inner_code, '(ripper)', line_no)
182
- byte_pos = 0
183
- line_positions = [0]
184
- inner_code.lines.each do |line|
185
- line_positions << line_positions.last + line.bytesize
235
+ def dispatch(location, type)
236
+ if location
237
+ @tokens << [location.start_line, location.start_column, 1, location.end_line, location.end_column, type, location.slice]
186
238
  end
239
+ end
187
240
 
188
- on_scan = proc do |elem|
189
- start_pos = line_positions[elem.pos[0] - 1] + elem.pos[1]
241
+ def visit_array_node(node)
242
+ if node.opening&.match?(/\A%[iI]/)
243
+ dispatch node.opening_loc, :symbol
244
+ dispatch node.closing_loc, :symbol
245
+ end
246
+ super
247
+ end
190
248
 
191
- # yield uncolorable code
192
- if byte_pos < start_pos
193
- yield(nil, inner_code.byteslice(byte_pos...start_pos), nil)
194
- end
249
+ def visit_def_node(node)
250
+ dispatch node.name_loc, :method_name
251
+ super
252
+ end
195
253
 
196
- if byte_pos <= start_pos
197
- str = elem.tok
198
- yield(elem.event, str, elem.state)
199
- byte_pos = start_pos + str.bytesize
254
+ def visit_interpolated_symbol_node(node)
255
+ dispatch node.opening_loc, :symbol
256
+ node.parts.each do |part|
257
+ case part
258
+ when Prism::StringNode
259
+ dispatch part.content_loc, :symbol
260
+ when Prism::EmbeddedStatementsNode
261
+ dispatch part.opening_loc, :symbol
262
+ dispatch part.closing_loc, :symbol
263
+ when Prism::EmbeddedVariableNode
264
+ dispatch part.operator_loc, :symbol
200
265
  end
201
266
  end
267
+ dispatch node.closing_loc, :symbol
268
+ super
269
+ end
202
270
 
203
- lexer.scan.each do |elem|
204
- next if allow_last_error and /meets end of file|unexpected end-of-input/ =~ elem.message
205
- on_scan.call(elem)
271
+ def visit_symbol_node(node)
272
+ if (node.opening_loc.nil? && node.closing == ':') || node.closing&.match?(/\A['"]:\z/)
273
+ # Colorize { symbol: 1 } and { 'symbol': 1 } as label
274
+ dispatch node.location, :LABEL
275
+ else
276
+ dispatch node.opening_loc, :symbol
277
+ dispatch node.value_loc, :symbol
278
+ dispatch node.closing_loc, :symbol
206
279
  end
207
- # yield uncolorable DATA section
208
- yield(nil, inner_code.byteslice(byte_pos...inner_code.bytesize), nil) if byte_pos < inner_code.bytesize
209
280
  end
210
- ensure
211
- $VERBOSE = verbose
212
281
  end
213
282
 
214
- def dispatch_seq(token, expr, str, in_symbol:)
215
- if ERROR_TOKENS.include?(token)
216
- TOKEN_SEQ_EXPRS[token][0]
217
- elsif in_symbol
218
- [YELLOW]
219
- elsif TOKEN_KEYWORDS.fetch(token, []).include?(str)
220
- [CYAN, BOLD]
221
- elsif (seq, exprs = TOKEN_SEQ_EXPRS[token]; (expr & (exprs || 0)) != 0)
222
- seq
223
- else
224
- nil
225
- end
226
- end
227
- end
228
-
229
- # A class to manage a state to know whether the current token is for Symbol or not.
230
- class SymbolState
231
- def initialize
232
- # Push `true` to detect Symbol. `false` to increase the nest level for non-Symbol.
233
- @stack = []
234
- end
283
+ private
235
284
 
236
- # Return true if the token is a part of Symbol.
237
- def scan_token(token)
238
- prev_state = @stack.last
239
- case token
240
- when :on_symbeg, :on_symbols_beg, :on_qsymbols_beg
241
- @stack << true
242
- when :on_ident, :on_op, :on_const, :on_ivar, :on_cvar, :on_gvar, :on_kw, :on_backtick
243
- if @stack.last # Pop only when it's Symbol
244
- @stack.pop
245
- return prev_state
246
- end
247
- when :on_tstring_beg
248
- @stack << false
249
- when :on_embexpr_beg
250
- @stack << false
251
- return prev_state
252
- when :on_tstring_end # :on_tstring_end may close Symbol
253
- @stack.pop
254
- return prev_state
255
- when :on_embexpr_end
256
- @stack.pop
257
- end
258
- @stack.last
285
+ def without_circular_ref(obj, seen:, &block)
286
+ return false if seen.key?(obj)
287
+ seen[obj] = true
288
+ block.call
289
+ ensure
290
+ seen.delete(obj)
259
291
  end
260
292
  end
261
- private_constant :SymbolState
262
293
  end
263
294
  end
@@ -54,6 +54,8 @@ module IRB
54
54
  def clipboard_program
55
55
  @clipboard_program ||= if IRB.conf[:COPY_COMMAND]
56
56
  IRB.conf[:COPY_COMMAND]
57
+ elsif executable?("clip.exe")
58
+ "clip.exe"
57
59
  elsif executable?("pbcopy")
58
60
  "pbcopy"
59
61
  elsif executable?("xclip")
@@ -62,12 +64,20 @@ module IRB
62
64
  end
63
65
 
64
66
  def executable?(command)
65
- system("which #{command} > /dev/null 2>&1")
67
+ if windows?
68
+ system("where #{command} > NUL 2>&1")
69
+ else
70
+ system("which #{command} > /dev/null 2>&1")
71
+ end
66
72
  end
67
73
 
68
74
  def clipboard_available?
69
75
  !!clipboard_program
70
76
  end
77
+
78
+ def windows?
79
+ /mingw|mswin/.match?(RUBY_PLATFORM)
80
+ end
71
81
  end
72
82
  end
73
83
  end
@@ -12,26 +12,6 @@ module IRB
12
12
 
13
13
  # Set of reserved words used by Ruby, you should not use these for
14
14
  # constants or variables
15
- ReservedWords = %w[
16
- __ENCODING__ __LINE__ __FILE__
17
- BEGIN END
18
- alias and
19
- begin break
20
- case class
21
- def defined? do
22
- else elsif end ensure
23
- false for
24
- if in
25
- module
26
- next nil not
27
- or
28
- redo rescue retry return
29
- self super
30
- then true
31
- undef unless until
32
- when while
33
- yield
34
- ]
35
15
 
36
16
  HELP_COMMAND_PREPOSING = /\Ahelp\s+/
37
17
 
@@ -60,13 +40,13 @@ module IRB
60
40
 
61
41
  def retrieve_gem_and_system_load_path
62
42
  candidates = (GEM_PATHS | $LOAD_PATH)
63
- candidates.map do |p|
43
+ candidates.filter_map do |p|
64
44
  if p.respond_to?(:to_path)
65
45
  p.to_path
66
46
  else
67
47
  String(p) rescue nil
68
48
  end
69
- end.compact.sort
49
+ end.sort
70
50
  end
71
51
 
72
52
  def retrieve_files_to_require_from_load_path
@@ -127,7 +107,14 @@ module IRB
127
107
 
128
108
  return commands unless result
129
109
 
130
- commands | result.completion_candidates.map { target + _1 }
110
+ encoded_candidates = result.completion_candidates.filter_map do |i|
111
+ encoded = i.encode(Encoding.default_external)
112
+ target + encoded
113
+ rescue Encoding::UndefinedConversionError
114
+ # If the string cannot be converted, we just ignore it
115
+ nil
116
+ end
117
+ commands | encoded_candidates
131
118
  end
132
119
 
133
120
  def doc_namespace(preposing, matched, _postposing, bind:)
@@ -179,7 +166,7 @@ module IRB
179
166
  else
180
167
  return nil # It's not String literal
181
168
  end
182
- tokens = RubyLex.ripper_lex_without_warning(preposing.gsub(/\s*\z/, ''))
169
+ tokens = RubyLex.ripper_lex_without_warning(preposing.rstrip)
183
170
  tok = nil
184
171
  tokens.reverse_each do |t|
185
172
  unless [:on_lparen, :on_sp, :on_ignored_sp, :on_nl, :on_ignored_nl, :on_comment].include?(t.event)
@@ -191,16 +178,12 @@ module IRB
191
178
 
192
179
  case tok.tok
193
180
  when 'require'
194
- retrieve_files_to_require_from_load_path.select { |path|
195
- path.start_with?(actual_target)
196
- }.map { |path|
197
- quote + path
181
+ retrieve_files_to_require_from_load_path.filter_map { |path|
182
+ quote + path if path.start_with?(actual_target)
198
183
  }
199
184
  when 'require_relative'
200
- retrieve_files_to_require_relative_from_current_dir.select { |path|
201
- path.start_with?(actual_target)
202
- }.map { |path|
203
- quote + path
185
+ retrieve_files_to_require_relative_from_current_dir.filter_map { |path|
186
+ quote + path if path.start_with?(actual_target)
204
187
  }
205
188
  end
206
189
  end
@@ -218,7 +201,12 @@ module IRB
218
201
  # It doesn't make sense to propose commands with other preposing
219
202
  commands = [] unless preposing.empty?
220
203
 
221
- completion_data = retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) }
204
+ completion_data = retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.filter_map do |i|
205
+ i.encode(Encoding.default_external)
206
+ rescue Encoding::UndefinedConversionError
207
+ # If the string cannot be converted, we just ignore it
208
+ nil
209
+ end
222
210
  commands | completion_data
223
211
  end
224
212
 
@@ -287,7 +275,7 @@ module IRB
287
275
  nil
288
276
  else
289
277
  sym = $1
290
- candidates = Symbol.all_symbols.collect do |s|
278
+ candidates = Symbol.all_symbols.filter_map do |s|
291
279
  s.inspect
292
280
  rescue EncodingError
293
281
  # ignore
@@ -459,12 +447,19 @@ module IRB
459
447
  eval("#{perfect_match_var}.class.name", bind) rescue nil
460
448
  else
461
449
  candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s}
462
- candidates |= ReservedWords
450
+ candidates |= RubyLex::RESERVED_WORDS.map(&:to_s)
463
451
  candidates.find{ |i| i == input }
464
452
  end
465
453
  else
466
454
  candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s}
467
- candidates |= ReservedWords
455
+ candidates |= RubyLex::RESERVED_WORDS.map(&:to_s)
456
+
457
+ target_encoding = Encoding.default_external
458
+ candidates = candidates.compact.filter_map do |candidate|
459
+ candidate.encoding == target_encoding ? candidate : candidate.encode(target_encoding)
460
+ rescue EncodingError
461
+ nil
462
+ end
468
463
  candidates.grep(/^#{Regexp.quote(input)}/).sort
469
464
  end
470
465
  end
data/lib/irb/context.rb CHANGED
@@ -632,14 +632,15 @@ module IRB
632
632
  command_class = Command.load_command(command)
633
633
  end
634
634
 
635
- # Check visibility
636
- public_method = !!KERNEL_PUBLIC_METHOD.bind_call(main, command) rescue false
637
- private_method = !public_method && !!KERNEL_METHOD.bind_call(main, command) rescue false
638
- if command_class && Command.execute_as_command?(command, public_method: public_method, private_method: private_method)
639
- Statement::Command.new(code, command_class, arg)
640
- else
641
- Statement::Expression.new(code, is_assignment_expression)
635
+ if command_class
636
+ # Check whether the command conflicts with existing methods
637
+ public_method = !!KERNEL_PUBLIC_METHOD.bind_call(main, command) rescue false
638
+ private_method = !public_method && !!KERNEL_METHOD.bind_call(main, command) rescue false
639
+ if Command.execute_as_command?(command, public_method: public_method, private_method: private_method)
640
+ return Statement::Command.new(code, command_class, arg)
641
+ end
642
642
  end
643
+ Statement::Expression.new(code, is_assignment_expression)
643
644
  end
644
645
 
645
646
  def colorize_input(input, complete:)
@@ -648,7 +649,7 @@ module IRB
648
649
  parsed_input = parse_input(input, false)
649
650
  if parsed_input.is_a?(Statement::Command)
650
651
  name, sep, arg = input.split(/(\s+)/, 2)
651
- arg = IRB::Color.colorize_code(arg, complete: complete, local_variables: lvars)
652
+ arg = IRB::Color.colorize_code(arg, complete: complete, local_variables: lvars) if arg
652
653
  "#{IRB::Color.colorize(name, [:BOLD])}\e[m#{sep}#{arg}"
653
654
  else
654
655
  IRB::Color.colorize_code(input, complete: complete, local_variables: lvars)
data/lib/irb/debug.rb CHANGED
@@ -49,7 +49,7 @@ module IRB
49
49
  def DEBUGGER__.capture_frames(*args)
50
50
  frames = capture_frames_without_irb(*args)
51
51
  frames.reject! do |frame|
52
- frame.realpath&.start_with?(IRB_DIR) || frame.path == "<internal:prelude>"
52
+ frame.realpath&.start_with?(IRB_DIR) || frame.path&.start_with?("<internal:")
53
53
  end
54
54
  frames
55
55
  end
@@ -60,7 +60,7 @@ module IRB
60
60
  if !DEBUGGER__::CONFIG[:no_hint] && irb.context.io.is_a?(RelineInputMethod)
61
61
  Reline.output_modifier_proc = proc do |input, complete:|
62
62
  unless input.strip.empty?
63
- cmd = input.split(/\s/, 2).first
63
+ cmd = input[/\S+/]
64
64
 
65
65
  if !complete && DEBUGGER__.commands.key?(cmd)
66
66
  input = input.sub(/\n$/, " # debug command\n")
@@ -87,7 +87,7 @@ module IRB
87
87
  module SkipPathHelperForIRB
88
88
  def skip_internal_path?(path)
89
89
  # The latter can be removed once https://github.com/ruby/debug/issues/866 is resolved
90
- super || path.match?(IRB_DIR) || path.match?('<internal:prelude>')
90
+ super || path&.match?(IRB_DIR) || path&.match?('<internal:prelude>')
91
91
  end
92
92
  end
93
93
 
@@ -121,12 +121,13 @@ module IRB
121
121
  interrupted = false
122
122
  prev_trap = trap("SIGINT") { interrupted = true }
123
123
  canvas = Canvas.new(Reline.get_screen_size)
124
+ otio = Reline::IOGate.prep
124
125
  Reline::IOGate.set_winch_handler do
125
126
  canvas = Canvas.new(Reline.get_screen_size)
126
127
  end
127
128
  ruby_model = RubyModel.new
128
129
  print "\e[?25l" # hide cursor
129
- 0.step do |i| # TODO (0..).each needs Ruby 2.6 or later
130
+ (0..).each do |i|
130
131
  buff = canvas.draw do
131
132
  ruby_model.render_frame(i) do |p1, p2|
132
133
  canvas.line(p1, p2)
@@ -139,6 +140,7 @@ module IRB
139
140
  end
140
141
  rescue Interrupt
141
142
  ensure
143
+ Reline::IOGate.deprep(otio)
142
144
  print "\e[?25h" # show cursor
143
145
  trap("SIGINT", prev_trap)
144
146
  end