Files
ChocolateyPackages/EthanBrown.SublimeText2.WebPackages/tools/PackageCache/CoffeeComplete Plus (Autocompletion)/CoffeeGotoDefinition.py
2013-04-04 08:54:25 -04:00

250 lines
11 KiB
Python

import sublime, sublime_plugin
import re
import os
import threading
import coffee_utils
from coffee_utils import debug
COMMAND_NAME = 'coffee_goto_definition'
STATUS_MESSAGE_DEFINITION_FOUND = "Coffee: Definition for \"%s\" found."
STATUS_MESSAGE_NO_DEFINITION_FOUND = "Coffee: No definition for \"%s\" found."
STATUS_MESSAGE_COFFEE_GOTO_DEFINITION = "Coffee: Goto Definition of \"%s\""
# SEARCH ORDER:
# Current file class (TitleCaps only)
# Current file function
# Current file assignment
# Global TitleCaps.coffee class
# Global search for class (TitleCaps only)
# Global search for function
# TODO:
# X Add config for "this" aliases (DONE)
# - Codo docs searching for function parameter types
# X Goto definition knows about function parameters and for loop variables
# - Smarter operand parsing. E.g. Given: this.test = "test", when goto "test", look for "this.test = ", not "test ="
# - Check contents of currently open views
# - Menu integration
class CoffeeGotoDefinitionCommand(sublime_plugin.TextCommand):
def run(self, edit):
# Get the window
self.window = sublime.active_window()
# The current view
view = self.view
# Lines for currently viewed file
current_file_lines = coffee_utils.get_view_content_lines(view)
# Get currently selected word
coffee_utils.select_current_word(view)
selected_word = coffee_utils.get_selected_word(view)
selected_region = self.view.sel()[0]
# http://www.sublimetext.com/forum/viewtopic.php?f=6&t=9076
settings = sublime.load_settings(coffee_utils.SETTINGS_FILE_NAME)
# Pull the excluded dirs from preferences
excluded_dirs = settings.get(coffee_utils.PREFERENCES_COFFEE_EXCLUDED_DIRS)
if not excluded_dirs:
excluded_dirs = []
restricted_to_dirs = settings.get(coffee_utils.PREFERENCES_COFFEE_RESTRICTED_TO_PATHS)
if not restricted_to_dirs:
restricted_to_dirs = []
# List of all project folders
project_folder_list = self.window.folders()
if restricted_to_dirs:
specific_project_folders = []
for next_restricted_dir in restricted_to_dirs:
for next_project_folder in project_folder_list:
next_specific_folder = os.path.normpath(os.path.join(next_project_folder, next_restricted_dir))
specific_project_folders.append(next_specific_folder)
project_folder_list = specific_project_folders
# If there is a word selection and we're looking at a coffee file...
if len(selected_word) > 0 and coffee_utils.is_coffee_syntax(view):
thread = CoffeeGotoDefinitionThread(project_folder_list, current_file_lines, selected_word, excluded_dirs, selected_region)
thread.start()
self.check_operation(thread)
def check_operation(self, thread, previous_progress_indicator_tuple=None):
selected_word = thread.selected_word
if not thread.is_alive():
# Flatten any selection ranges
if len(self.view.sel()) > 0:
region = self.view.sel()[0]
debug(region)
end_point = region.end()
region_to_select = sublime.Region(end_point, end_point)
coffee_utils.select_region_in_view(self.view, region_to_select)
matched_location_tuple = thread.matched_location_tuple
if matched_location_tuple:
# debug("Match found!")
file_to_open = matched_location_tuple[0]
row = matched_location_tuple[1] + 1
column = matched_location_tuple[2] + 1
match = matched_location_tuple[3]
row_start_index = matched_location_tuple[4]
# If there is a file to open...
if file_to_open:
# Open the file in the editor
coffee_utils.open_file_at_position(self.window, file_to_open, row, column)
# Otherwise, assume we found the match in the current view
else:
match_end = row_start_index + match.start() + len(match.group())
region_to_select = sublime.Region(match_end, match_end)
coffee_utils.select_region_in_view(self.view, region_to_select)
self.view.show(region_to_select)
self.window.active_view().set_status(COMMAND_NAME, STATUS_MESSAGE_DEFINITION_FOUND % selected_word)
else:
self.window.active_view().set_status(COMMAND_NAME, STATUS_MESSAGE_NO_DEFINITION_FOUND % selected_word)
else:
# Create the command's goto definition text, including the selected word. For the status bar.
goto_definition_status_text = STATUS_MESSAGE_COFFEE_GOTO_DEFINITION % selected_word
# Get a tuple containing the progress text, progress position, and progress direction.
# This is used to animate a progress indicator in the status bar.
current_progress_indicator_tuple = coffee_utils.get_progress_indicator_tuple(previous_progress_indicator_tuple)
# Get the progress text
progress_indicator_status_text = current_progress_indicator_tuple[0]
# Set the status bar text so the user knows what's going on
self.window.active_view().set_status(COMMAND_NAME, goto_definition_status_text + " " + progress_indicator_status_text)
# Check again momentarily to see if the operation has completed.
sublime.set_timeout(lambda: self.check_operation(thread, current_progress_indicator_tuple), 100)
class CoffeeGotoDefinitionThread(threading.Thread):
def __init__(self, project_folder_list, current_file_lines, selected_word, excluded_dirs, selected_region):
self.project_folder_list = project_folder_list
self.current_file_lines = current_file_lines
self.selected_word = selected_word
self.excluded_dirs = excluded_dirs
self.selected_region = selected_region
# None if no match was found, or a tuple containing the filename, row, column and match
self.matched_location_tuple = None
threading.Thread.__init__(self)
def run(self):
project_folder_list = self.project_folder_list
current_file_lines = self.current_file_lines
selected_word = self.selected_word
excluded_dirs = self.excluded_dirs
selected_region = self.selected_region
# This will be assigned whem a match is made
matched_location_tuple = None
# The regular expression used to search for the selected class
class_regex = coffee_utils.CLASS_REGEX % re.escape(selected_word)
# The regex used to search for the selected function
function_regex = coffee_utils.FUNCTION_REGEX % re.escape(selected_word)
# The regex used to search for the selected variable assignment
assignment_regex = coffee_utils.ASSIGNMENT_REGEX % re.escape(selected_word)
# The regex used to search for the selected variable as a parameter in a method
param_regex = coffee_utils.PARAM_REGEX.format(name=re.escape(selected_word))
# The regex used to search for the selected variable as a for loop var
for_loop_regex = coffee_utils.FOR_LOOP_REGEX % re.escape(selected_word)
debug(("Selected: \"%s\"" % selected_word))
# ------ CURRENT FILE: CLASS (TitleCaps ONLY) ------------
if not matched_location_tuple:
# If so, we assume it is a class.
debug("Checking for local class %s..." % selected_word)
class_location_search_tuple = coffee_utils.find_location_of_regex_in_files(class_regex, current_file_lines, [])
if class_location_search_tuple:
matched_location_tuple = class_location_search_tuple
# ------ GLOBAL SEARCH: CLASS ----------------------------
if not matched_location_tuple:
# Coffeescript filename regex
coffeescript_filename_regex = coffee_utils.COFFEE_FILENAME_REGEX
# All coffeescript file paths
all_coffee_file_paths = coffee_utils.get_files_in(project_folder_list, coffeescript_filename_regex, excluded_dirs)
debug("Checking globally for class %s..." % selected_word)
# Assume it is a file called selected_word.coffee
exact_file_name_regex = "^" + re.escape(selected_word) + coffee_utils.COFFEE_EXTENSION_WITH_DOT + "$"
exact_name_file_paths = coffee_utils.get_files_in(project_folder_list, exact_file_name_regex, excluded_dirs)
exact_location_search_tuple = coffee_utils.find_location_of_regex_in_files(class_regex, None, exact_name_file_paths)
if exact_location_search_tuple:
matched_location_tuple = exact_location_search_tuple
else:
global_class_location_search_tuple = coffee_utils.find_location_of_regex_in_files(class_regex, None, all_coffee_file_paths)
if global_class_location_search_tuple:
matched_location_tuple = global_class_location_search_tuple
# ------ CURRENT FILE: FUNCTION --------------------------
if not matched_location_tuple:
debug("Checking for local function %s..." % selected_word)
local_function_location_search_tuple = coffee_utils.find_location_of_regex_in_files(function_regex, current_file_lines, [])
if local_function_location_search_tuple:
matched_location_tuple = local_function_location_search_tuple
# ------ CURRENT FILE: ASSIGNMENT ------------------------
if not matched_location_tuple:
debug("Checking for local assignment of %s..." % selected_word)
backwards_match_tuple = coffee_utils.search_backwards_for(current_file_lines, assignment_regex, selected_region)
if backwards_match_tuple:
filename_tuple = tuple([None])
matched_location_tuple = filename_tuple + backwards_match_tuple
else:
# Nothing found. Now let's look backwards for a method parameter
param_match_tuple = coffee_utils.search_backwards_for(current_file_lines, param_regex, selected_region)
if param_match_tuple:
filename_tuple = tuple([None])
matched_location_tuple = filename_tuple + param_match_tuple
else:
for_loop_match_tuple = coffee_utils.search_backwards_for(current_file_lines, for_loop_regex, selected_region)
if for_loop_match_tuple:
filename_tuple = tuple([None])
matched_location_tuple = filename_tuple + for_loop_match_tuple
# Otherwise, forwards search for it. It could be defined in the constructor.
else:
forwards_match_tuple = coffee_utils.find_location_of_regex_in_files(assignment_regex, current_file_lines, [])
if forwards_match_tuple:
matched_location_tuple = forwards_match_tuple
# ------ GLOBAL SEARCH: FUNCTION -------------------------
if not matched_location_tuple:
# Coffeescript filename regex
coffeescript_filename_regex = coffee_utils.COFFEE_FILENAME_REGEX
# All coffeescript file paths
all_coffee_file_paths = coffee_utils.get_files_in(project_folder_list, coffeescript_filename_regex, excluded_dirs)
debug("Checking globally for function %s..." % selected_word)
global_function_location_search_tuple = coffee_utils.find_location_of_regex_in_files(function_regex, None, all_coffee_file_paths)
if global_function_location_search_tuple:
matched_location_tuple = global_function_location_search_tuple
# ------ DOT OPERATION LOOKUP (TBD) ----------------------
# TODO: Pull out dot operator object, determine its assignment type, find class, goto method/property.
# Also, determine where to put this lookup.
# ------ SUPER METHOD LOOKUP (TBD) -----------------------
# TODO: If selected_word is "super", assume a function and then attempt to find
# extending class and open it to the function the cursor is within.
# ------ STORE MATCH RESULTS -----------------------------
# If not None, then we found something that matched the search!
if matched_location_tuple:
self.matched_location_tuple = matched_location_tuple