irb 1.4.2 → 1.5.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.
- checksums.yaml +4 -4
- data/README.md +51 -0
- data/Rakefile +25 -0
- data/irb.gemspec +1 -1
- data/lib/irb/cmd/debug.rb +96 -0
- data/lib/irb/cmd/edit.rb +65 -0
- data/lib/irb/cmd/ls.rb +9 -0
- data/lib/irb/cmd/show_source.rb +62 -43
- data/lib/irb/color.rb +9 -2
- data/lib/irb/completion.rb +53 -40
- data/lib/irb/context.rb +34 -2
- data/lib/irb/ext/save-history.rb +1 -1
- data/lib/irb/extend-command.rb +23 -15
- data/lib/irb/init.rb +20 -5
- data/lib/irb/input-method.rb +9 -8
- data/lib/irb/ruby-lex.rb +40 -23
- data/lib/irb/version.rb +2 -2
- data/lib/irb.rb +74 -13
- data/man/irb.1 +23 -2
- metadata +9 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33ff0b70fb3730f087bc24a7a8b63253ba1a4ad0416a0ffcd1e5395f26e5d0b2
|
4
|
+
data.tar.gz: 9cd418e90733c4cb4658368b2a899b50490ad9dbd7352a1c1df0070c10d9669d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
data/lib/irb/cmd/edit.rb
ADDED
@@ -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
|
|
data/lib/irb/cmd/show_source.rb
CHANGED
@@ -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
|
-
|
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(
|
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
|
|
data/lib/irb/completion.rb
CHANGED
@@ -64,25 +64,27 @@ module IRB
|
|
64
64
|
if File.respond_to?(:absolute_path?)
|
65
65
|
File.absolute_path?(p)
|
66
66
|
else
|
67
|
-
|
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
|
-
|
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
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
58
|
+
USE_REIDLINE is deprecated, please use USE_MULTILINE instead.
|
56
59
|
MSG
|
57
|
-
@use_multiline = IRB.conf[:
|
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
|
data/lib/irb/ext/save-history.rb
CHANGED
@@ -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 ==
|
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
|
data/lib/irb/extend-command.rb
CHANGED
@@ -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
|
-
#
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
#
|
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] =
|
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
|
-
|
383
|
-
|
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}"}
|
data/lib/irb/input-method.rb
CHANGED
@@ -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
|
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
|
-
|
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 = "
|
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
|
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
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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 =
|
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
|
-
|
733
|
-
|
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
|
-
|
739
|
-
|
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
|
-
|
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
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
|
140
|
+
# To disable autocompletion for irb, add the following to your +.irbrc+:
|
97
141
|
#
|
98
|
-
#
|
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
|
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 '
|
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
|
-
|
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
|
+
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-
|
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.
|
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.
|
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: []
|