irb 1.4.2 → 1.6.1

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.
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "debug"
4
+
5
+ module IRB
6
+ # :stopdoc:
7
+
8
+ module ExtendCommand
9
+ class Next < DebugCommand
10
+ def execute(*args)
11
+ super(do_cmds: ["next", *args].join(" "))
12
+ end
13
+ end
14
+ end
15
+
16
+ # :startdoc:
17
+ end
data/lib/irb/cmd/nop.rb CHANGED
@@ -13,17 +13,41 @@ module IRB
13
13
  # :stopdoc:
14
14
 
15
15
  module ExtendCommand
16
+ class CommandArgumentError < StandardError; end
17
+
16
18
  class Nop
19
+ class << self
20
+ def category(category = nil)
21
+ @category = category if category
22
+ @category
23
+ end
24
+
25
+ def description(description = nil)
26
+ @description = description if description
27
+ @description
28
+ end
29
+
30
+ private
31
+
32
+ def string_literal?(args)
33
+ sexp = Ripper.sexp(args)
34
+ sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal
35
+ end
36
+ end
17
37
 
18
38
  if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.7.0"
19
39
  def self.execute(conf, *opts, **kwargs, &block)
20
40
  command = new(conf)
21
41
  command.execute(*opts, **kwargs, &block)
42
+ rescue CommandArgumentError => e
43
+ puts e.message
22
44
  end
23
45
  else
24
46
  def self.execute(conf, *opts, &block)
25
47
  command = new(conf)
26
48
  command.execute(*opts, &block)
49
+ rescue CommandArgumentError => e
50
+ puts e.message
27
51
  end
28
52
  end
29
53
 
@@ -18,12 +18,18 @@ module IRB
18
18
 
19
19
  module ExtendCommand
20
20
  class Workspaces < Nop
21
+ category "IRB"
22
+ description "Show workspaces."
23
+
21
24
  def execute(*obj)
22
25
  irb_context.workspaces.collect{|ws| ws.main}
23
26
  end
24
27
  end
25
28
 
26
29
  class PushWorkspace < Workspaces
30
+ category "IRB"
31
+ description "Push an object to the workspace stack."
32
+
27
33
  def execute(*obj)
28
34
  irb_context.push_workspace(*obj)
29
35
  super
@@ -31,6 +37,9 @@ module IRB
31
37
  end
32
38
 
33
39
  class PopWorkspace < Workspaces
40
+ category "IRB"
41
+ description "Pop a workspace from the workspace stack."
42
+
34
43
  def execute(*obj)
35
44
  irb_context.pop_workspace(*obj)
36
45
  super
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+ require_relative "nop"
5
+
6
+ module IRB
7
+ # :stopdoc:
8
+
9
+ module ExtendCommand
10
+ class ShowCmds < Nop
11
+ category "IRB"
12
+ description "List all available commands and their description."
13
+
14
+ def execute(*args)
15
+ commands_info = IRB::ExtendCommandBundle.all_commands_info
16
+ commands_grouped_by_categories = commands_info.group_by { |cmd| cmd[:category] }
17
+ longest_cmd_name_length = commands_info.map { |c| c[:display_name] }.max { |a, b| a.length <=> b.length }.length
18
+
19
+ output = StringIO.new
20
+
21
+ commands_grouped_by_categories.each do |category, cmds|
22
+ output.puts Color.colorize(category, [:BOLD])
23
+
24
+ cmds.each do |cmd|
25
+ output.puts " #{cmd[:display_name].to_s.ljust(longest_cmd_name_length)} #{cmd[:description]}"
26
+ end
27
+
28
+ output.puts
29
+ end
30
+
31
+ puts output.string
32
+
33
+ nil
34
+ end
35
+ end
36
+ end
37
+
38
+ # :startdoc:
39
+ end
@@ -9,12 +9,71 @@ module IRB
9
9
 
10
10
  module ExtendCommand
11
11
  class ShowSource < Nop
12
+ category "Context"
13
+ description "Show the source code of a given method or constant."
14
+
15
+ class << self
16
+ def transform_args(args)
17
+ # Return a string literal as is for backward compatibility
18
+ if args.empty? || string_literal?(args)
19
+ args
20
+ else # Otherwise, consider the input as a String for convenience
21
+ args.strip.dump
22
+ end
23
+ end
24
+
25
+ def find_source(str, irb_context)
26
+ case str
27
+ when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name
28
+ eval(str, irb_context.workspace.binding) # trigger autoload
29
+ base = irb_context.workspace.binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object }
30
+ file, line = base.const_source_location(str) if base.respond_to?(:const_source_location) # Ruby 2.7+
31
+ when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method
32
+ owner = eval(Regexp.last_match[:owner], irb_context.workspace.binding)
33
+ method = Regexp.last_match[:method]
34
+ if owner.respond_to?(:instance_method) && owner.instance_methods.include?(method.to_sym)
35
+ file, line = owner.instance_method(method).source_location
36
+ end
37
+ when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method
38
+ receiver = eval(Regexp.last_match[:receiver] || 'self', irb_context.workspace.binding)
39
+ method = Regexp.last_match[:method]
40
+ file, line = receiver.method(method).source_location if receiver.respond_to?(method)
41
+ end
42
+ if file && line
43
+ Source.new(file: file, first_line: line, last_line: find_end(file, line))
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def find_end(file, first_line)
50
+ return first_line unless File.exist?(file)
51
+ lex = RubyLex.new
52
+ lines = File.read(file).lines[(first_line - 1)..-1]
53
+ tokens = RubyLex.ripper_lex_without_warning(lines.join)
54
+ prev_tokens = []
55
+
56
+ # chunk with line number
57
+ tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk|
58
+ code = lines[0..lnum].join
59
+ prev_tokens.concat chunk
60
+ continue = lex.process_continue(prev_tokens)
61
+ code_block_open = lex.check_code_block(code, prev_tokens)
62
+ if !continue && !code_block_open
63
+ return first_line + lnum
64
+ end
65
+ end
66
+ first_line
67
+ end
68
+ end
69
+
12
70
  def execute(str = nil)
13
71
  unless str.is_a?(String)
14
72
  puts "Error: Expected a string but got #{str.inspect}"
15
73
  return
16
74
  end
17
- source = find_source(str)
75
+
76
+ source = self.class.find_source(str, @irb_context)
18
77
  if source && File.exist?(source.file)
19
78
  show_source(source)
20
79
  else
@@ -35,48 +94,6 @@ module IRB
35
94
  puts
36
95
  end
37
96
 
38
- def find_source(str)
39
- case str
40
- when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name
41
- eval(str, irb_context.workspace.binding) # trigger autoload
42
- base = irb_context.workspace.binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object }
43
- file, line = base.const_source_location(str) if base.respond_to?(:const_source_location) # Ruby 2.7+
44
- when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method
45
- owner = eval(Regexp.last_match[:owner], irb_context.workspace.binding)
46
- method = Regexp.last_match[:method]
47
- if owner.respond_to?(:instance_method) && owner.instance_methods.include?(method.to_sym)
48
- file, line = owner.instance_method(method).source_location
49
- end
50
- when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method
51
- receiver = eval(Regexp.last_match[:receiver] || 'self', irb_context.workspace.binding)
52
- method = Regexp.last_match[:method]
53
- file, line = receiver.method(method).source_location if receiver.respond_to?(method)
54
- end
55
- if file && line
56
- Source.new(file: file, first_line: line, last_line: find_end(file, line))
57
- end
58
- end
59
-
60
- def find_end(file, first_line)
61
- return first_line unless File.exist?(file)
62
- lex = RubyLex.new
63
- lines = File.read(file).lines[(first_line - 1)..-1]
64
- tokens = RubyLex.ripper_lex_without_warning(lines.join)
65
- prev_tokens = []
66
-
67
- # chunk with line number
68
- tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk|
69
- code = lines[0..lnum].join
70
- prev_tokens.concat chunk
71
- continue = lex.process_continue(prev_tokens)
72
- code_block_open = lex.check_code_block(code, prev_tokens)
73
- if !continue && !code_block_open
74
- return first_line + lnum
75
- end
76
- end
77
- first_line
78
- end
79
-
80
97
  def bold(str)
81
98
  Color.colorize(str, [:BOLD])
82
99
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "debug"
4
+
5
+ module IRB
6
+ # :stopdoc:
7
+
8
+ module ExtendCommand
9
+ class Step < DebugCommand
10
+ def execute(*args)
11
+ super(do_cmds: ["step", *args].join(" "))
12
+ end
13
+ end
14
+ end
15
+
16
+ # :startdoc:
17
+ end
@@ -10,31 +10,57 @@
10
10
  #
11
11
 
12
12
  require_relative "nop"
13
- require_relative "../ext/multi-irb"
14
13
 
15
14
  module IRB
16
15
  # :stopdoc:
17
16
 
18
17
  module ExtendCommand
19
- class IrbCommand < Nop
18
+ class MultiIRBCommand < Nop
19
+ def initialize(conf)
20
+ super
21
+ extend_irb_context
22
+ end
23
+
24
+ private
25
+
26
+ def extend_irb_context
27
+ # this extension patches IRB context like IRB.CurrentContext
28
+ require_relative "../ext/multi-irb"
29
+ end
30
+ end
31
+
32
+ class IrbCommand < MultiIRBCommand
33
+ category "IRB"
34
+ description "Start a child IRB."
35
+
20
36
  def execute(*obj)
21
37
  IRB.irb(nil, *obj)
22
38
  end
23
39
  end
24
40
 
25
- class Jobs < Nop
41
+ class Jobs < MultiIRBCommand
42
+ category "IRB"
43
+ description "List of current sessions."
44
+
26
45
  def execute
27
46
  IRB.JobManager
28
47
  end
29
48
  end
30
49
 
31
- class Foreground < Nop
32
- def execute(key)
50
+ class Foreground < MultiIRBCommand
51
+ category "IRB"
52
+ description "Switches to the session of the given number."
53
+
54
+ def execute(key = nil)
55
+ raise CommandArgumentError.new("Please specify the id of target IRB job (listed in the `jobs` command).") unless key
33
56
  IRB.JobManager.switch(key)
34
57
  end
35
58
  end
36
59
 
37
- class Kill < Nop
60
+ class Kill < MultiIRBCommand
61
+ category "IRB"
62
+ description "Kills the session with the given number."
63
+
38
64
  def execute(*keys)
39
65
  IRB.JobManager.kill(*keys)
40
66
  end
@@ -7,6 +7,9 @@ module IRB
7
7
 
8
8
  module ExtendCommand
9
9
  class Whereami < Nop
10
+ category "Context"
11
+ description "Show the source code around binding.irb again."
12
+
10
13
  def execute(*)
11
14
  code = irb_context.workspace.code_around_binding
12
15
  if code
data/lib/irb/color.rb CHANGED
@@ -123,13 +123,15 @@ module IRB # :nodoc:
123
123
  # If `complete` is false (code is incomplete), this does not warn compile_error.
124
124
  # This option is needed to avoid warning a user when the compile_error is happening
125
125
  # because the input is not wrong but just incomplete.
126
- def colorize_code(code, complete: true, ignore_error: false, colorable: colorable?)
126
+ def colorize_code(code, complete: true, ignore_error: false, colorable: colorable?, local_variables: [])
127
127
  return code unless colorable
128
128
 
129
129
  symbol_state = SymbolState.new
130
130
  colored = +''
131
+ lvars_code = RubyLex.generate_local_variables_assign_code(local_variables)
132
+ code_with_lvars = lvars_code ? "#{lvars_code}\n#{code}" : code
131
133
 
132
- scan(code, allow_last_error: !complete) do |token, str, expr|
134
+ scan(code_with_lvars, allow_last_error: !complete) do |token, str, expr|
133
135
  # handle uncolorable code
134
136
  if token.nil?
135
137
  colored << Reline::Unicode.escape_for_print(str)
@@ -152,6 +154,11 @@ module IRB # :nodoc:
152
154
  end
153
155
  end
154
156
  end
157
+
158
+ if lvars_code
159
+ raise "#{lvars_code.dump} should have no \\n" if lvars_code.include?("\n")
160
+ colored.sub!(/\A.+\n/, '') # delete_prefix lvars_code with colors
161
+ end
155
162
  colored
156
163
  end
157
164
 
@@ -64,25 +64,27 @@ module IRB
64
64
  if File.respond_to?(:absolute_path?)
65
65
  File.absolute_path?(p)
66
66
  else
67
- if File.absolute_path(p) == p
68
- true
69
- else
70
- false
71
- end
67
+ File.absolute_path(p) == p
72
68
  end
73
69
  end
74
70
 
71
+ GEM_PATHS =
72
+ if defined?(Gem::Specification)
73
+ Gem::Specification.latest_specs(true).map { |s|
74
+ s.require_paths.map { |p|
75
+ if absolute_path?(p)
76
+ p
77
+ else
78
+ File.join(s.full_gem_path, p)
79
+ end
80
+ }
81
+ }.flatten
82
+ else
83
+ []
84
+ end.freeze
85
+
75
86
  def self.retrieve_gem_and_system_load_path
76
- gem_paths = Gem::Specification.latest_specs(true).map { |s|
77
- s.require_paths.map { |p|
78
- if absolute_path?(p)
79
- p
80
- else
81
- File.join(s.full_gem_path, p)
82
- end
83
- }
84
- }.flatten if defined?(Gem::Specification)
85
- candidates = (gem_paths.to_a | $LOAD_PATH)
87
+ candidates = (GEM_PATHS | $LOAD_PATH)
86
88
  candidates.map do |p|
87
89
  if p.respond_to?(:to_path)
88
90
  p.to_path
@@ -171,10 +173,10 @@ module IRB
171
173
  receiver = $1
172
174
  message = $3
173
175
 
174
- candidates = String.instance_methods.collect{|m| m.to_s}
175
176
  if doc_namespace
176
177
  "String.#{message}"
177
178
  else
179
+ candidates = String.instance_methods.collect{|m| m.to_s}
178
180
  select_message(receiver, message, candidates)
179
181
  end
180
182
 
@@ -183,10 +185,10 @@ module IRB
183
185
  receiver = $1
184
186
  message = $2
185
187
 
186
- candidates = Regexp.instance_methods.collect{|m| m.to_s}
187
188
  if doc_namespace
188
189
  "Regexp.#{message}"
189
190
  else
191
+ candidates = Regexp.instance_methods.collect{|m| m.to_s}
190
192
  select_message(receiver, message, candidates)
191
193
  end
192
194
 
@@ -195,10 +197,10 @@ module IRB
195
197
  receiver = $1
196
198
  message = $2
197
199
 
198
- candidates = Array.instance_methods.collect{|m| m.to_s}
199
200
  if doc_namespace
200
201
  "Array.#{message}"
201
202
  else
203
+ candidates = Array.instance_methods.collect{|m| m.to_s}
202
204
  select_message(receiver, message, candidates)
203
205
  end
204
206
 
@@ -207,29 +209,33 @@ module IRB
207
209
  receiver = $1
208
210
  message = $2
209
211
 
210
- proc_candidates = Proc.instance_methods.collect{|m| m.to_s}
211
- hash_candidates = Hash.instance_methods.collect{|m| m.to_s}
212
212
  if doc_namespace
213
213
  ["Proc.#{message}", "Hash.#{message}"]
214
214
  else
215
+ proc_candidates = Proc.instance_methods.collect{|m| m.to_s}
216
+ hash_candidates = Hash.instance_methods.collect{|m| m.to_s}
215
217
  select_message(receiver, message, proc_candidates | hash_candidates)
216
218
  end
217
219
 
218
220
  when /^(:[^:.]*)$/
219
221
  # Symbol
220
- return nil if doc_namespace
221
- sym = $1
222
- candidates = Symbol.all_symbols.collect do |s|
223
- ":" + s.id2name.encode(Encoding.default_external)
224
- rescue EncodingError
225
- # ignore
222
+ if doc_namespace
223
+ nil
224
+ else
225
+ sym = $1
226
+ candidates = Symbol.all_symbols.collect do |s|
227
+ ":" + s.id2name.encode(Encoding.default_external)
228
+ rescue EncodingError
229
+ # ignore
230
+ end
231
+ candidates.grep(/^#{Regexp.quote(sym)}/)
226
232
  end
227
- candidates.grep(/^#{Regexp.quote(sym)}/)
228
-
229
233
  when /^::([A-Z][^:\.\(\)]*)$/
230
234
  # Absolute Constant or class methods
231
235
  receiver = $1
236
+
232
237
  candidates = Object.constants.collect{|m| m.to_s}
238
+
233
239
  if doc_namespace
234
240
  candidates.find { |i| i == receiver }
235
241
  else
@@ -240,16 +246,18 @@ module IRB
240
246
  # Constant or class methods
241
247
  receiver = $1
242
248
  message = $2
243
- begin
244
- candidates = eval("#{receiver}.constants.collect{|m| m.to_s}", bind)
245
- candidates |= eval("#{receiver}.methods.collect{|m| m.to_s}", bind)
246
- rescue Exception
247
- candidates = []
248
- end
249
+
249
250
  if doc_namespace
250
251
  "#{receiver}::#{message}"
251
252
  else
252
- select_message(receiver, message, candidates, "::")
253
+ begin
254
+ candidates = eval("#{receiver}.constants.collect{|m| m.to_s}", bind)
255
+ candidates |= eval("#{receiver}.methods.collect{|m| m.to_s}", bind)
256
+ rescue Exception
257
+ candidates = []
258
+ end
259
+
260
+ select_message(receiver, message, candidates.sort, "::")
253
261
  end
254
262
 
255
263
  when /^(:[^:.]+)(\.|::)([^.]*)$/
@@ -258,10 +266,10 @@ module IRB
258
266
  sep = $2
259
267
  message = $3
260
268
 
261
- candidates = Symbol.instance_methods.collect{|m| m.to_s}
262
269
  if doc_namespace
263
270
  "Symbol.#{message}"
264
271
  else
272
+ candidates = Symbol.instance_methods.collect{|m| m.to_s}
265
273
  select_message(receiver, message, candidates, sep)
266
274
  end
267
275
 
@@ -273,6 +281,7 @@ module IRB
273
281
 
274
282
  begin
275
283
  instance = eval(receiver, bind)
284
+
276
285
  if doc_namespace
277
286
  "#{instance.class.name}.#{message}"
278
287
  else
@@ -283,7 +292,7 @@ module IRB
283
292
  if doc_namespace
284
293
  nil
285
294
  else
286
- candidates = []
295
+ []
287
296
  end
288
297
  end
289
298
 
@@ -305,7 +314,7 @@ module IRB
305
314
  if doc_namespace
306
315
  nil
307
316
  else
308
- candidates = []
317
+ []
309
318
  end
310
319
  end
311
320
 
@@ -313,6 +322,7 @@ module IRB
313
322
  # global var
314
323
  gvar = $1
315
324
  all_gvars = global_variables.collect{|m| m.to_s}
325
+
316
326
  if doc_namespace
317
327
  all_gvars.find{ |i| i == gvar }
318
328
  else
@@ -351,11 +361,13 @@ module IRB
351
361
  to_ignore = ignored_modules
352
362
  ObjectSpace.each_object(Module){|m|
353
363
  next if (to_ignore.include?(m) rescue true)
364
+ next unless m.respond_to?(:instance_methods) # JRuby has modules that represent java packages. They don't include many common ruby methods
354
365
  candidates.concat m.instance_methods(false).collect{|x| x.to_s}
355
366
  }
356
367
  candidates.sort!
357
368
  candidates.uniq!
358
369
  end
370
+
359
371
  if doc_namespace
360
372
  rec_class = rec.is_a?(Module) ? rec : rec.class
361
373
  "#{rec_class.name}#{sep}#{candidates.find{ |i| i == message }}"
@@ -370,10 +382,11 @@ module IRB
370
382
  message = $1
371
383
 
372
384
  candidates = String.instance_methods(true).collect{|m| m.to_s}
385
+
373
386
  if doc_namespace
374
387
  "String.#{candidates.find{ |i| i == message }}"
375
388
  else
376
- select_message(receiver, message, candidates)
389
+ select_message(receiver, message, candidates.sort)
377
390
  end
378
391
 
379
392
  else
@@ -390,7 +403,7 @@ module IRB
390
403
  else
391
404
  candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s}
392
405
  candidates |= ReservedWords
393
- candidates.grep(/^#{Regexp.quote(input)}/)
406
+ candidates.grep(/^#{Regexp.quote(input)}/).sort
394
407
  end
395
408
  end
396
409
  end
data/lib/irb/context.rb CHANGED
@@ -49,12 +49,15 @@ module IRB
49
49
  if IRB.conf.has_key?(:USE_MULTILINE)
50
50
  @use_multiline = IRB.conf[:USE_MULTILINE]
51
51
  elsif IRB.conf.has_key?(:USE_RELINE) # backward compatibility
52
+ warn <<~MSG.strip
53
+ USE_RELINE is deprecated, please use USE_MULTILINE instead.
54
+ MSG
52
55
  @use_multiline = IRB.conf[:USE_RELINE]
53
56
  elsif IRB.conf.has_key?(:USE_REIDLINE)
54
57
  warn <<~MSG.strip
55
- USE_REIDLINE is deprecated, please use USE_RELINE instead.
58
+ USE_REIDLINE is deprecated, please use USE_MULTILINE instead.
56
59
  MSG
57
- @use_multiline = IRB.conf[:USE_RELINE]
60
+ @use_multiline = IRB.conf[:USE_REIDLINE]
58
61
  else
59
62
  @use_multiline = nil
60
63
  end
@@ -149,6 +152,8 @@ module IRB
149
152
  if @newline_before_multiline_output.nil?
150
153
  @newline_before_multiline_output = true
151
154
  end
155
+
156
+ @command_aliases = IRB.conf[:COMMAND_ALIASES]
152
157
  end
153
158
 
154
159
  # The top-level workspace, see WorkSpace#main
@@ -326,6 +331,9 @@ module IRB
326
331
  # See IRB@Command+line+options for more command line options.
327
332
  attr_accessor :back_trace_limit
328
333
 
334
+ # User-defined IRB command aliases
335
+ attr_accessor :command_aliases
336
+
329
337
  # Alias for #use_multiline
330
338
  alias use_multiline? use_multiline
331
339
  # Alias for #use_singleline
@@ -477,6 +485,20 @@ module IRB
477
485
  line = "begin ::Kernel.raise _; rescue _.class\n#{line}\n""end"
478
486
  @workspace.local_variable_set(:_, exception)
479
487
  end
488
+
489
+ # Transform a non-identifier alias (@, $) or keywords (next, break)
490
+ command, args = line.split(/\s/, 2)
491
+ if original = command_aliases[command.to_sym]
492
+ line = line.gsub(/\A#{Regexp.escape(command)}/, original.to_s)
493
+ command = original
494
+ end
495
+
496
+ # Hook command-specific transformation
497
+ command_class = ExtendCommandBundle.load_command(command)
498
+ if command_class&.respond_to?(:transform_args)
499
+ line = "#{command} #{command_class.transform_args(args)}"
500
+ end
501
+
480
502
  set_last_value(@workspace.evaluate(self, line, irb_path, line_no))
481
503
  end
482
504
 
@@ -518,5 +540,21 @@ module IRB
518
540
  end
519
541
  alias __to_s__ to_s
520
542
  alias to_s inspect
543
+
544
+ def local_variables # :nodoc:
545
+ workspace.binding.local_variables
546
+ end
547
+
548
+ # Return true if it's aliased from the argument and it's not an identifier.
549
+ def symbol_alias?(command)
550
+ return nil if command.match?(/\A\w+\z/)
551
+ command_aliases.key?(command.to_sym)
552
+ end
553
+
554
+ # Return true if the command supports transforming args
555
+ def transform_args?(command)
556
+ command = command_aliases.fetch(command.to_sym, command)
557
+ ExtendCommandBundle.load_command(command)&.respond_to?(:transform_args)
558
+ end
521
559
  end
522
560
  end
@@ -70,10 +70,10 @@ module IRB
70
70
  end
71
71
  history_file = IRB.rc_file("_history") unless history_file
72
72
  if File.exist?(history_file)
73
- open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f|
73
+ File.open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f|
74
74
  f.each { |l|
75
75
  l = l.chomp
76
- if self.class == ReidlineInputMethod and history.last&.end_with?("\\")
76
+ if self.class == RelineInputMethod and history.last&.end_with?("\\")
77
77
  history.last.delete_suffix!("\\")
78
78
  history.last << "\n" << l
79
79
  else