pry/lib/pry/commands/whereami.rb

206 lines
5.6 KiB
Ruby

# frozen_string_literal: true
require 'method_source'
class Pry
class Command
class Whereami < Pry::ClassCommand
def initialize(*)
super
@method_code = nil
end
class << self
attr_accessor :method_size_cutoff
end
@method_size_cutoff = 30
match 'whereami'
description 'Show code surrounding the current context.'
group 'Context'
banner <<-'BANNER'
Usage: whereami [-qn] [LINES]
Describe the current location. If you use `binding.pry` inside a method then
whereami will print out the source for that method.
If a number is passed, then LINES lines before and after the current line will be
shown instead of the method itself.
The `-q` flag can be used to suppress error messages in the case that there's
no code to show. This is used by pry in the default before_session hook to show
you when you arrive at a `binding.pry`.
The `-n` flag can be used to hide line numbers so that code can be copy/pasted
effectively.
When pry was started on an Object and there is no associated method, whereami
will instead output a brief description of the current object.
BANNER
def setup
if target.respond_to?(:source_location)
file, @line = target.source_location
@file = expand_path(file)
else
@file = expand_path(target.eval('__FILE__'))
@line = target.eval('__LINE__')
end
@method = Pry::Method.from_binding(target)
end
def options(opt)
opt.on :q, :quiet, "Don't display anything in case of an error"
opt.on :n, :"no-line-numbers", "Do not display line numbers"
opt.on :m, :method, "Show the complete source for the current method."
opt.on :c, :class, "Show the complete source for the current class or module."
opt.on :f, :file, "Show the complete source for the current file."
end
def code
@code ||= if opts.present?(:m)
method_code || raise(CommandError, "Cannot find method code.")
elsif opts.present?(:c)
class_code || raise(CommandError, "Cannot find class code.")
elsif opts.present?(:f)
Pry::Code.from_file(@file)
elsif args.any?
code_window
else
default_code
end
end
def code?
!!code
rescue MethodSource::SourceNotFoundError
false
end
def bad_option_combination?
[opts.present?(:m), opts.present?(:f),
opts.present?(:c), args.any?].count(true) > 1
end
def location
"#{@file}:#{@line} #{@method && @method.name_with_owner}"
end
def process
if bad_option_combination?
raise CommandError, "Only one of -m, -c, -f, and LINES may be specified."
end
return if nothing_to_do?
if internal_binding?(target)
handle_internal_binding
return
end
set_file_and_dir_locals(@file)
pretty_code = code.with_line_numbers(use_line_numbers?)
.with_marker(marker)
.highlighted
pry_instance.pager.page(
"\n#{bold('From:')} #{location}:\n\n" << pretty_code << "\n"
)
end
private
def nothing_to_do?
opts.quiet? && (internal_binding?(target) || !code?)
end
def use_line_numbers?
!opts.present?(:n)
end
def marker
!opts.present?(:n) && @line
end
def top_level?
target_self == Pry.main
end
def handle_internal_binding
if top_level?
output.puts "At the top level."
else
output.puts "Inside #{Pry.view_clip(target_self)}."
end
end
def small_method?
@method.source_range.count < self.class.method_size_cutoff
end
def default_code
if method_code && small_method?
method_code
else
code_window
end
end
def code_window
Pry::Code.from_file(@file).around(@line, window_size)
end
def method_code
return @method_code if @method_code
@method_code = Pry::Code.from_method(@method) if valid_method?
end
# This either returns the `target_self`
# or it returns the class of `target_self` if `target_self` is not a class.
# @return [Pry::WrappedModule]
def target_class
return Pry::WrappedModule(target_self) if target_self.is_a?(Module)
Pry::WrappedModule(target_self.class)
end
def class_code
@class_code ||=
begin
mod = @method ? Pry::WrappedModule(@method.owner) : target_class
idx = mod.candidates.find_index { |v| expand_path(v.source_file) == @file }
idx && Pry::Code.from_module(mod, idx)
end
end
def valid_method?
@method && @method.source? && expand_path(@method.source_file) == @file &&
@method.source_range.include?(@line)
end
def expand_path(filename)
return unless filename
return filename if Pry.eval_path == filename
File.expand_path(filename)
end
def window_size
if args.empty?
pry_instance.config.default_window_size
else
args.first.to_i
end
end
end
Pry::Commands.add_command(Pry::Command::Whereami)
Pry::Commands.alias_command '@', 'whereami'
Pry::Commands.alias_command(/whereami[!?]+/, 'whereami')
end
end