Skip to content

Commit

Permalink
Making the finder a class and introducing better @-variable finding
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh Symonds committed Feb 10, 2010
1 parent 85fac2b commit 5aae65a
Showing 1 changed file with 119 additions and 84 deletions.
203 changes: 119 additions & 84 deletions Support/bin/jump_to_method_definition.rb
Expand Up @@ -5,107 +5,142 @@
require 'rubygems'
require "#{ENV['TM_SUPPORT_PATH']}/lib/tm/htmloutput"

@found = []
#start at rails root for this project or the current directory if you're searching outside of a rails project
@root = RailsPath.new.rails_root || TextMate.directory

def get_method_regexp_for(term)
"^\s*def (self\.)?#{Regexp.escape(term)}([\(]{1}[^\)]*[\)]{1}\s*$|\s*$)"
end
class FindMethod
attr_accessor :found_methods, :root, :filepath, :term

def initialize(term, filepath = RailsPath.new)
@term = term
@term = @term + $1 if TextMate.current_line.match(Regexp.new(Regexp.escape(@term) + '(!|\?)'))
@found_methods = []
@filepath = filepath
@root = RailsPath.new.rails_root || TextMate.directory
self.find
self.render_results
end

def method_regexp_for(term)
"^\\s*def (self\.)?#{Regexp.escape(term)}([\(]{1}[^\)]*[\)]{1}\\s*$|\\s*$)"
end

def get_association_regexp_for(term)
"^\s*(belongs_to|has_many|has_one|has_and_belongs_to_many|scope|named_scope) :#{Regexp.escape(term)}[\,]?"
end
def association_regexp_for(term)
"^\\s*(belongs_to|has_many|has_one|has_and_belongs_to_many|scope|named_scope) :#{Regexp.escape(term)}[\,]?"
end

def get_class_or_module_regexp_for(term)
"^\s*(class|module)[^<]*[\s:]#{Regexp.escape(term)}[\s:\#\n]*[<$\n]"
end
def class_or_module_regexp_for(term)
"^\\s*(class|module)[^<]*[\\s:]#{Regexp.escape(term)}[\\s:\#\n]*[<$\n]"
end

def get_class_variable_regexp_for(term)
"#{Regexp.escape('@' + term)}[^a-zA-Z0-9\?]"
end
def variable_regexp_for(term)
"#{Regexp.escape(term)}\\s="
end

def path_regexp_for(term)
"[^\.].resource[s]? (:|')#{term}(s|es)?[']?"
end

def find_in_file_or_directory(file_or_directory, match_string)
return if file_or_directory.nil?
if File.directory?(file_or_directory)
Dir.glob(File.join(file_or_directory,'**','*.rb')).each do |file|
find_in_file(file, match_string)
def find_in_file(file, match_string)
begin
File.open(file) do |f|
f.each_line do |line|
@found_methods << {:filename => f.path, :line_number => f.lineno, :line => line.strip} if line.match(match_string)
end
end
rescue Errno::ENOENT
return false
end
else
find_in_file(file_or_directory, match_string)
end
end

def find_in_file(file, match_string)
begin
File.open(file) do |f|
f.each_line do |line|
@found << {:filename => f.path, :line_number => f.lineno, :line => line.strip} if line.match(match_string)
def found_in_file(file, match_string)
begin
File.open(file) do |f|
f.each_line do |line|
return f.lineno if line.match(match_string)
end
end
rescue Errno::ENOENT
return false
end
rescue Errno::ENOENT
return false
end
end

@term = TextMate.selected_text || TextMate.current_word
def find_in_directory(directory, match_string)
Dir.glob(File.join(directory,'**','*.rb')).each do |file|
find_in_file(file, match_string)
end
end

case
# First, if this starts with a capital, it's probably a class or a module
when @term=~/^[A-Z]/
# Search the local project for any potentially matching class or module.
class_or_module_regexp = get_class_or_module_regexp_for(@term)
find_in_file_or_directory(@root, class_or_module_regexp)
def find_in_gems(match_string)
Gem.latest_load_paths.each do |directory|
find_in_directory(directory, match_string)
end
end

# Then search the Gems directory, pulling only the most recent gems.
Gem.latest_load_paths.each do |directory|
find_in_file_or_directory(directory, class_or_module_regexp)
def find_class_or_module
match = class_or_module_regexp_for(@term)
find_in_directory(@root, match)
find_in_gems(match)
end
# Second, if this starts with a @, it's a class variable
when TextMate.current_line.match(Regexp.escape('@' + @term ))
find_in_file_or_directory(TextMate.filepath, get_class_variable_regexp_for(@term))
# Let's guess it's a method.
else
@term = @term + $1 if TextMate.current_line.match(Regexp.new(Regexp.escape(@term) + '(!|\?)'))
method_regexp = get_method_regexp_for(@term)
# Search the local project for any potentially matching method.
find_in_file_or_directory(@root, method_regexp)
# Search for associations that could create this magic method.
find_in_file_or_directory(File.join(@root,'app','models'), get_association_regexp_for(@term))

# If it's a format of a magic method generated by routes look for it in routes.rb.
if path = @term.match(/(new_|edit_)?(.*?)_(path|url)/)
path = path[2].split('_').first
filename = File.join(@root,"config","routes.rb")
find_in_file_or_directory(filename, "[^\.].resource[s]? (:|')#{path}(s|es)?[']?")
def find_variable
@term = "@#{@term}"
if @filepath.file_type == :view
probable_controller = File.join(@root,"app","controllers","#{@filepath.controller_name}_controller.rb")
if File.file?(probable_controller)
find_in_file(File.join(@root,"app","controllers","#{@filepath.controller_name}_controller.rb"), variable_regexp_for(@term))
action = @filepath.action_name
if line_number = found_in_file(probable_controller, method_regexp_for(action))
@found_methods = [@found_methods.sort {|m1, m2| (line_number - m1[:line_number]) <=> (line_number - m2[:line_number])}.first]
end
end
else
find_in_file(TextMate.filepath, variable_regexp_for(@term))
end
end

def find_method
find_in_directory(@root, method_regexp_for(@term))
find_in_directory(File.join(@root,'app','models'), association_regexp_for(@term))

# Then search the Gems directory, pulling only the most recent gems.
Gem.latest_load_paths.each do |directory|
find_in_file_or_directory(directory, method_regexp)
end
end
if path = @term.match(/(new_|edit_)?(.*?)_(path|url)/)
path = path[2].split('_').first
find_in_file(File.join(@root,"config","routes.rb"), path_regexp_for(path))
end

# Render results sensibly.
if @found.empty?
TextMate.exit_show_tool_tip("Could not find definition for '#{@term}'")
elsif @found.size == 1
TextMate.open(File.join(@found[0][:filename]), @found[0][:line_number] - 1)
TextMate.exit_show_tool_tip("Found definition for '#{@term}' in #{@found[0][:filename].gsub("#{@root}/",'')}")
else
TextMate::HTMLOutput.show(
:title => "Definitions for #{@term}",
:sub_title => "#{@found.size} Definitions Found"
) do |io|
io << "<div class='executor'><table border='0' cellspacing='5' cellpading'0'>"
io << "<pre>Found #{@found.size} definitions for #{@term}:</pre>"
io << "<thead><td><h4>Location</h4></td><td><h4>Line</h4></td><td><h4>Definition</h4></td></thead><tbody>"
@found.each do |location|
io << "<tr><td><a class='near' href='txmt://open?url=file://#{location[:filename]}&line=#{location[:line_number]}'>#{location[:filename].gsub("#{@root}/",'')}</a></td>"
io << "<td>#{location[:line_number]}</td>"
io << "<td><strong>#{location[:line].strip}</strong><td?</tr>"
find_in_gems(method_regexp_for(@term))
end

def find
case
when @term=~/^[A-Z]/ then find_class_or_module # First, if this starts with a capital, it's probably a class or a module
when TextMate.current_line.match(Regexp.escape('@' + @term )) then find_variable # Second, if this starts with a @, it's an instance variable
else find_method # Otherwise, try to find it as a method
end
io << "</tbody></table></div>"
end
TextMate.exit_show_html

def render_results
if @found_methods.empty?
TextMate.exit_show_tool_tip("Could not find definition for '#{@term}'")
elsif @found_methods.size == 1
TextMate.open(File.join(@found_methods[0][:filename]), @found_methods[0][:line_number] - 1)
TextMate.exit_show_tool_tip("Found definition for '#{@term}' in #{@found_methods[0][:filename].gsub("#{@root}/",'')}")
else
TextMate::HTMLOutput.show(
:title => "Definitions for #{@term}",
:sub_title => "#{@found_methods.size} Definitions Found"
) do |io|
io << "<div class='executor'><table border='0' cellspacing='5' cellpading'0'>"
io << "<pre>Found #{@found_methods.size} definitions for #{@term}:</pre>"
io << "<thead><td><h4>Location</h4></td><td><h4>Line</h4></td><td><h4>Definition</h4></td></thead><tbody>"
@found_methods.each do |location|
io << "<tr><td><a class='near' href='txmt://open?url=file://#{location[:filename]}&line=#{location[:line_number]}'>#{location[:filename].gsub("#{@root}/",'')}</a></td>"
io << "<td>#{location[:line_number]}</td>"
io << "<td><strong>#{location[:line].strip}</strong><td?</tr>"
end
io << "</tbody></table></div>"
end
TextMate.exit_show_html
end
end

end

FindMethod.new(TextMate.selected_text || TextMate.current_word)

0 comments on commit 5aae65a

Please sign in to comment.