irb 1.4.2 → 1.5.0

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: 25f63261bbf06e8b1605b8a680e538dd90641ad2492c0bfb0f8e127f673e2793
4
- data.tar.gz: 9f113f6c97887e83f9d476dba5e4c0a5656d76cc554e1e818c94e8828b4051fa
3
+ metadata.gz: 33ff0b70fb3730f087bc24a7a8b63253ba1a4ad0416a0ffcd1e5395f26e5d0b2
4
+ data.tar.gz: 9cd418e90733c4cb4658368b2a899b50490ad9dbd7352a1c1df0070c10d9669d
5
5
  SHA512:
6
- metadata.gz: 024d8e4dc13978bbae5ed3404e3ecff763da3fcc01e26c43d3e7e54822121bbc708de49c5a9c951a15c0ceee05a60cf282cbbd6ae6ec42960e9a8943a9217417
7
- data.tar.gz: 146edac46f0d81a01e120493e56eefa8d8b51143851718876617673bcbddf19b54e246d55541833101926e17186ce55b6484e2fb20e6f57735ee49ddfe0b6890
6
+ metadata.gz: ec6102a41d55404aa8e5585243cbe74fcdbe1e1d5bcd88ddae4240adff4281d14200b99c621324515a4e226eff5964644a7e8456d707af2ca7dcf8e5211a0b4b
7
+ data.tar.gz: 0ed81d78ce49803706b36f36e173acb1e27f3e217bd3c6e00e46d4875b6be3c7958a2cb3db146703c3cbf7198094561a562e0fd244f6a61501f9ae5196df343a
data/README.md CHANGED
@@ -40,6 +40,57 @@ irb(main):006:1> end
40
40
 
41
41
  The Readline extension module can be used with irb. Use of Readline is default if it's installed.
42
42
 
43
+ ## Commands
44
+
45
+ The following commands are available on IRB.
46
+
47
+ * `cwws`
48
+ * Show the current workspace.
49
+ * `cb`, `cws`, `chws`
50
+ * Change the current workspace to an object.
51
+ * `bindings`, `workspaces`
52
+ * Show workspaces.
53
+ * `edit`
54
+ * Open a file with the editor command defined with `ENV["EDITOR"]`
55
+ * `edit` - opens the file the current context belongs to (if applicable)
56
+ * `edit foo.rb` - opens `foo.rb`
57
+ * `edit Foo` - opens the location of `Foo`
58
+ * `edit Foo.bar` - opens the location of `Foo.bar`
59
+ * `edit Foo#bar` - opens the location of `Foo#bar`
60
+ * `pushb`, `pushws`
61
+ * Push an object to the workspace stack.
62
+ * `popb`, `popws`
63
+ * Pop a workspace from the workspace stack.
64
+ * `load`
65
+ * Load a Ruby file.
66
+ * `require`
67
+ * Require a Ruby file.
68
+ * `source`
69
+ * Loads a given file in the current session.
70
+ * `irb`
71
+ * Start a child IRB.
72
+ * `jobs`
73
+ * List of current sessions.
74
+ * `fg`
75
+ * Switches to the session of the given number.
76
+ * `kill`
77
+ * Kills the session with the given number.
78
+ * `help`
79
+ * Enter the mode to look up RI documents.
80
+ * `irb_info`
81
+ * Show information about IRB.
82
+ * `ls`
83
+ * Show methods, constants, and variables.
84
+ `-g [query]` or `-G [query]` allows you to filter out the output.
85
+ * `measure`
86
+ * `measure` enables the mode to measure processing time. `measure :off` disables it.
87
+ * `$`, `show_source`
88
+ * Show the source code of a given method or constant.
89
+ * `@`, `whereami`
90
+ * Show the source code around binding.irb again.
91
+ * `debug`
92
+ * Start the debugger of debug.gem.
93
+
43
94
  ## Documentation
44
95
 
45
96
  https://docs.ruby-lang.org/en/master/IRB.html
data/Rakefile CHANGED
@@ -8,6 +8,31 @@ Rake::TestTask.new(:test) do |t|
8
8
  t.test_files = FileList["test/irb/test_*.rb"]
9
9
  end
10
10
 
11
+ # To make sure they have been correctly setup for Ruby CI.
12
+ desc "Run each irb test file in isolation."
13
+ task :test_in_isolation do
14
+ failed = false
15
+
16
+ FileList["test/irb/test_*.rb"].each do |test_file|
17
+ ENV["TEST"] = test_file
18
+ begin
19
+ Rake::Task["test"].execute
20
+ rescue => e
21
+ failed = true
22
+ msg = "Test '#{test_file}' failed when being executed in isolation. Please make sure 'rake test TEST=#{test_file}' passes."
23
+ separation_line = '=' * msg.length
24
+
25
+ puts <<~MSG
26
+ #{separation_line}
27
+ #{msg}
28
+ #{separation_line}
29
+ MSG
30
+ end
31
+ end
32
+
33
+ fail "Some tests failed when being executed in isolation" if failed
34
+ end
35
+
11
36
  Rake::TestTask.new(:test_yamatanooroti) do |t|
12
37
  t.libs << 'test' << "test/lib"
13
38
  t.libs << 'lib'
data/irb.gemspec CHANGED
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
34
34
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
35
35
  spec.require_paths = ["lib"]
36
36
 
37
- spec.required_ruby_version = Gem::Requirement.new(">= 2.5")
37
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.6")
38
38
 
39
39
  spec.add_dependency "reline", ">= 0.3.0"
40
40
  end
@@ -0,0 +1,96 @@
1
+ require_relative "nop"
2
+
3
+ module IRB
4
+ # :stopdoc:
5
+
6
+ module ExtendCommand
7
+ class Debug < Nop
8
+ BINDING_IRB_FRAME_REGEXPS = [
9
+ '<internal:prelude>',
10
+ binding.method(:irb).source_location.first,
11
+ ].map { |file| /\A#{Regexp.escape(file)}:\d+:in `irb'\z/ }
12
+ IRB_DIR = File.expand_path('..', __dir__)
13
+
14
+ def execute(*args)
15
+ unless binding_irb?
16
+ puts "`debug` command is only available when IRB is started with binding.irb"
17
+ return
18
+ end
19
+
20
+ unless setup_debugger
21
+ puts <<~MSG
22
+ You need to install the debug gem before using this command.
23
+ If you use `bundle exec`, please add `gem "debug"` into your Gemfile.
24
+ MSG
25
+ return
26
+ end
27
+
28
+ # To make debugger commands like `next` or `continue` work without asking
29
+ # the user to quit IRB after that, we need to exit IRB first and then hit
30
+ # a TracePoint on #debug_break.
31
+ file, lineno = IRB::Irb.instance_method(:debug_break).source_location
32
+ DEBUGGER__::SESSION.add_line_breakpoint(file, lineno + 1, oneshot: true, hook_call: false)
33
+ # exit current Irb#run call
34
+ throw :IRB_EXIT
35
+ end
36
+
37
+ private
38
+
39
+ def binding_irb?
40
+ caller.any? do |frame|
41
+ BINDING_IRB_FRAME_REGEXPS.any? do |regexp|
42
+ frame.match?(regexp)
43
+ end
44
+ end
45
+ end
46
+
47
+ def setup_debugger
48
+ unless defined?(DEBUGGER__::SESSION)
49
+ begin
50
+ require "debug/session"
51
+ rescue LoadError # debug.gem is not written in Gemfile
52
+ return false unless load_bundled_debug_gem
53
+ end
54
+ DEBUGGER__.start(nonstop: true)
55
+ end
56
+
57
+ unless DEBUGGER__.respond_to?(:capture_frames_without_irb)
58
+ DEBUGGER__.singleton_class.send(:alias_method, :capture_frames_without_irb, :capture_frames)
59
+
60
+ def DEBUGGER__.capture_frames(*args)
61
+ frames = capture_frames_without_irb(*args)
62
+ frames.reject! do |frame|
63
+ frame.realpath&.start_with?(IRB_DIR) || frame.path == "<internal:prelude>"
64
+ end
65
+ frames
66
+ end
67
+ end
68
+
69
+ true
70
+ end
71
+
72
+ # This is used when debug.gem is not written in Gemfile. Even if it's not
73
+ # installed by `bundle install`, debug.gem is installed by default because
74
+ # it's a bundled gem. This method tries to activate and load that.
75
+ def load_bundled_debug_gem
76
+ # Discover latest debug.gem under GEM_PATH
77
+ debug_gem = Gem.paths.path.map { |path| Dir.glob("#{path}/gems/debug-*") }.flatten.select do |path|
78
+ File.basename(path).match?(/\Adebug-\d+\.\d+\.\d+\z/)
79
+ end.sort_by do |path|
80
+ Gem::Version.new(File.basename(path).delete_prefix('debug-'))
81
+ end.last
82
+ return false unless debug_gem
83
+
84
+ # Attempt to forcibly load the bundled gem
85
+ $LOAD_PATH << "#{debug_gem}/lib"
86
+ begin
87
+ require "debug/session"
88
+ puts "Loaded #{File.basename(debug_gem)}"
89
+ true
90
+ rescue LoadError
91
+ false
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,65 @@
1
+ require 'shellwords'
2
+ require_relative "nop"
3
+
4
+ module IRB
5
+ # :stopdoc:
6
+
7
+ module ExtendCommand
8
+ class Edit < Nop
9
+ class << self
10
+ def transform_args(args)
11
+ # Return a string literal as is for backward compatibility
12
+ if args.nil? || args.empty? || string_literal?(args)
13
+ args
14
+ else # Otherwise, consider the input as a String for convenience
15
+ args.strip.dump
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def string_literal?(args)
22
+ sexp = Ripper.sexp(args)
23
+ sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal
24
+ end
25
+ end
26
+
27
+ def execute(*args)
28
+ path = args.first
29
+
30
+ if path.nil? && (irb_path = @irb_context.irb_path)
31
+ path = irb_path
32
+ end
33
+
34
+ if !File.exist?(path)
35
+ require_relative "show_source"
36
+
37
+ source =
38
+ begin
39
+ ShowSource.find_source(path, @irb_context)
40
+ rescue NameError
41
+ # if user enters a path that doesn't exist, it'll cause NameError when passed here because find_source would try to evaluate it as well
42
+ # in this case, we should just ignore the error
43
+ end
44
+
45
+ if source && File.exist?(source.file)
46
+ path = source.file
47
+ else
48
+ puts "Can not find file: #{path}"
49
+ return
50
+ end
51
+ end
52
+
53
+ if editor = ENV['EDITOR']
54
+ puts "command: '#{editor}'"
55
+ puts " path: #{path}"
56
+ system(*Shellwords.split(editor), path)
57
+ else
58
+ puts "Can not find editor setting: ENV['EDITOR']"
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ # :startdoc:
65
+ end
data/lib/irb/cmd/ls.rb CHANGED
@@ -9,6 +9,15 @@ module IRB
9
9
 
10
10
  module ExtendCommand
11
11
  class Ls < Nop
12
+ def self.transform_args(args)
13
+ if match = args&.match(/\A(?<args>.+\s|)(-g|-G)\s+(?<grep>[^\s]+)\s*\n\z/)
14
+ args = match[:args]
15
+ "#{args}#{',' unless args.chomp.empty?} grep: /#{match[:grep]}/"
16
+ else
17
+ args
18
+ end
19
+ end
20
+
12
21
  def execute(*arg, grep: nil)
13
22
  o = Output.new(grep: grep)
14
23
 
@@ -9,12 +9,73 @@ module IRB
9
9
 
10
10
  module ExtendCommand
11
11
  class ShowSource < Nop
12
+ class << self
13
+ def transform_args(args)
14
+ # Return a string literal as is for backward compatibility
15
+ if args.empty? || string_literal?(args)
16
+ args
17
+ else # Otherwise, consider the input as a String for convenience
18
+ args.strip.dump
19
+ end
20
+ end
21
+
22
+ def find_source(str, irb_context)
23
+ case str
24
+ when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name
25
+ eval(str, irb_context.workspace.binding) # trigger autoload
26
+ base = irb_context.workspace.binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object }
27
+ file, line = base.const_source_location(str) if base.respond_to?(:const_source_location) # Ruby 2.7+
28
+ when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method
29
+ owner = eval(Regexp.last_match[:owner], irb_context.workspace.binding)
30
+ method = Regexp.last_match[:method]
31
+ if owner.respond_to?(:instance_method) && owner.instance_methods.include?(method.to_sym)
32
+ file, line = owner.instance_method(method).source_location
33
+ end
34
+ when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method
35
+ receiver = eval(Regexp.last_match[:receiver] || 'self', irb_context.workspace.binding)
36
+ method = Regexp.last_match[:method]
37
+ file, line = receiver.method(method).source_location if receiver.respond_to?(method)
38
+ end
39
+ if file && line
40
+ Source.new(file: file, first_line: line, last_line: find_end(file, line))
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def find_end(file, first_line)
47
+ return first_line unless File.exist?(file)
48
+ lex = RubyLex.new
49
+ lines = File.read(file).lines[(first_line - 1)..-1]
50
+ tokens = RubyLex.ripper_lex_without_warning(lines.join)
51
+ prev_tokens = []
52
+
53
+ # chunk with line number
54
+ tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk|
55
+ code = lines[0..lnum].join
56
+ prev_tokens.concat chunk
57
+ continue = lex.process_continue(prev_tokens)
58
+ code_block_open = lex.check_code_block(code, prev_tokens)
59
+ if !continue && !code_block_open
60
+ return first_line + lnum
61
+ end
62
+ end
63
+ first_line
64
+ end
65
+
66
+ def string_literal?(args)
67
+ sexp = Ripper.sexp(args)
68
+ sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal
69
+ end
70
+ end
71
+
12
72
  def execute(str = nil)
13
73
  unless str.is_a?(String)
14
74
  puts "Error: Expected a string but got #{str.inspect}"
15
75
  return
16
76
  end
17
- source = find_source(str)
77
+
78
+ source = self.class.find_source(str, @irb_context)
18
79
  if source && File.exist?(source.file)
19
80
  show_source(source)
20
81
  else
@@ -35,48 +96,6 @@ module IRB
35
96
  puts
36
97
  end
37
98
 
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
99
  def bold(str)
81
100
  Color.colorize(str, [:BOLD])
82
101
  end
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 (ex: @, $)
490
+ command, args = line.split(/\s/, 2)
491
+ if original = symbol_alias(command)
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,15 @@ 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 a command name 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[command.to_sym]
552
+ end
521
553
  end
522
554
  end
@@ -73,7 +73,7 @@ module IRB
73
73
  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
@@ -116,6 +116,14 @@ module IRB # :nodoc:
116
116
  [:kill, OVERRIDE_PRIVATE_ONLY],
117
117
  ],
118
118
 
119
+ [
120
+ :irb_debug, :Debug, "cmd/debug",
121
+ [:debug, NO_OVERRIDE],
122
+ ],
123
+ [
124
+ :irb_edit, :Edit, "cmd/edit",
125
+ [:edit, NO_OVERRIDE],
126
+ ],
119
127
  [
120
128
  :irb_help, :Help, "cmd/help",
121
129
  [:help, NO_OVERRIDE],
@@ -147,21 +155,21 @@ module IRB # :nodoc:
147
155
 
148
156
  ]
149
157
 
150
- # Installs the default irb commands:
151
- #
152
- # +irb_current_working_workspace+:: Context#main
153
- # +irb_change_workspace+:: Context#change_workspace
154
- # +irb_workspaces+:: Context#workspaces
155
- # +irb_push_workspace+:: Context#push_workspace
156
- # +irb_pop_workspace+:: Context#pop_workspace
157
- # +irb_load+:: #irb_load
158
- # +irb_require+:: #irb_require
159
- # +irb_source+:: IrbLoader#source_file
160
- # +irb+:: IRB.irb
161
- # +irb_jobs+:: JobManager
162
- # +irb_fg+:: JobManager#switch
163
- # +irb_kill+:: JobManager#kill
164
- # +irb_help+:: IRB@Command+line+options
158
+ # Convert a command name to its implementation class if such command exists
159
+ def self.load_command(command)
160
+ command = command.to_sym
161
+ @EXTEND_COMMANDS.each do |cmd_name, cmd_class, load_file, *aliases|
162
+ next if cmd_name != command && aliases.all? { |alias_name, _| alias_name != command }
163
+
164
+ if !defined?(ExtendCommand) || !ExtendCommand.const_defined?(cmd_class, false)
165
+ require_relative load_file
166
+ end
167
+ return ExtendCommand.const_get(cmd_class, false)
168
+ end
169
+ nil
170
+ end
171
+
172
+ # Installs the default irb commands.
165
173
  def self.install_extend_commands
166
174
  for args in @EXTEND_COMMANDS
167
175
  def_extend_command(*args)
data/lib/irb/init.rb CHANGED
@@ -44,7 +44,7 @@ module IRB # :nodoc:
44
44
  @CONF[:IRB_RC] = nil
45
45
 
46
46
  @CONF[:USE_SINGLELINE] = false unless defined?(ReadlineInputMethod)
47
- @CONF[:USE_COLORIZE] = !ENV['NO_COLOR']
47
+ @CONF[:USE_COLORIZE] = (nc = ENV['NO_COLOR']).nil? || nc.empty?
48
48
  @CONF[:USE_AUTOCOMPLETE] = true
49
49
  @CONF[:INSPECT_MODE] = true
50
50
  @CONF[:USE_TRACER] = false
@@ -158,6 +158,11 @@ module IRB # :nodoc:
158
158
  @CONF[:LC_MESSAGES] = Locale.new
159
159
 
160
160
  @CONF[:AT_EXIT] = []
161
+
162
+ @CONF[:COMMAND_ALIASES] = {
163
+ :'$' => :show_source,
164
+ :'@' => :whereami,
165
+ }
161
166
  end
162
167
 
163
168
  def IRB.set_measure_callback(type = nil, arg = nil, &block)
@@ -255,8 +260,20 @@ module IRB # :nodoc:
255
260
  when "--nosingleline", "--noreadline"
256
261
  @CONF[:USE_SINGLELINE] = false
257
262
  when "--multiline", "--reidline"
263
+ if opt == "--reidline"
264
+ warn <<~MSG.strip
265
+ --reidline is deprecated, please use --multiline instead.
266
+ MSG
267
+ end
268
+
258
269
  @CONF[:USE_MULTILINE] = true
259
270
  when "--nomultiline", "--noreidline"
271
+ if opt == "--noreidline"
272
+ warn <<~MSG.strip
273
+ --noreidline is deprecated, please use --nomultiline instead.
274
+ MSG
275
+ end
276
+
260
277
  @CONF[:USE_MULTILINE] = false
261
278
  when /^--extra-doc-dir(?:=(.+))?/
262
279
  opt = $1 || argv.shift
@@ -379,11 +396,9 @@ module IRB # :nodoc:
379
396
  end
380
397
  if xdg_config_home = ENV["XDG_CONFIG_HOME"]
381
398
  irb_home = File.join(xdg_config_home, "irb")
382
- unless File.exist? irb_home
383
- require 'fileutils'
384
- FileUtils.mkdir_p irb_home
399
+ if File.directory?(irb_home)
400
+ yield proc{|rc| irb_home + "/irb#{rc}"}
385
401
  end
386
- yield proc{|rc| irb_home + "/irb#{rc}"}
387
402
  end
388
403
  if home = ENV["HOME"]
389
404
  yield proc{|rc| home+"/.irb#{rc}"}
@@ -286,7 +286,8 @@ module IRB
286
286
  if IRB.conf[:USE_COLORIZE]
287
287
  proc do |output, complete: |
288
288
  next unless IRB::Color.colorable?
289
- IRB::Color.colorize_code(output, complete: complete)
289
+ lvars = IRB.CurrentContext&.local_variables || []
290
+ IRB::Color.colorize_code(output, complete: complete, local_variables: lvars)
290
291
  end
291
292
  else
292
293
  proc do |output|
@@ -295,8 +296,13 @@ module IRB
295
296
  end
296
297
  Reline.dig_perfect_match_proc = IRB::InputCompletor::PerfectMatchedProc
297
298
  Reline.autocompletion = IRB.conf[:USE_AUTOCOMPLETE]
299
+
298
300
  if IRB.conf[:USE_AUTOCOMPLETE]
299
- Reline.add_dialog_proc(:show_doc, SHOW_DOC_DIALOG, Reline::DEFAULT_DIALOG_CONTEXT)
301
+ begin
302
+ require 'rdoc'
303
+ Reline.add_dialog_proc(:show_doc, SHOW_DOC_DIALOG, Reline::DEFAULT_DIALOG_CONTEXT)
304
+ rescue LoadError
305
+ end
300
306
  end
301
307
  end
302
308
 
@@ -320,11 +326,6 @@ module IRB
320
326
  [195, 164], # The "ä" that appears when Alt+d is pressed on xterm.
321
327
  [226, 136, 130] # The "∂" that appears when Alt+d in pressed on iTerm2.
322
328
  ]
323
- begin
324
- require 'rdoc'
325
- rescue LoadError
326
- return nil
327
- end
328
329
 
329
330
  if just_cursor_moving and completion_journey_data.nil?
330
331
  return nil
@@ -460,7 +461,7 @@ module IRB
460
461
  # For debug message
461
462
  def inspect
462
463
  config = Reline::Config.new
463
- str = "ReidlineInputMethod with Reline #{Reline::VERSION}"
464
+ str = "RelineInputMethod with Reline #{Reline::VERSION}"
464
465
  if config.respond_to?(:inputrc_path)
465
466
  inputrc_path = File.expand_path(config.inputrc_path)
466
467
  else
data/lib/irb/ruby-lex.rb CHANGED
@@ -48,7 +48,7 @@ class RubyLex
48
48
  end
49
49
 
50
50
  # io functions
51
- def set_input(io, p = nil, context: nil, &block)
51
+ def set_input(io, p = nil, context:, &block)
52
52
  @io = io
53
53
  if @io.respond_to?(:check_termination)
54
54
  @io.check_termination do |code|
@@ -65,6 +65,12 @@ class RubyLex
65
65
  false
66
66
  end
67
67
  else
68
+ # Accept any single-line input starting with a non-identifier alias (ex: @, $)
69
+ command = code.split(/\s/, 2).first
70
+ if context.symbol_alias(command)
71
+ next true
72
+ end
73
+
68
74
  code.gsub!(/\s*\z/, '').concat("\n")
69
75
  ltype, indent, continue, code_block_open = check_state(code, context: context)
70
76
  if ltype or indent > 0 or continue or code_block_open
@@ -136,16 +142,18 @@ class RubyLex
136
142
  :on_param_error
137
143
  ]
138
144
 
145
+ def self.generate_local_variables_assign_code(local_variables)
146
+ "#{local_variables.join('=')}=nil;" unless local_variables.empty?
147
+ end
148
+
139
149
  def self.ripper_lex_without_warning(code, context: nil)
140
150
  verbose, $VERBOSE = $VERBOSE, nil
141
- if context
142
- lvars = context.workspace&.binding&.local_variables
143
- if lvars && !lvars.empty?
144
- code = "#{lvars.join('=')}=nil\n#{code}"
145
- line_no = 0
146
- else
147
- line_no = 1
148
- end
151
+ lvars_code = generate_local_variables_assign_code(context&.local_variables || [])
152
+ if lvars_code
153
+ code = "#{lvars_code}\n#{code}"
154
+ line_no = 0
155
+ else
156
+ line_no = 1
149
157
  end
150
158
 
151
159
  compile_with_errors_suppressed(code, line_no: line_no) do |inner_code, line_no|
@@ -162,7 +170,7 @@ class RubyLex
162
170
  end
163
171
  end
164
172
  else
165
- lexer.parse.reject { |it| it.pos.first == 0 }
173
+ lexer.parse.reject { |it| it.pos.first == 0 }.sort_by(&:pos)
166
174
  end
167
175
  end
168
176
  ensure
@@ -214,6 +222,8 @@ class RubyLex
214
222
  ltype = process_literal_type(tokens)
215
223
  indent = process_nesting_level(tokens)
216
224
  continue = process_continue(tokens)
225
+ lvars_code = self.class.generate_local_variables_assign_code(context.local_variables)
226
+ code = "#{lvars_code}\n#{code}" if lvars_code
217
227
  code_block_open = check_code_block(code, tokens)
218
228
  [ltype, indent, continue, code_block_open]
219
229
  end
@@ -233,13 +243,13 @@ class RubyLex
233
243
  @code_block_open = false
234
244
  end
235
245
 
236
- def each_top_level_statement
246
+ def each_top_level_statement(context)
237
247
  initialize_input
238
248
  catch(:TERM_INPUT) do
239
249
  loop do
240
250
  begin
241
251
  prompt
242
- unless l = lex
252
+ unless l = lex(context)
243
253
  throw :TERM_INPUT if @line == ''
244
254
  else
245
255
  @line_no += l.count("\n")
@@ -269,18 +279,15 @@ class RubyLex
269
279
  end
270
280
  end
271
281
 
272
- def lex
282
+ def lex(context)
273
283
  line = @input.call
274
284
  if @io.respond_to?(:check_termination)
275
285
  return line # multiline
276
286
  end
277
287
  code = @line + (line.nil? ? '' : line)
278
288
  code.gsub!(/\s*\z/, '').concat("\n")
279
- @tokens = self.class.ripper_lex_without_warning(code)
280
- @continue = process_continue
281
- @code_block_open = check_code_block(code)
282
- @indent = process_nesting_level
283
- @ltype = process_literal_type
289
+ @tokens = self.class.ripper_lex_without_warning(code, context: context)
290
+ @ltype, @indent, @continue, @code_block_open = check_state(code, @tokens, context: context)
284
291
  line
285
292
  end
286
293
 
@@ -706,6 +713,7 @@ class RubyLex
706
713
  i = 0
707
714
  start_token = []
708
715
  end_type = []
716
+ pending_heredocs = []
709
717
  while i < tokens.size
710
718
  t = tokens[i]
711
719
  case t.event
@@ -729,18 +737,27 @@ class RubyLex
729
737
  end
730
738
  end
731
739
  when :on_backtick
732
- start_token << t
733
- end_type << :on_tstring_end
740
+ if t.state.allbits?(Ripper::EXPR_BEG)
741
+ start_token << t
742
+ end_type << :on_tstring_end
743
+ end
734
744
  when :on_qwords_beg, :on_words_beg, :on_qsymbols_beg, :on_symbols_beg
735
745
  start_token << t
736
746
  end_type << :on_tstring_end
737
747
  when :on_heredoc_beg
738
- start_token << t
739
- end_type << :on_heredoc_end
748
+ pending_heredocs << t
749
+ end
750
+
751
+ if pending_heredocs.any? && t.tok.include?("\n")
752
+ pending_heredocs.reverse_each do |t|
753
+ start_token << t
754
+ end_type << :on_heredoc_end
755
+ end
756
+ pending_heredocs = []
740
757
  end
741
758
  i += 1
742
759
  end
743
- start_token.last.nil? ? nil : start_token.last
760
+ pending_heredocs.first || start_token.last
744
761
  end
745
762
 
746
763
  def process_literal_type(tokens = @tokens)
data/lib/irb/version.rb CHANGED
@@ -11,7 +11,7 @@
11
11
  #
12
12
 
13
13
  module IRB # :nodoc:
14
- VERSION = "1.4.2"
14
+ VERSION = "1.5.0"
15
15
  @RELEASE_VERSION = VERSION
16
- @LAST_UPDATE_DATE = "2022-10-03"
16
+ @LAST_UPDATE_DATE = "2022-11-20"
17
17
  end
data/lib/irb.rb CHANGED
@@ -53,6 +53,50 @@ require_relative "irb/easter-egg"
53
53
  #
54
54
  # :include: ./irb/lc/help-message
55
55
  #
56
+ # == Commands
57
+ #
58
+ # The following commands are available on IRB.
59
+ #
60
+ # * cwws
61
+ # * Show the current workspace.
62
+ # * cb, cws, chws
63
+ # * Change the current workspace to an object.
64
+ # * bindings, workspaces
65
+ # * Show workspaces.
66
+ # * pushb, pushws
67
+ # * Push an object to the workspace stack.
68
+ # * popb, popws
69
+ # * Pop a workspace from the workspace stack.
70
+ # * load
71
+ # * Load a Ruby file.
72
+ # * require
73
+ # * Require a Ruby file.
74
+ # * source
75
+ # * Loads a given file in the current session.
76
+ # * irb
77
+ # * Start a child IRB.
78
+ # * jobs
79
+ # * List of current sessions.
80
+ # * fg
81
+ # * Switches to the session of the given number.
82
+ # * kill
83
+ # * Kills the session with the given number.
84
+ # * help
85
+ # * Enter the mode to look up RI documents.
86
+ # * irb_info
87
+ # * Show information about IRB.
88
+ # * ls
89
+ # * Show methods, constants, and variables.
90
+ # -g [query] or -G [query] allows you to filter out the output.
91
+ # * measure
92
+ # * measure enables the mode to measure processing time. measure :off disables it.
93
+ # * $, show_source
94
+ # * Show the source code of a given method or constant.
95
+ # * @, whereami
96
+ # * Show the source code around binding.irb again.
97
+ # * debug
98
+ # * Start the debugger of debug.gem.
99
+ #
56
100
  # == Configuration
57
101
  #
58
102
  # IRB reads a personal initialization file when it's invoked.
@@ -93,9 +137,9 @@ require_relative "irb/easter-egg"
93
137
  #
94
138
  # === Autocompletion
95
139
  #
96
- # To enable autocompletion for irb, add the following to your +.irbrc+:
140
+ # To disable autocompletion for irb, add the following to your +.irbrc+:
97
141
  #
98
- # require 'irb/completion'
142
+ # IRB.conf[:USE_AUTOCOMPLETE] = false
99
143
  #
100
144
  # === History
101
145
  #
@@ -426,10 +470,25 @@ module IRB
426
470
  def initialize(workspace = nil, input_method = nil)
427
471
  @context = Context.new(self, workspace, input_method)
428
472
  @context.main.extend ExtendCommandBundle
473
+ @context.command_aliases.each do |alias_name, cmd_name|
474
+ next if @context.symbol_alias(alias_name)
475
+ @context.main.install_alias_method(alias_name, cmd_name)
476
+ end
429
477
  @signal_status = :IN_IRB
430
478
  @scanner = RubyLex.new
431
479
  end
432
480
 
481
+ # A hook point for `debug` command's TracePoint after :IRB_EXIT as well as its clean-up
482
+ def debug_break
483
+ # it means the debug command is executed
484
+ if defined?(DEBUGGER__) && DEBUGGER__.respond_to?(:capture_frames_without_irb)
485
+ # after leaving this initial breakpoint, revert the capture_frames patch
486
+ DEBUGGER__.singleton_class.send(:alias_method, :capture_frames, :capture_frames_without_irb)
487
+ # and remove the redundant method
488
+ DEBUGGER__.singleton_class.send(:undef_method, :capture_frames_without_irb)
489
+ end
490
+ end
491
+
433
492
  def run(conf = IRB.conf)
434
493
  conf[:IRB_RC].call(context) if conf[:IRB_RC]
435
494
  conf[:MAIN_CONTEXT] = context
@@ -506,13 +565,15 @@ module IRB
506
565
 
507
566
  @scanner.set_auto_indent(@context) if @context.auto_indent_mode
508
567
 
509
- @scanner.each_top_level_statement do |line, line_no|
568
+ @scanner.each_top_level_statement(@context) do |line, line_no|
510
569
  signal_status(:IN_EVAL) do
511
570
  begin
512
571
  line.untaint if RUBY_VERSION < '2.7'
513
572
  if IRB.conf[:MEASURE] && IRB.conf[:MEASURE_CALLBACKS].empty?
514
573
  IRB.set_measure_callback
515
574
  end
575
+ # Assignment expression check should be done before @context.evaluate to handle code like `a /2#/ if false; a = 1`
576
+ is_assignment = assignment_expression?(line)
516
577
  if IRB.conf[:MEASURE] && !IRB.conf[:MEASURE_CALLBACKS].empty?
517
578
  result = nil
518
579
  last_proc = proc{ result = @context.evaluate(line, line_no, exception: exc) }
@@ -529,7 +590,7 @@ module IRB
529
590
  @context.evaluate(line, line_no, exception: exc)
530
591
  end
531
592
  if @context.echo?
532
- if assignment_expression?(line)
593
+ if is_assignment
533
594
  if @context.echo_on_assignment?
534
595
  output_value(@context.echo_on_assignment? == :truncate)
535
596
  end
@@ -592,11 +653,7 @@ module IRB
592
653
 
593
654
  if exc.backtrace
594
655
  order = nil
595
- if '2.5.0' == RUBY_VERSION
596
- # Exception#full_message doesn't have keyword arguments.
597
- message = exc.full_message # the same of (highlight: true, order: bottom)
598
- order = :bottom
599
- elsif '2.5.1' <= RUBY_VERSION && RUBY_VERSION < '3.0.0'
656
+ if RUBY_VERSION < '3.0.0'
600
657
  if STDOUT.tty?
601
658
  message = exc.full_message(order: :bottom)
602
659
  order = :bottom
@@ -827,9 +884,12 @@ module IRB
827
884
  # array of parsed expressions. The first element of each expression is the
828
885
  # expression's type.
829
886
  verbose, $VERBOSE = $VERBOSE, nil
830
- result = ASSIGNMENT_NODE_TYPES.include?(Ripper.sexp(line)&.dig(1,-1,0))
887
+ code = "#{RubyLex.generate_local_variables_assign_code(@context.local_variables) || 'nil;'}\n#{line}"
888
+ # Get the last node_type of the line. drop(1) is to ignore the local_variables_assign_code part.
889
+ node_type = Ripper.sexp(code)&.dig(1)&.drop(1)&.dig(-1, 0)
890
+ ASSIGNMENT_NODE_TYPES.include?(node_type)
891
+ ensure
831
892
  $VERBOSE = verbose
832
- result
833
893
  end
834
894
 
835
895
  ATTR_TTY = "\e[%sm"
@@ -919,12 +979,13 @@ class Binding
919
979
  #
920
980
  #
921
981
  # See IRB@IRB+Usage for more information.
922
- def irb
982
+ def irb(show_code: true)
923
983
  IRB.setup(source_location[0], argv: [])
924
984
  workspace = IRB::WorkSpace.new(self)
925
- STDOUT.print(workspace.code_around_binding)
985
+ STDOUT.print(workspace.code_around_binding) if show_code
926
986
  binding_irb = IRB::Irb.new(workspace)
927
987
  binding_irb.context.irb_path = File.expand_path(source_location[0])
928
988
  binding_irb.run(IRB.conf)
989
+ binding_irb.debug_break
929
990
  end
930
991
  end
data/man/irb.1 CHANGED
@@ -173,8 +173,19 @@ The default value is 16.
173
173
  .El
174
174
  .Pp
175
175
  .Sh ENVIRONMENT
176
- .Bl -tag -compact
176
+ .Bl -tag -compact -width "XDG_CONFIG_HOME"
177
+ .It Ev IRB_LANG
178
+ The locale used for
179
+ .Nm .
180
+ .Pp
177
181
  .It Ev IRBRC
182
+ The path to the personal initialization file.
183
+ .Pp
184
+ .It Ev XDG_CONFIG_HOME
185
+ .Nm
186
+ respects XDG_CONFIG_HOME. If this is set, load
187
+ .Pa $XDG_CONFIG_HOME/irb/irbrc
188
+ as a personal initialization file.
178
189
  .Pp
179
190
  .El
180
191
  .Pp
@@ -186,7 +197,17 @@ depends on same variables as
186
197
  .Sh FILES
187
198
  .Bl -tag -compact
188
199
  .It Pa ~/.irbrc
189
- Personal irb initialization.
200
+ Personal irb initialization. If
201
+ .Ev IRBRC
202
+ is set, read
203
+ .Pa $IRBRC
204
+ instead. If
205
+ .Ev IRBRC
206
+ is not set and
207
+ .Ev XDG_CONFIG_HOME
208
+ is set,
209
+ .Pa $XDG_CONFIG_HOME/irb/irbrc
210
+ is loaded.
190
211
  .Pp
191
212
  .El
192
213
  .Pp
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: irb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  - Keiju ISHITSUKA
9
- autorequire:
9
+ autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2022-10-05 00:00:00.000000000 Z
12
+ date: 2022-11-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: reline
@@ -47,6 +47,8 @@ files:
47
47
  - irb.gemspec
48
48
  - lib/irb.rb
49
49
  - lib/irb/cmd/chws.rb
50
+ - lib/irb/cmd/debug.rb
51
+ - lib/irb/cmd/edit.rb
50
52
  - lib/irb/cmd/fork.rb
51
53
  - lib/irb/cmd/help.rb
52
54
  - lib/irb/cmd/info.rb
@@ -99,7 +101,7 @@ licenses:
99
101
  - Ruby
100
102
  - BSD-2-Clause
101
103
  metadata: {}
102
- post_install_message:
104
+ post_install_message:
103
105
  rdoc_options: []
104
106
  require_paths:
105
107
  - lib
@@ -107,15 +109,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
107
109
  requirements:
108
110
  - - ">="
109
111
  - !ruby/object:Gem::Version
110
- version: '2.5'
112
+ version: '2.6'
111
113
  required_rubygems_version: !ruby/object:Gem::Requirement
112
114
  requirements:
113
115
  - - ">="
114
116
  - !ruby/object:Gem::Version
115
117
  version: '0'
116
118
  requirements: []
117
- rubygems_version: 3.4.0.dev
118
- signing_key:
119
+ rubygems_version: 3.3.7
120
+ signing_key:
119
121
  specification_version: 4
120
122
  summary: Interactive Ruby command-line tool for REPL (Read Eval Print Loop).
121
123
  test_files: []