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 +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: []
|