summaryrefslogtreecommitdiff
path: root/lib/irb/source_finder.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/irb/source_finder.rb')
-rw-r--r--lib/irb/source_finder.rb96
1 files changed, 65 insertions, 31 deletions
diff --git a/lib/irb/source_finder.rb b/lib/irb/source_finder.rb
index ad9ee21026..26aae7643b 100644
--- a/lib/irb/source_finder.rb
+++ b/lib/irb/source_finder.rb
@@ -4,12 +4,58 @@ require_relative "ruby-lex"
module IRB
class SourceFinder
- Source = Struct.new(
- :file, # @param [String] - file name
- :first_line, # @param [String] - first line
- :last_line, # @param [String] - last line
- keyword_init: true,
- )
+ class Source
+ attr_reader :file, :line
+ def initialize(file, line, ast_source = nil)
+ @file = file
+ @line = line
+ @ast_source = ast_source
+ end
+
+ def file_exist?
+ File.exist?(@file)
+ end
+
+ def binary_file?
+ # If the line is zero, it means that the target's source is probably in a binary file.
+ @line.zero?
+ end
+
+ def file_content
+ @file_content ||= File.read(@file)
+ end
+
+ def colorized_content
+ if !binary_file? && file_exist?
+ end_line = Source.find_end(file_content, @line)
+ # To correctly colorize, we need to colorize full content and extract the relevant lines.
+ colored = IRB::Color.colorize_code(file_content)
+ colored.lines[@line - 1...end_line].join
+ elsif @ast_source
+ IRB::Color.colorize_code(@ast_source)
+ end
+ end
+
+ def self.find_end(code, first_line)
+ lex = RubyLex.new
+ lines = code.lines[(first_line - 1)..-1]
+ tokens = RubyLex.ripper_lex_without_warning(lines.join)
+ prev_tokens = []
+
+ # chunk with line number
+ tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk|
+ code = lines[0..lnum].join
+ prev_tokens.concat chunk
+ continue = lex.should_continue?(prev_tokens)
+ syntax = lex.check_code_syntax(code, local_variables: [])
+ if !continue && syntax == :valid
+ return first_line + lnum
+ end
+ end
+ first_line
+ end
+ end
+
private_constant :Source
def initialize(irb_context)
@@ -27,40 +73,28 @@ module IRB
owner = eval(Regexp.last_match[:owner], context_binding)
method = Regexp.last_match[:method]
return unless owner.respond_to?(:instance_method)
- file, line = method_target(owner, super_level, method, "owner")
+ method = method_target(owner, super_level, method, "owner")
+ file, line = method&.source_location
when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method
receiver = eval(Regexp.last_match[:receiver] || 'self', context_binding)
method = Regexp.last_match[:method]
return unless receiver.respond_to?(method, true)
- file, line = method_target(receiver, super_level, method, "receiver")
+ method = method_target(receiver, super_level, method, "receiver")
+ file, line = method&.source_location
end
- # If the line is zero, it means that the target's source is probably in a binary file, which we should ignore.
- if file && line && !line.zero? && File.exist?(file)
- Source.new(file: file, first_line: line, last_line: find_end(file, line))
+ return unless file && line
+
+ if File.exist?(file)
+ Source.new(file, line)
+ elsif method
+ # Method defined with eval, probably in IRB session
+ source = RubyVM::AbstractSyntaxTree.of(method)&.source rescue nil
+ Source.new(file, line, source)
end
end
private
- def find_end(file, first_line)
- lex = RubyLex.new
- lines = File.read(file).lines[(first_line - 1)..-1]
- tokens = RubyLex.ripper_lex_without_warning(lines.join)
- prev_tokens = []
-
- # chunk with line number
- tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk|
- code = lines[0..lnum].join
- prev_tokens.concat chunk
- continue = lex.should_continue?(prev_tokens)
- syntax = lex.check_code_syntax(code, local_variables: [])
- if !continue && syntax == :valid
- return first_line + lnum
- end
- end
- first_line
- end
-
def method_target(owner_receiver, super_level, method, type)
case type
when "owner"
@@ -71,7 +105,7 @@ module IRB
super_level.times do |s|
target_method = target_method.super_method if target_method
end
- target_method.nil? ? nil : target_method.source_location
+ target_method
rescue NameError
nil
end