1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/lib/irb/cmd/show_source.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

96 lines
3.2 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
require_relative "nop"
require_relative "../color"
require_relative "../ruby-lex"
module IRB
# :stopdoc:
module ExtendCommand
class ShowSource < Nop
def execute(str = nil)
unless str.is_a?(String)
puts "Error: Expected a string but got #{str.inspect}"
return
end
source = find_source(str)
if source && File.exist?(source.file)
show_source(source)
else
puts "Error: Couldn't locate a definition for #{str}"
end
nil
end
private
# @param [IRB::ExtendCommand::ShowSource::Source] source
def show_source(source)
puts
puts "#{bold("From")}: #{source.file}:#{source.first_line}"
puts
code = IRB::Color.colorize_code(File.read(source.file))
puts code.lines[(source.first_line - 1)...source.last_line].join
puts
end
def find_source(str)
case str
when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name
eval(str, irb_context.workspace.binding) # trigger autoload
base = irb_context.workspace.binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object }
file, line = base.const_source_location(str) if base.respond_to?(:const_source_location) # Ruby 2.7+
when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method
owner = eval(Regexp.last_match[:owner], irb_context.workspace.binding)
method = Regexp.last_match[:method]
if owner.respond_to?(:instance_method) && owner.instance_methods.include?(method.to_sym)
file, line = owner.instance_method(method).source_location
end
when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method
receiver = eval(Regexp.last_match[:receiver] || 'self', irb_context.workspace.binding)
method = Regexp.last_match[:method]
file, line = receiver.method(method).source_location if receiver.respond_to?(method)
end
if file && line
Source.new(file: file, first_line: line, last_line: find_end(file, line))
end
end
def find_end(file, first_line)
return first_line unless File.exist?(file)
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
Compatibility with IRB Instead of accessing the struct as an array, access it via methods. There are other places inside of this file already using this API (for example https://github.com/ruby/ruby/blob/e0a5c3d2b71dfad038d7562fdd33f02ffd79232d/lib/irb/ruby-lex.rb#L829-L830). This commit moves all struct array-ish calls to use their method calls instead. It is also ~1.23 faster accessing values via a method instead of as an array according to this microbenchmark: ```ruby Elem = Struct.new(:pos, :event, :tok, :state, :message) do def initialize(pos, event, tok, state, message = nil) super(pos, event, tok, State.new(state), message) end # ... def to_a a = super a.pop unless a.empty? a end end class ElemClass attr_accessor :pos, :event, :tok, :state, :message def initialize(pos, event, tok, state, message = nil) @pos = pos @event = event @tok = tok @state = State.new(state) @message = message end def to_a if @message [@pos, @event, @tok, @state, @message] else [@pos, @event, @tok, @state] end end end # stub state class creation for now class State; def initialize(val); end; end ``` ```ruby Benchmark.ips do |x| x.report("struct") { struct[1] } x.report("class ") { from_class.event } x.compare! end; nil ``` ``` Warming up -------------------------------------- struct 1.624M i/100ms class 1.958M i/100ms Calculating ------------------------------------- struct 17.139M (± 2.6%) i/s - 86.077M in 5.025801s class 21.104M (± 3.4%) i/s - 105.709M in 5.015193s Comparison: class : 21103826.3 i/s struct: 17139201.5 i/s - 1.23x (± 0.00) slower ```
2021-11-07 19:33:04 -06:00
tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk|
code = lines[0..lnum].join
prev_tokens.concat chunk
continue = lex.process_continue(prev_tokens)
code_block_open = lex.check_code_block(code, prev_tokens)
if !continue && !code_block_open
return first_line + lnum
end
end
first_line
end
def bold(str)
Color.colorize(str, [:BOLD])
end
Source = Struct.new(
:file, # @param [String] - file name
:first_line, # @param [String] - first line
:last_line, # @param [String] - last line
keyword_init: true,
)
private_constant :Source
end
end
# :startdoc:
end