This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user