1156 lines
		
	
	
		
			52 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1156 lines
		
	
	
		
			52 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import sublime
 | |
| import re
 | |
| import os
 | |
| 
 | |
| # TODO:
 | |
| # - Document this file.
 | |
| # - Split out functionality where possible.
 | |
| 
 | |
| # This file is what happens when you code non-stop for several days.
 | |
| # I tried to make the main files as easy to follow along as possible.
 | |
| # This file, not so much.
 | |
| 
 | |
| # Set to true to enable debug output
 | |
| DEBUG = False
 | |
| 
 | |
| SETTINGS_FILE_NAME = "CoffeeComplete Plus.sublime-settings"
 | |
| PREFERENCES_COFFEE_EXCLUDED_DIRS = "coffee_autocomplete_plus_excluded_dirs"
 | |
| PREFERENCES_COFFEE_RESTRICTED_TO_PATHS = "coffee_autocomplete_plus_restricted_to_paths"
 | |
| PREFERENCES_THIS_ALIASES = "coffee_autocomplete_plus_this_aliases"
 | |
| PREFERENCES_MEMBER_EXCLUSION_REGEXES = "coffee_autocomplete_plus_member_exclusion_regexes"
 | |
| BUILT_IN_TYPES_SETTINGS_FILE_NAME = "CoffeeComplete Plus Built-In Types.sublime-settings"
 | |
| BUILT_IN_TYPES_SETTINGS_KEY = "coffee_autocomplete_plus_built_in_types"
 | |
| CUSTOM_TYPES_SETTINGS_FILE_NAME = "CoffeeComplete Plus Custom Types.sublime-settings"
 | |
| CUSTOM_TYPES_SETTINGS_KEY = "coffee_autocomplete_plus_custom_types"
 | |
| FUNCTION_RETURN_TYPES_SETTINGS_KEY = "coffee_autocomplete_plus_function_return_types"
 | |
| FUNCTION_RETURN_TYPE_TYPE_NAME_KEY = "type_name"
 | |
| FUNCTION_RETURN_TYPE_FUNCTION_NAMES_KEY = "function_names"
 | |
| 
 | |
| COFFEESCRIPT_SYNTAX = r"CoffeeScript"
 | |
| COFFEE_EXTENSION_WITH_DOT = "\.coffee|\.litcoffee|\.coffee\.md"
 | |
| CONSTRUCTOR_KEYWORDS = ["constructor", "initialize", "init"]
 | |
| THIS_SUGAR_SYMBOL = "@"
 | |
| THIS_KEYWORD = "this"
 | |
| PERIOD_OPERATOR = "."
 | |
| COFFEE_FILENAME_REGEX = r".+?" + re.escape(COFFEE_EXTENSION_WITH_DOT)
 | |
| CLASS_REGEX = r"class\s+%s((\s*$)|[^a-zA-Z0-9_$])"
 | |
| CLASS_REGEX_ANY = r"class\s+([a-zA-Z0-9_$]+)((\s*$)|[^a-zA-Z0-9_$])"
 | |
| CLASS_REGEX_WITH_EXTENDS = r"class\s+%s\s*($|(\s+extends\s+([a-zA-Z0-9_$.]+)))"
 | |
| SINGLE_LINE_COMMENT_REGEX = r"#.*?$"
 | |
| TYPE_HINT_COMMENT_REGEX = r"#.*?\[([a-zA-Z0-9_$]+)\].*$"
 | |
| TYPE_HINT_PARAMETER_COMMENT_REGEX = r"#.*?(\[([a-zA-Z0-9_$]+)\]\s*{var_name}((\s*$)|[^a-zA-Z0-9_$]))|({var_name}\s*\[([a-zA-Z0-9_$]+)\]((\s*$)|[^a-zA-Z0-9_$]))"
 | |
| # Function regular expression. Matches:
 | |
| # methodName  =   (aas,bsa, casd )    ->
 | |
| FUNCTION_REGEX = r"(^|[^a-zA-Z0-9_$])(%s)\s*[:]\s*(\((.*?)\))?\s*[=\-]>"
 | |
| FUNCTION_REGEX_ANY = r"(^|[^a-zA-Z0-9_$])(([a-zA-Z0-9_$]+))\s*[:]\s*(\((.*?)\))?\s*[=\-]>"
 | |
| # Assignment regular expression. Matches:
 | |
| # asdadasd =
 | |
| ASSIGNMENT_REGEX = r"(^|[^a-zA-Z0-9_$])%s\s*="
 | |
| # Static assignment regex
 | |
| STATIC_ASSIGNMENT_REGEX = r"^\s*([@]|(this\s*[.]))\s*([a-zA-Z0-9_$]+)\s*[:=]"
 | |
| # Static function regex
 | |
| STATIC_FUNCTION_REGEX = r"(^|[^a-zA-Z0-9_$])\s*([@]|(this\s*[.]))\s*([a-zA-Z0-9_$]+)\s*[:]\s*(\((.*?)\))?\s*[=\-]>"
 | |
| # Regex for finding a function parameter. Call format on the string, with name=var_name
 | |
| PARAM_REGEX = r"\(\s*(({name})|({name}\s*=?.*?[,].*?)|(.*?[,]\s*{name}\s*=?.*?[,].*?)|(.*?[,]\s*{name}))\s*=?.*?\)\s*[=\-]>"
 | |
| # Regex for finding a variable declared in a for loop.
 | |
| FOR_LOOP_REGEX = r"for\s*.*?[^a-zA-Z0-9_$]%s[^a-zA-Z0-9_$]"
 | |
| # Regex for constructor @ params, used for type hinting.
 | |
| CONSTRUCTOR_SELF_ASSIGNMENT_PARAM_REGEX = r"(?:(?:constructor)|(?:initialize)|(?:init))\s*[:]\s*\(\s*((@{name})|(@{name}\s*[,].*?)|(.*?[,]\s*@{name}\s*[,].*?)|(.*?[,]\s*@{name}))\s*\)\s*[=\-]>\s*$"
 | |
| 
 | |
| # Assignment with the value it's being assigned to. Matches:
 | |
| # blah = new Dinosaur()
 | |
| ASSIGNMENT_VALUE_WITH_DOT_REGEX = r"(^|[^a-zA-Z0-9_$])%s\s*=\s*(.*)"
 | |
| ASSIGNMENT_VALUE_WITHOUT_DOT_REGEX = r"(^|[^a-zA-Z0-9_$.])%s\s*=\s*(.*)"
 | |
| 
 | |
| # Used to determining what class is being created with the new keyword. Matches:
 | |
| # new Macaroni
 | |
| NEW_OPERATION_REGEX = r"new\s+([a-zA-Z0-9_$.]+)"
 | |
| 
 | |
| PROPERTY_INDICATOR = u'\u25CB'
 | |
| METHOD_INDICATOR = u'\u25CF'
 | |
| INHERITED_INDICATOR = u'\u2C75'
 | |
| 
 | |
| BUILT_IN_TYPES_TYPE_NAME_KEY = "name"
 | |
| BUILT_IN_TYPES_TYPE_ENABLED_KEY = "enabled"
 | |
| BUILT_IN_TYPES_CONSTRUCTOR_KEY = "constructor"
 | |
| BUILT_IN_TYPES_STATIC_PROPERTIES_KEY = "static_properties"
 | |
| BUILT_IN_TYPES_STATIC_PROPERTY_NAME_KEY = "name"
 | |
| BUILT_IN_TYPES_STATIC_METHODS_KEY = "static_methods"
 | |
| BUILT_IN_TYPES_STATIC_METHOD_NAME_KEY = "name"
 | |
| BUILT_IN_TYPES_INSTANCE_PROPERTIES_KEY = "instance_properties"
 | |
| BUILT_IN_TYPES_INSTANCE_PROPERTY_NAME_KEY = "name"
 | |
| BUILT_IN_TYPES_INSTANCE_METHODS_KEY = "instance_methods"
 | |
| BUILT_IN_TYPES_INSTANCE_METHOD_NAME_KEY = "name"
 | |
| BUILT_IN_TYPES_METHOD_NAME_KEY = "name"
 | |
| BUILT_IN_TYPES_METHOD_INSERTION_KEY = "insertion"
 | |
| BUILT_IN_TYPES_METHOD_ARGS_KEY = "args"
 | |
| BUILT_IN_TYPES_METHOD_ARG_NAME_KEY = "name"
 | |
| BUILT_IN_TYPES_INHERITS_FROM_OBJECT_KEY = "inherits_from_object"
 | |
| 
 | |
| 
 | |
| # Utility functions
 | |
| def debug(message):
 | |
|     if DEBUG:
 | |
|         print message
 | |
| 
 | |
| 
 | |
| def select_current_word(view):
 | |
|     if len(view.sel()) > 0:
 | |
|         selected_text = view.sel()[0]
 | |
|         word_region = view.word(selected_text)
 | |
|         view.sel().clear()
 | |
|         view.sel().add(word_region)
 | |
| 
 | |
| 
 | |
| def get_selected_word(view):
 | |
|     word = ""
 | |
|     if len(view.sel()) > 0:
 | |
|         selected_text = view.sel()[0]
 | |
|         word_region = view.word(selected_text)
 | |
|         word = get_word_at(view, word_region)
 | |
|     return word
 | |
| 
 | |
| 
 | |
| def get_word_at(view, region):
 | |
|     word = ""
 | |
|     word_region = view.word(region)
 | |
|     word = view.substr(word_region)
 | |
|     word = re.sub(r'[^a-zA-Z0-9_$]', '', word)
 | |
|     word = word.strip()
 | |
|     return word
 | |
| 
 | |
| 
 | |
| def get_token_at(view, region):
 | |
|     token = ""
 | |
|     if len(view.sel()) > 0:
 | |
|         selected_line = view.line(region)
 | |
|         preceding_text = view.substr(sublime.Region(selected_line.begin(), region.begin())).strip()
 | |
|         token_regex = r"[^a-zA-Z0-9_$.@]*?([a-zA-Z0-9_$.@]+)$"
 | |
|         match = re.search(token_regex, preceding_text)
 | |
|         if match:
 | |
|             token = match.group(1)
 | |
|     token = token.strip()
 | |
|     return token
 | |
| 
 | |
| 
 | |
| def get_preceding_symbol(view, prefix, locations):
 | |
|     index = locations[0]
 | |
|     symbol_region = sublime.Region(index - 1 - len(prefix), index - len(prefix))
 | |
|     symbol = view.substr(symbol_region)
 | |
|     return symbol
 | |
| 
 | |
| 
 | |
| def get_preceding_function_call(view):
 | |
|     function_call = ""
 | |
|     if len(view.sel()) > 0:
 | |
|         selected_text = view.sel()[0]
 | |
|         selected_line = view.line(sublime.Region(selected_text.begin() - 1, selected_text.begin() - 1))
 | |
|         preceding_text = view.substr(sublime.Region(selected_line.begin(), selected_text.begin() - 1)).strip()
 | |
|         function_call_regex = r".*?([a-zA-Z0-9_$]+)\s*\(.*?\)"
 | |
|         match = re.search(function_call_regex, preceding_text)
 | |
|         if match:
 | |
|             function_call = match.group(1)
 | |
|     return function_call
 | |
| 
 | |
| 
 | |
| def get_preceding_token(view):
 | |
|     token = ""
 | |
|     if len(view.sel()) > 0:
 | |
|         selected_text = view.sel()[0]
 | |
|         if selected_text.begin() > 2:
 | |
|             token_region = sublime.Region(selected_text.begin() - 1, selected_text.begin() - 1)
 | |
|             token = get_token_at(view, token_region)
 | |
|     return token
 | |
| 
 | |
| 
 | |
| # Complete this.
 | |
| def get_preceding_call_chain(view):
 | |
|     word = ""
 | |
|     if len(view.sel()) > 0:
 | |
|         selected_text = view.sel()[0]
 | |
|         selected_text = view.sel()[0]
 | |
|         selected_line = view.line(sublime.Region(selected_text.begin() - 1, selected_text.begin() - 1))
 | |
|         preceding_text = view.substr(sublime.Region(selected_line.begin(), selected_text.begin() - 1)).strip()
 | |
|         function_call_regex = r".*?([a-zA-Z0-9_$]+)\s*\(.*?\)"
 | |
|         match = re.search(function_call_regex, preceding_text)
 | |
|         if match:
 | |
|             #function_call = match.group(1)
 | |
|             pass
 | |
|     return word
 | |
| 
 | |
| 
 | |
| def is_capitalized(word):
 | |
|     capitalized = False
 | |
|     # Underscores are sometimes used to indicate an internal property, so we
 | |
|     # find the first occurrence of an a-zA-Z character. If not found, we assume lowercase.
 | |
|     az_word = re.sub("[^a-zA-Z]", "", word)
 | |
|     if len(az_word) > 0:
 | |
|         first_letter = az_word[0]
 | |
|         capitalized = first_letter.isupper()
 | |
| 
 | |
|     # Special case for $
 | |
|     capitalized = capitalized | word.startswith("$")
 | |
| 
 | |
|     return capitalized
 | |
| 
 | |
| 
 | |
| def get_files_in(directory_list, filename_regex, excluded_dirs):
 | |
|     files = []
 | |
|     for next_directory in directory_list:
 | |
|         # http://docs.python.org/2/library/os.html?highlight=os.walk#os.walk
 | |
|         for path, dirs, filenames in os.walk(next_directory):
 | |
|             # print str(path)
 | |
|             for next_excluded_dir in excluded_dirs:
 | |
|                 try:
 | |
|                     dirs.remove(next_excluded_dir)
 | |
|                 except:
 | |
|                     pass
 | |
|             for next_file_name in filenames:
 | |
|                 # http://docs.python.org/2/library/re.html
 | |
|                 match = re.search(filename_regex, next_file_name)
 | |
|                 if match:
 | |
|                     # http://docs.python.org/2/library/os.path.html?highlight=os.path.join#os.path.join
 | |
|                     next_full_path = os.path.join(path, next_file_name)
 | |
|                     files.append(next_full_path)
 | |
|     return files
 | |
| 
 | |
| 
 | |
| def get_lines_for_file(file_path):
 | |
|     lines = []
 | |
|     try:
 | |
|         # http://docs.python.org/2/tutorial/inputoutput.html
 | |
|         opened_file = open(file_path, "r")  # r = read only
 | |
|         lines = opened_file.readlines()
 | |
|     except:
 | |
|         pass
 | |
|     return lines
 | |
| 
 | |
| 
 | |
| # Returns a tuple with (row, column, match, row_start_index), or None
 | |
| def get_positions_of_regex_match_in_file(file_lines, regex):
 | |
|     found_a_match = False
 | |
|     matched_row = -1
 | |
|     matched_column = -1
 | |
|     match_found = None
 | |
|     line_start_index = -1
 | |
| 
 | |
|     current_row = 0
 | |
| 
 | |
|     current_line_start_index = 0
 | |
|     for next_line in file_lines:
 | |
|         # Remove comments
 | |
|         modified_next_line = re.sub(SINGLE_LINE_COMMENT_REGEX, "", next_line)
 | |
|         match = re.search(regex, modified_next_line)
 | |
|         if match:
 | |
|             found_a_match = True
 | |
|             matched_row = current_row
 | |
|             matched_column = match.end()
 | |
|             match_found = match
 | |
|             line_start_index = current_line_start_index
 | |
|             break
 | |
|         current_row = current_row + 1
 | |
|         current_line_start_index = current_line_start_index + len(next_line)
 | |
| 
 | |
|     positions_tuple = None
 | |
|     if found_a_match:
 | |
|         positions_tuple = (matched_row, matched_column, match_found, line_start_index)
 | |
| 
 | |
|     return positions_tuple
 | |
| 
 | |
| 
 | |
| def open_file_at_position(window, file_path, row, column):
 | |
|     # Beef
 | |
|     # http://www.sublimetext.com/docs/2/api_reference.html#sublime.Window
 | |
|     path_with_position_encoding = file_path + ":" + str(row) + ":" + str(column)
 | |
|     window.open_file(path_with_position_encoding, sublime.ENCODED_POSITION)
 | |
|     return
 | |
| 
 | |
| 
 | |
| # Returns a tuple with (file_path, row, column, match, row_start_index)
 | |
| def find_location_of_regex_in_files(contents_regex, local_file_lines, global_file_path_list=[]):
 | |
|     # The match tuple containing the filename and positions.
 | |
|     # Will be returned as None if no matches are found.
 | |
|     file_match_tuple = None
 | |
| 
 | |
|     if local_file_lines:
 | |
|         # Search the file for the regex.
 | |
|         positions_tuple = get_positions_of_regex_match_in_file(local_file_lines, contents_regex)
 | |
|         if positions_tuple:
 | |
|             # We've found a match! Save the file path plus the positions and the match itself
 | |
|             file_match_tuple = tuple([None]) + positions_tuple
 | |
| 
 | |
|     # If we are to search globally...
 | |
|     if not file_match_tuple and global_file_path_list:
 | |
|         for next_file_path in global_file_path_list:
 | |
|             if next_file_path:
 | |
|                 file_lines = get_lines_for_file(next_file_path)
 | |
|                 # Search the file for the regex.
 | |
|                 positions_tuple = get_positions_of_regex_match_in_file(file_lines, contents_regex)
 | |
|                 if positions_tuple:
 | |
|                     # We've found a match! Save the file path plus the positions and the match itself
 | |
|                     file_match_tuple = tuple([next_file_path]) + positions_tuple
 | |
|                     # Stop the for loop
 | |
|                     break
 | |
|     return file_match_tuple
 | |
| 
 | |
| 
 | |
| def select_region_in_view(view, region):
 | |
|     view.sel().clear()
 | |
|     view.sel().add(region)
 | |
|     # Refresh hack.
 | |
|     original_position = view.viewport_position()
 | |
|     view.set_viewport_position((original_position[0], original_position[1] + 1))
 | |
|     view.set_viewport_position(original_position)
 | |
| 
 | |
| 
 | |
| def get_progress_indicator_tuple(previous_indicator_tuple):
 | |
|     STATUS_MESSAGE_PROGRESS_INDICATOR = "[%s=%s]"
 | |
|     if not previous_indicator_tuple:
 | |
|         previous_indicator_tuple = ("", 0, 1)
 | |
|     progress_indicator_position = previous_indicator_tuple[1]
 | |
|     progress_indicator_direction = previous_indicator_tuple[2]
 | |
|     # This animates a little activity indicator in the status area.
 | |
|     # It animates an equals symbol bouncing back and fourth between square brackets.
 | |
|     # We calculate the padding around the equal based on the last known position.
 | |
|     num_spaces_before = progress_indicator_position % 8
 | |
|     num_spaces_after = (7) - num_spaces_before
 | |
|     # When the equals hits the edge, we change directions.
 | |
|     # Direction is -1 for moving left and 1 for moving right.
 | |
|     if not num_spaces_after:
 | |
|         progress_indicator_direction = -1
 | |
|     if not num_spaces_before:
 | |
|         progress_indicator_direction = 1
 | |
|     progress_indicator_position += progress_indicator_direction
 | |
|     padding_before = ' ' * num_spaces_before
 | |
|     padding_after = ' ' * num_spaces_after
 | |
|     # Create the progress indication text
 | |
|     progress_indicator_text = STATUS_MESSAGE_PROGRESS_INDICATOR % (padding_before, padding_after)
 | |
|     # Return the progress indication tuple
 | |
|     return (progress_indicator_text, progress_indicator_position, progress_indicator_direction)
 | |
| 
 | |
| 
 | |
| def get_syntax_name(view):
 | |
|     syntax = os.path.splitext(os.path.basename(view.settings().get('syntax')))[0]
 | |
|     return syntax
 | |
| 
 | |
| 
 | |
| def is_coffee_syntax(view):
 | |
|     return bool(re.match(COFFEESCRIPT_SYNTAX, get_syntax_name(view)))
 | |
| 
 | |
| 
 | |
| def get_this_type(file_lines, start_region):
 | |
| 
 | |
|     type_found = None
 | |
|     # Search backwards from current position for the type
 | |
|     # We're looking for a class definition
 | |
|     class_regex = CLASS_REGEX_ANY
 | |
| 
 | |
|     match_tuple = search_backwards_for(file_lines, class_regex, start_region)
 | |
|     if match_tuple:
 | |
|         # debug(str(match_tuple[0]) + ", " + str(match_tuple[1]) + ", " + match_tuple[2].group(1))
 | |
|         type_found = match_tuple[2].group(1)
 | |
|     else:
 | |
|         debug("No match!")
 | |
| 
 | |
|     return type_found
 | |
| 
 | |
| 
 | |
| def get_variable_type(file_lines, token, start_region, global_file_path_list, built_in_types, previous_variable_names=[]):
 | |
| 
 | |
|     type_found = None
 | |
| 
 | |
|     # Check for "this"
 | |
|     if token == "this":
 | |
|         type_found = get_this_type(file_lines, start_region)
 | |
|     elif token.startswith("@"):
 | |
|         token = "this." + token[1:]
 | |
| 
 | |
|     # We're looking for a variable assignent
 | |
|     assignment_regex = ASSIGNMENT_VALUE_WITH_DOT_REGEX % token
 | |
| 
 | |
|     # print "Assignment regex: " + assignment_regex
 | |
| 
 | |
|     # Search backwards from current position for the type
 | |
|     if not type_found:
 | |
|         match_tuple = search_backwards_for(file_lines, assignment_regex, start_region)
 | |
|         if match_tuple:
 | |
|             type_found = get_type_from_assignment_match_tuple(token, match_tuple, file_lines, previous_variable_names)
 | |
|             # Well, we found the assignment. But we don't know what it is.
 | |
|             # Let's try to find a variable name and get THAT variable type...
 | |
|             if not type_found:
 | |
|                 type_found = get_type_from_assigned_variable_name(file_lines, token, match_tuple, global_file_path_list, built_in_types, previous_variable_names)
 | |
| 
 | |
|     # Let's try searching backwards for parameter hints in comments...
 | |
|     if not type_found:
 | |
|         # The regex used to search for the variable as a parameter in a method
 | |
|         param_regex = PARAM_REGEX.format(name=re.escape(token))
 | |
|         match_tuple = search_backwards_for(file_lines, param_regex, start_region)
 | |
|         # We found the variable! it's a parameter. Let's find a comment with a type hint.
 | |
|         if match_tuple:
 | |
|             type_found = get_type_from_parameter_match_tuple(token, match_tuple, file_lines, previous_variable_names)
 | |
| 
 | |
|     # If backwards searching isn't working, at least try to find something...
 | |
|     if not type_found:
 | |
|         # Forward search from beginning for assignment:
 | |
|         match_tuple = get_positions_of_regex_match_in_file(file_lines, assignment_regex)
 | |
|         if match_tuple:
 | |
|             type_found = get_type_from_assignment_match_tuple(token, match_tuple, file_lines, previous_variable_names)
 | |
|             if not type_found:
 | |
|                 type_found = get_type_from_assigned_variable_name(file_lines, token, match_tuple, global_file_path_list, built_in_types, previous_variable_names)
 | |
| 
 | |
|     # If still nothing, maybe it's an @ parameter in the constructor?
 | |
|     if not type_found:
 | |
| 
 | |
|         # Get the last word in the chain, if it's a chain.
 | |
|         # E.g. Get variableName from this.variableName.[autocomplete]
 | |
|         selected_word = token[token.rfind(".") + 1:]
 | |
| 
 | |
|         if token.startswith(THIS_KEYWORD + ".") or token.startswith(THIS_SUGAR_SYMBOL):
 | |
| 
 | |
|             # The regex used to search for the variable as a parameter in a method
 | |
|             param_regex = CONSTRUCTOR_SELF_ASSIGNMENT_PARAM_REGEX.format(name=re.escape(selected_word))
 | |
| 
 | |
|             # Forward search from beginning for param:
 | |
|             match_tuple = get_positions_of_regex_match_in_file(file_lines, param_regex)
 | |
|             # We found the variable! it's a parameter. Let's find a comment with a type hint.
 | |
|             if match_tuple:
 | |
|                 type_found = get_type_from_parameter_match_tuple(selected_word, match_tuple, file_lines)
 | |
| 
 | |
|         if not type_found:
 | |
|             # Find something. Anything!
 | |
|             word_assignment_regex = ASSIGNMENT_VALUE_WITHOUT_DOT_REGEX % selected_word
 | |
| 
 | |
|             # Forward search from beginning for assignment:
 | |
|             match_tuple = get_positions_of_regex_match_in_file(file_lines, word_assignment_regex)
 | |
|             if match_tuple:
 | |
|                 type_found = get_type_from_assignment_match_tuple(token, match_tuple, file_lines, previous_variable_names)
 | |
|                 if not type_found:
 | |
|                     type_found = get_type_from_assigned_variable_name(file_lines, token, match_tuple, global_file_path_list, built_in_types, previous_variable_names)
 | |
| 
 | |
|     return type_found
 | |
| 
 | |
| 
 | |
| def get_type_from_assigned_variable_name(file_lines, token, match_tuple, global_file_path_list, built_in_types, previous_variable_names=[]):
 | |
| 
 | |
|     type_found = None
 | |
| 
 | |
|     assignment_value_string = match_tuple[2].group(2).strip()
 | |
|     # row start index + column index
 | |
|     token_index = match_tuple[3] + match_tuple[1]
 | |
|     token_region = sublime.Region(token_index, token_index)
 | |
|     token_match = re.search(r"^([a-zA-Z0-9_$.]+)$", assignment_value_string)
 | |
|     if token_match:
 | |
|         next_token = token_match.group(1)
 | |
|         if next_token not in previous_variable_names:
 | |
|             previous_variable_names.append(token)
 | |
|             type_found = get_variable_type(file_lines, next_token, token_region, global_file_path_list, built_in_types, previous_variable_names)
 | |
| 
 | |
|     # Determine what type a method returns
 | |
|     if not type_found:
 | |
|         # print "assignment_value_string: " + assignment_value_string
 | |
|         method_call_regex = r"([a-zA-Z0-9_$.]+)\s*[.]\s*([a-zA-Z0-9_$]+)\s*\("
 | |
|         method_call_match = re.search(method_call_regex, assignment_value_string)
 | |
|         if method_call_match:
 | |
|             object_name = method_call_match.group(1)
 | |
|             method_name = method_call_match.group(2)
 | |
|             object_type = get_variable_type(file_lines, object_name, token_region, global_file_path_list, built_in_types, previous_variable_names)
 | |
|             if object_type:
 | |
|                 type_found = get_return_type_for_method(object_type, method_name, file_lines, global_file_path_list, built_in_types)
 | |
| 
 | |
|     return type_found
 | |
| 
 | |
| 
 | |
| def get_return_type_for_method(object_type, method_name, file_lines, global_file_path_list, built_in_types):
 | |
| 
 | |
|     type_found = None
 | |
| 
 | |
|     next_class_to_scan = object_type
 | |
| 
 | |
|     # Search the class and all super classes
 | |
|     while next_class_to_scan and not type_found:
 | |
| 
 | |
|         class_regex = CLASS_REGEX % re.escape(next_class_to_scan)
 | |
|         # (file_path, row, column, match, row_start_index)
 | |
|         class_location_search_tuple = find_location_of_regex_in_files(class_regex, file_lines, global_file_path_list)
 | |
|         if class_location_search_tuple:
 | |
| 
 | |
|             file_found = class_location_search_tuple[0]
 | |
| 
 | |
|             # Consider if it was found locally, in the view
 | |
|             if not file_found:
 | |
|                 class_file_lines = file_lines
 | |
|             else:
 | |
|                 class_file_lines = get_lines_for_file(file_found)
 | |
| 
 | |
|             # If found, search for the method in question.
 | |
|             method_regex = FUNCTION_REGEX % re.escape(method_name)
 | |
|             positions_tuple = get_positions_of_regex_match_in_file(class_file_lines, method_regex)
 | |
|             # (row, column, match, row_start_index)
 | |
|             if positions_tuple:
 | |
|                 # Check for comments, and hopefully the return hint, on previous rows.
 | |
|                 matched_row = positions_tuple[0]
 | |
|                 row_to_check_index = matched_row - 1
 | |
| 
 | |
|                 non_comment_code_reached = False
 | |
|                 while not non_comment_code_reached and row_to_check_index >= 0 and not type_found:
 | |
|                     current_row_text = class_file_lines[row_to_check_index]
 | |
| 
 | |
|                     # Make sure this line only contains comments.
 | |
|                     mod_line = re.sub(SINGLE_LINE_COMMENT_REGEX, "", current_row_text).strip()
 | |
|                     # If it wasn't just a comment line...
 | |
|                     if len(mod_line) > 0:
 | |
|                         non_comment_code_reached = True
 | |
|                     else:
 | |
|                         # Search for hint: @return [TYPE]
 | |
|                         return_type_hint_regex = r"@return\s*\[([a-zA-Z0-9_$]+)\]"
 | |
|                         hint_match = re.search(return_type_hint_regex, current_row_text)
 | |
|                         if hint_match:
 | |
|                             # We found it!
 | |
|                             type_found = hint_match.group(1)
 | |
|                     row_to_check_index = row_to_check_index - 1
 | |
| 
 | |
|             # If nothing was found, see if the class extends another one and is inheriting the method.
 | |
|             if not type_found:
 | |
|                 extends_regex = CLASS_REGEX_WITH_EXTENDS % next_class_to_scan
 | |
|                 # (row, column, match, row_start_index)
 | |
|                 extends_match_positions = get_positions_of_regex_match_in_file(class_file_lines, extends_regex)
 | |
|                 if extends_match_positions:
 | |
|                     extends_match = extends_match_positions[2]
 | |
|                     next_class_to_scan = extends_match.group(3)
 | |
|                 else:
 | |
|                     next_class_to_scan = None
 | |
|     return type_found
 | |
| 
 | |
| 
 | |
| def get_type_from_assignment_match_tuple(variable_name, match_tuple, file_lines, previous_variable_names=[]):
 | |
| 
 | |
|     type_found = None
 | |
|     if match_tuple:
 | |
|         match = match_tuple[2]
 | |
|         assignment_value_string = match.group(2)
 | |
|         # Check for a type hint on current row or previous row.
 | |
|         # These will override anything else.
 | |
|         matched_row = match_tuple[0]
 | |
|         previous_row = matched_row - 1
 | |
|         current_row_text = file_lines[matched_row]
 | |
|         hint_match = re.search(TYPE_HINT_COMMENT_REGEX, current_row_text)
 | |
|         if hint_match:
 | |
|             type_found = hint_match.group(1)
 | |
|         if not type_found and previous_row >= 0:
 | |
|             previous_row_text = file_lines[previous_row]
 | |
|             hint_match = re.search(TYPE_HINT_COMMENT_REGEX, previous_row_text)
 | |
|             if hint_match:
 | |
|                 type_found = hint_match.group(1)
 | |
|         if not type_found:
 | |
|             assignment_value_string = re.sub(SINGLE_LINE_COMMENT_REGEX, "", assignment_value_string).strip()
 | |
|             type_found = get_type_from_assignment_value(assignment_value_string)
 | |
|     return type_found
 | |
| 
 | |
| 
 | |
| def get_type_from_parameter_match_tuple(variable_name, match_tuple, file_lines, previous_variable_names=[]):
 | |
| 
 | |
|     type_found = None
 | |
|     if match_tuple:
 | |
|         # Check for comments, and hopefully type hints, on previous rows.
 | |
|         matched_row = match_tuple[0]
 | |
|         row_to_check_index = matched_row - 1
 | |
| 
 | |
|         non_comment_code_reached = False
 | |
|         while not non_comment_code_reached and row_to_check_index >= 0 and not type_found:
 | |
|             current_row_text = file_lines[row_to_check_index]
 | |
| 
 | |
|             # Make sure this line only contains comments.
 | |
|             mod_line = re.sub(SINGLE_LINE_COMMENT_REGEX, "", current_row_text).strip()
 | |
|             # If it wasn't just a comment line...
 | |
|             if len(mod_line) > 0:
 | |
|                 non_comment_code_reached = True
 | |
|             else:
 | |
|                 # It's a comment. Let's look for a type hint in the form:
 | |
|                 # variable_name [TYPE] ~OR~ [TYPE] variable_name
 | |
|                 hint_regex = TYPE_HINT_PARAMETER_COMMENT_REGEX.format(var_name=re.escape(variable_name))
 | |
|                 hint_match = re.search(hint_regex, current_row_text)
 | |
|                 if hint_match:
 | |
|                     # One of these two groups contains the type...
 | |
|                     if hint_match.group(2):
 | |
|                         type_found = hint_match.group(2)
 | |
|                     else:
 | |
|                         type_found = hint_match.group(6)
 | |
|             row_to_check_index = row_to_check_index - 1
 | |
|     return type_found
 | |
| 
 | |
| 
 | |
| def get_type_from_assignment_value(assignment_value_string):
 | |
|     determined_type = None
 | |
| 
 | |
|     assignment_value_string = assignment_value_string.strip()
 | |
| 
 | |
|     # Check for built in types
 | |
|     object_regex = r"^\{.*\}$"
 | |
|     if not determined_type:
 | |
|         match = re.search(object_regex, assignment_value_string)
 | |
|         if match:
 | |
|             determined_type = "Object"
 | |
|     double_quote_string_regex = r"(^\".*\"$)|(^.*?\+\s*\".*?\"$)|(^\".*?\"\s*\+.*?$)|(^.*?\s*\+\s*\".*?\"\s*\+\s*.*?$)"
 | |
|     if not determined_type:
 | |
|         match = re.search(double_quote_string_regex, assignment_value_string)
 | |
|         if match:
 | |
|             determined_type = "String"
 | |
|     single_quote_string_regex = r"(^['].*[']$)|(^.*?\+\s*['].*?[']$)|(^['].*?[']\s*\+.*?$)|(^.*?\s*\+\s*['].*?[']\s*\+\s*.*?$)"
 | |
|     if not determined_type:
 | |
|         match = re.search(single_quote_string_regex, assignment_value_string)
 | |
|         if match:
 | |
|             determined_type = "String"
 | |
|     array_regex = r"^\[.*\]\s*$"
 | |
|     if not determined_type:
 | |
|         match = re.search(array_regex, assignment_value_string)
 | |
|         if match:
 | |
|             determined_type = "Array"
 | |
|     boolean_regex = r"^(true)|(false)$"
 | |
|     if not determined_type:
 | |
|         match = re.search(boolean_regex, assignment_value_string)
 | |
|         if match:
 | |
|             determined_type = "Boolean"
 | |
|     # http://stackoverflow.com/questions/4703390/how-to-extract-a-floating-number-from-a-string-in-python
 | |
|     number_regex = r"^[-+]?\d*\.\d+|\d+$"
 | |
|     if not determined_type:
 | |
|         match = re.search(number_regex, assignment_value_string)
 | |
|         if match:
 | |
|             determined_type = "Number"
 | |
|     regexp_regex = r"^/.*/[a-z]*$"
 | |
|     if not determined_type:
 | |
|         match = re.search(regexp_regex, assignment_value_string)
 | |
|         if match:
 | |
|             determined_type = "RegExp"
 | |
|     new_operation_regex = NEW_OPERATION_REGEX
 | |
|     if not determined_type:
 | |
|         match = re.search(new_operation_regex, assignment_value_string)
 | |
|         if match:
 | |
|             determined_type = get_class_from_end_of_chain(match.group(1))
 | |
| 
 | |
|     return determined_type
 | |
| 
 | |
| 
 | |
| # Tuple returned: (matched_row, matched_column, match, row_start_index)
 | |
| def search_backwards_for(file_lines, regex, start_region):
 | |
| 
 | |
|     matched_row = -1
 | |
|     matched_column = -1
 | |
|     match_found = None
 | |
|     row_start_index = -1
 | |
| 
 | |
|     start_index = start_region.begin()
 | |
|     # debug("start: " + str(start_index))
 | |
|     characters_consumed = 0
 | |
|     start_line = -1
 | |
|     indentation_size = 0
 | |
|     current_line_index = 0
 | |
|     for next_line in file_lines:
 | |
|         # Find the line we're starting on...
 | |
|         offset = start_index - characters_consumed
 | |
|         if offset <= len(next_line) + 1:
 | |
|             # debug("Start line: " + next_line)
 | |
|             characters_consumed = characters_consumed + len(next_line)
 | |
|             indentation_size = get_indentation_size(next_line)
 | |
|             start_line = current_line_index
 | |
|             break
 | |
| 
 | |
|         characters_consumed = characters_consumed + len(next_line)
 | |
|         current_line_index = current_line_index + 1
 | |
| 
 | |
|     row_start_index = characters_consumed
 | |
| 
 | |
|     if start_line >= 0:
 | |
|         # debug("start line: " + str(start_line))
 | |
|         # Go backwards, searching for the class definition.
 | |
|         for i in reversed(range(start_line + 1)):
 | |
|             previous_line = file_lines[i]
 | |
|             # print "Next line: " + previous_line[:-1]
 | |
|             row_start_index = row_start_index - len(previous_line)
 | |
|             # debug("Line " + str(i) + ": " + re.sub("\n", "", previous_line))
 | |
|             # Returns -1 for empty lines or lines with comments only.
 | |
|             next_line_indentation = get_indentation_size(previous_line)
 | |
|             #debug("Seeking <= indentation_size: " + str(indentation_size) + ", Current: " + str(next_line_indentation))
 | |
|             # Ignore lines with larger indentation sizes and empty lines (or lines with comments only)
 | |
|             if next_line_indentation >= 0 and next_line_indentation <= indentation_size:
 | |
|                 indentation_size = next_line_indentation
 | |
|                 # Check for the class
 | |
|                 match = re.search(regex, previous_line)
 | |
|                 if match:
 | |
|                     matched_row = i
 | |
|                     matched_column = match.end()
 | |
|                     match_found = match
 | |
|                     break
 | |
|     match_tuple = None
 | |
|     if match_found:
 | |
|         match_tuple = (matched_row, matched_column, match_found, row_start_index)
 | |
|     return match_tuple
 | |
| 
 | |
| 
 | |
| def get_indentation_size(line_of_text):
 | |
|     size = -1
 | |
|     mod_line = re.sub("\n", "", line_of_text)
 | |
|     mod_line = re.sub(SINGLE_LINE_COMMENT_REGEX, "", mod_line)
 | |
|     # If it wasn't just a comment line...
 | |
|     if len(mod_line.strip()) > 0:
 | |
|         mod_line = re.sub(r"[^\t ].*", "", mod_line)
 | |
|         size = len(mod_line)
 | |
|     # debug("Indent size [" + str(size) + "]:\n" + re.sub("\n", "", line_of_text))
 | |
|     return size
 | |
| 
 | |
| 
 | |
| def get_completions_for_class(class_name, search_statically, local_file_lines, prefix, global_file_path_list, built_in_types, member_exclusion_regexes, show_private):
 | |
| 
 | |
|     # TODO: Use prefix to make suggestions.
 | |
| 
 | |
|     completions = []
 | |
|     scanned_classes = []
 | |
|     original_class_name_found = False
 | |
| 
 | |
|     function_completions = []
 | |
|     object_completions = []
 | |
| 
 | |
|     # First, determine if it is a built in type and return those completions...
 | |
|     # Built-in types include String, Number, etc, and are configurable in settings.
 | |
|     for next_built_in_type in built_in_types:
 | |
|         try:
 | |
|             if next_built_in_type[BUILT_IN_TYPES_TYPE_ENABLED_KEY]:
 | |
|                 next_class_name = next_built_in_type[BUILT_IN_TYPES_TYPE_NAME_KEY]
 | |
|                 if next_class_name == class_name:
 | |
|                     # We are looking at a built-in type! Collect completions for it...
 | |
|                     completions = get_completions_for_built_in_type(next_built_in_type, search_statically, False, member_exclusion_regexes)
 | |
|                     original_class_name_found = True
 | |
|                 elif next_class_name == "Function" and not function_completions:
 | |
|                     function_completions = get_completions_for_built_in_type(next_built_in_type, False, True, member_exclusion_regexes)
 | |
|                 elif next_class_name == "Object" and not object_completions:
 | |
|                     object_completions = get_completions_for_built_in_type(next_built_in_type, False, True, member_exclusion_regexes)
 | |
|         except Exception, e:
 | |
|             print repr(e)
 | |
| 
 | |
|     # If we didn't find completions for a built-in type, look further...
 | |
|     if not completions:
 | |
|         current_class_name = class_name
 | |
|         is_inherited = False
 | |
|         while current_class_name and current_class_name not in scanned_classes:
 | |
|             # print "Scanning " + current_class_name + "..."
 | |
|             # (class_found, completions, next_class_to_scan)
 | |
|             completion_tuple = (False, [], None)
 | |
|             if local_file_lines:
 | |
|                 # print "Searching locally..."
 | |
|                 # Search in local file.
 | |
|                 if search_statically:
 | |
|                     completion_tuple = collect_static_completions_from_file(local_file_lines, current_class_name, is_inherited, member_exclusion_regexes, show_private)
 | |
|                 else:
 | |
|                     completion_tuple = collect_instance_completions_from_file(local_file_lines, current_class_name, is_inherited, member_exclusion_regexes, show_private)
 | |
| 
 | |
|             # Search globally if nothing found and not local only...
 | |
|             if global_file_path_list and (not completion_tuple or not completion_tuple[0]):
 | |
|                 class_regex = CLASS_REGEX % re.escape(current_class_name)
 | |
|                 global_class_location_search_tuple = find_location_of_regex_in_files(class_regex, None, global_file_path_list)
 | |
|                 if global_class_location_search_tuple:
 | |
|                     # If found, perform Class method collection.
 | |
|                     file_to_open = global_class_location_search_tuple[0]
 | |
|                     class_file_lines = get_lines_for_file(file_to_open)
 | |
|                     if search_statically:
 | |
|                         completion_tuple = collect_static_completions_from_file(class_file_lines, current_class_name, is_inherited, member_exclusion_regexes, show_private)
 | |
|                     else:
 | |
|                         completion_tuple = collect_instance_completions_from_file(class_file_lines, current_class_name, is_inherited, member_exclusion_regexes, show_private)
 | |
| 
 | |
|             if current_class_name == class_name and completion_tuple[0]:
 | |
|                 original_class_name_found = True
 | |
| 
 | |
|             # print "Tuple: " + str(completion_tuple)
 | |
|             completions.extend(completion_tuple[1])
 | |
|             scanned_classes.append(current_class_name)
 | |
|             current_class_name = completion_tuple[2]
 | |
|             is_inherited = True
 | |
| 
 | |
|     if original_class_name_found:
 | |
|         # Add Object completions (if available) -- Everything is an Object
 | |
|         completions.extend(object_completions)
 | |
|         if search_statically:
 | |
|             completions.extend(function_completions)
 | |
| 
 | |
|     # Remove all duplicates
 | |
|     completions = list(set(completions))
 | |
|     # Sort
 | |
|     completions.sort()
 | |
|     return completions
 | |
| 
 | |
| 
 | |
| def case_insensitive_startswith(original_string, prefix):
 | |
|     return original_string.lower().startswith(prefix.lower())
 | |
| 
 | |
| 
 | |
| def get_completions_for_built_in_type(built_in_type, is_static, is_inherited, member_exclusion_regexes):
 | |
|     completions = []
 | |
|     if is_static:
 | |
|         static_properties = []
 | |
|         static_property_objs = built_in_type[BUILT_IN_TYPES_STATIC_PROPERTIES_KEY]
 | |
|         for next_static_property_obj in static_property_objs:
 | |
|             next_static_property = next_static_property_obj[BUILT_IN_TYPES_STATIC_PROPERTY_NAME_KEY]
 | |
|             if not is_member_excluded(next_static_property, member_exclusion_regexes):
 | |
|                 static_properties.append(next_static_property)
 | |
|         for next_static_property in static_properties:
 | |
|             next_completion = get_property_completion_tuple(next_static_property, is_inherited)
 | |
|             completions.append(next_completion)
 | |
| 
 | |
|         static_methods = built_in_type[BUILT_IN_TYPES_STATIC_METHODS_KEY]
 | |
|         for next_static_method in static_methods:
 | |
|             method_name = next_static_method[BUILT_IN_TYPES_METHOD_NAME_KEY]
 | |
|             if not is_member_excluded(method_name, member_exclusion_regexes):
 | |
|                 method_args = []
 | |
|                 method_insertions = []
 | |
|                 method_args_objs = next_static_method[BUILT_IN_TYPES_METHOD_ARGS_KEY]
 | |
|                 for next_method_arg_obj in method_args_objs:
 | |
|                     method_arg = next_method_arg_obj[BUILT_IN_TYPES_METHOD_ARG_NAME_KEY]
 | |
|                     method_args.append(method_arg)
 | |
|                     method_insertion = method_arg
 | |
|                     try:
 | |
|                         method_insertion = next_method_arg_obj[BUILT_IN_TYPES_METHOD_INSERTION_KEY]
 | |
|                     except:
 | |
|                         pass
 | |
|                     method_insertions.append(method_insertion)
 | |
|                 next_completion = get_method_completion_tuple(method_name, method_args, method_insertions, is_inherited)
 | |
|                 completions.append(next_completion)
 | |
|     else:
 | |
|         instance_properties = []
 | |
|         instance_property_objs = built_in_type[BUILT_IN_TYPES_INSTANCE_PROPERTIES_KEY]
 | |
|         for next_instance_property_obj in instance_property_objs:
 | |
|             next_instance_property = next_instance_property_obj[BUILT_IN_TYPES_INSTANCE_PROPERTY_NAME_KEY]
 | |
|             if not is_member_excluded(next_instance_property, member_exclusion_regexes):
 | |
|                 instance_properties.append(next_instance_property_obj[BUILT_IN_TYPES_INSTANCE_PROPERTY_NAME_KEY])
 | |
|         for next_instance_property in instance_properties:
 | |
|             next_completion = get_property_completion_tuple(next_instance_property, is_inherited)
 | |
|             completions.append(next_completion)
 | |
| 
 | |
|         instance_methods = built_in_type[BUILT_IN_TYPES_INSTANCE_METHODS_KEY]
 | |
|         for next_instance_method in instance_methods:
 | |
|             method_name = next_instance_method[BUILT_IN_TYPES_METHOD_NAME_KEY]
 | |
|             if not is_member_excluded(method_name, member_exclusion_regexes):
 | |
|                 method_args = []
 | |
|                 method_insertions = []
 | |
|                 method_args_objs = next_instance_method[BUILT_IN_TYPES_METHOD_ARGS_KEY]
 | |
|                 for next_method_arg_obj in method_args_objs:
 | |
|                     method_arg = next_method_arg_obj[BUILT_IN_TYPES_METHOD_ARG_NAME_KEY]
 | |
|                     method_args.append(method_arg)
 | |
|                     method_insertion = method_arg
 | |
|                     try:
 | |
|                         method_insertion = next_method_arg_obj[BUILT_IN_TYPES_METHOD_INSERTION_KEY]
 | |
|                     except:
 | |
|                         pass
 | |
|                     method_insertions.append(method_insertion)
 | |
|                 next_completion = get_method_completion_tuple(method_name, method_args, method_insertions, is_inherited)
 | |
|                 completions.append(next_completion)
 | |
|     return completions
 | |
| 
 | |
| 
 | |
| def collect_instance_completions_from_file(file_lines, class_name, is_inherited, member_exclusion_regexes, show_private):
 | |
| 
 | |
|     completions = []
 | |
|     extended_class = None
 | |
|     class_found = False
 | |
| 
 | |
|     property_completions = []
 | |
|     function_completions = []
 | |
| 
 | |
|     class_and_extends_regex = CLASS_REGEX_WITH_EXTENDS % class_name
 | |
| 
 | |
|     # Find class in file lines
 | |
|     match_tuple = get_positions_of_regex_match_in_file(file_lines, class_and_extends_regex)
 | |
|     if match_tuple:
 | |
|         class_found = True
 | |
|         row = match_tuple[0]
 | |
|         match = match_tuple[2]
 | |
| 
 | |
|         extended_class = match.group(3)
 | |
|         if extended_class:
 | |
|             extended_class = get_class_from_end_of_chain(extended_class)
 | |
| 
 | |
|         # If anything is equal to this after the first line, stop looking.
 | |
|         # At that point, the class definition has ended.
 | |
|         indentation_size = get_indentation_size(file_lines[row])
 | |
|         # print str(indentation_size) + ": " + file_lines[row]
 | |
|         # Let's dig for some info on this class!
 | |
|         if row + 1 < len(file_lines):
 | |
|             inside_constructor = False
 | |
|             constructor_indentation = -1
 | |
|             for row_index in range(row + 1, len(file_lines)):
 | |
|                 next_row = file_lines[row_index]
 | |
|                 next_indentation = get_indentation_size(next_row)
 | |
|                 # print str(next_indentation) + ": " + next_row
 | |
|                 if next_indentation >= 0:
 | |
|                     if next_indentation > indentation_size:
 | |
|                         if inside_constructor and next_indentation <= constructor_indentation:
 | |
|                             inside_constructor = False
 | |
|                         if inside_constructor:
 | |
|                             this_assignment_regex = "([@]|(this\s*[.]))\s*([a-zA-Z0-9_$]+)\s*="
 | |
|                             match = re.search(this_assignment_regex, next_row)
 | |
|                             if match:
 | |
|                                 prop = match.group(3)
 | |
|                                 if show_private or not is_member_excluded(prop, member_exclusion_regexes):
 | |
|                                     prop_completion_alias = get_property_completion_alias(prop, is_inherited)
 | |
|                                     prop_completion_insertion = get_property_completion_insertion(prop)
 | |
|                                     prop_completion = (prop_completion_alias, prop_completion_insertion)
 | |
|                                     if prop_completion not in property_completions:
 | |
|                                         property_completions.append(prop_completion)
 | |
|                         else:  # Not in constructor
 | |
|                             # Look for method definitions
 | |
|                             function_regex = FUNCTION_REGEX_ANY
 | |
|                             match = re.search(function_regex, next_row)
 | |
|                             if match and not re.search(STATIC_FUNCTION_REGEX, next_row):
 | |
|                                 function_name = match.group(2)
 | |
|                                 function_args_string = match.group(5)
 | |
|                                 if show_private or not is_member_excluded(function_name, member_exclusion_regexes):
 | |
|                                     if not function_name in CONSTRUCTOR_KEYWORDS:
 | |
|                                         function_args_list = []
 | |
|                                         if function_args_string:
 | |
|                                             function_args_list = function_args_string.split(",")
 | |
|                                         for i in range(len(function_args_list)):
 | |
|                                             # Fix each one up...
 | |
|                                             next_arg = function_args_list[i]
 | |
|                                             next_arg = next_arg.strip()
 | |
|                                             next_arg = re.sub("[^a-zA-Z0-9_$].*", "", next_arg)
 | |
|                                             function_args_list[i] = re.sub(THIS_SUGAR_SYMBOL, "", next_arg)
 | |
|                                         function_alias = get_method_completion_alias(function_name, function_args_list, is_inherited)
 | |
|                                         function_insertion = get_method_completion_insertion(function_name, function_args_list)
 | |
|                                         function_completion = (function_alias, function_insertion)
 | |
|                                         if function_completion not in function_completions:
 | |
|                                             function_completions.append(function_completion)
 | |
|                                     else:
 | |
|                                         function_args_list = []
 | |
|                                         if function_args_string:
 | |
|                                             function_args_list = function_args_string.split(",")
 | |
|                                         for i in range(len(function_args_list)):
 | |
|                                             # Check if it starts with @ -- this indicates an auto-set class variable
 | |
|                                             next_arg = function_args_list[i]
 | |
|                                             next_arg = next_arg.strip()
 | |
|                                             if next_arg.startswith(THIS_SUGAR_SYMBOL):
 | |
|                                                 # Clean it up...
 | |
|                                                 next_arg = re.sub(THIS_SUGAR_SYMBOL, "", next_arg)
 | |
|                                                 next_arg = re.sub("[^a-zA-Z0-9_$].*", "", next_arg)
 | |
|                                                 if show_private or not is_member_excluded(next_arg, member_exclusion_regexes):
 | |
|                                                     prop_completion_alias = get_property_completion_alias(next_arg, is_inherited)
 | |
|                                                     prop_completion_insertion = get_property_completion_insertion(next_arg)
 | |
|                                                     prop_completion = (prop_completion_alias, prop_completion_insertion)
 | |
|                                                     if prop_completion not in property_completions:
 | |
|                                                         property_completions.append(prop_completion)
 | |
|                                         inside_constructor = True
 | |
|                                         constructor_indentation = get_indentation_size(next_row)
 | |
|                     else:
 | |
|                         # Indentation limit hit. We're not in the class anymore.
 | |
|                         break
 | |
| 
 | |
|     completions = property_completions + function_completions
 | |
|     completion_tuple = (class_found, completions, extended_class)
 | |
|     return completion_tuple
 | |
| 
 | |
| 
 | |
| def get_class_from_end_of_chain(dot_operation_chain):
 | |
|     class_at_end = dot_operation_chain
 | |
|     next_period_index = class_at_end.find(PERIOD_OPERATOR)
 | |
|     while next_period_index >= 0:
 | |
|         class_at_end = class_at_end[(next_period_index + 1):]
 | |
|         class_at_end.strip()
 | |
|         next_period_index = class_at_end.find(PERIOD_OPERATOR)
 | |
|     if len(class_at_end) == 0:
 | |
|         class_at_end = None
 | |
|     return class_at_end
 | |
| 
 | |
| 
 | |
| def collect_static_completions_from_file(file_lines, class_name, is_inherited, member_exclusion_regexes, show_private):
 | |
| 
 | |
|     completions = []
 | |
|     extended_class = None
 | |
|     class_found = False
 | |
| 
 | |
|     property_completions = []
 | |
|     function_completions = []
 | |
| 
 | |
|     class_and_extends_regex = CLASS_REGEX_WITH_EXTENDS % class_name
 | |
| 
 | |
|     # Find class in file lines
 | |
|     match_tuple = get_positions_of_regex_match_in_file(file_lines, class_and_extends_regex)
 | |
|     if match_tuple:
 | |
|         class_found = True
 | |
|         row = match_tuple[0]
 | |
|         match = match_tuple[2]
 | |
| 
 | |
|         extended_class = match.group(3)
 | |
|         if extended_class:
 | |
|             # Clean it up.
 | |
|             next_period_index = extended_class.find(PERIOD_OPERATOR)
 | |
|             while next_period_index >= 0:
 | |
|                 extended_class = extended_class[(next_period_index + 1):]
 | |
|                 extended_class.strip()
 | |
|                 next_period_index = extended_class.find(PERIOD_OPERATOR)
 | |
|             if len(extended_class) == 0:
 | |
|                 extended_class = None
 | |
| 
 | |
|         # If anything is equal to this after the first line, stop looking.
 | |
|         # At that point, the class definition has ended.
 | |
|         indentation_size = get_indentation_size(file_lines[row])
 | |
| 
 | |
|         # Let's dig for some info on this class!
 | |
|         if row + 1 < len(file_lines):
 | |
| 
 | |
|             previous_indentation = -1
 | |
| 
 | |
|             for row_index in range(row + 1, len(file_lines)):
 | |
|                 next_row = file_lines[row_index]
 | |
|                 next_indentation = get_indentation_size(next_row)
 | |
|                 # print str(next_indentation) + ": " + next_row
 | |
|                 if next_indentation >= 0:
 | |
|                     if next_indentation > indentation_size:
 | |
|                         # print "Next: " + str(next_indentation) + ", Prev: " + str(previous_indentation)
 | |
|                         # Haven't found anything yet...
 | |
|                         # Look for class-level definitions...
 | |
|                         # If current line indentation is greater than previous indentation, we're in a definition
 | |
|                         if next_indentation > previous_indentation and previous_indentation >= 0:
 | |
|                             pass
 | |
|                         # Otherwise, save this indentation and examine the current line, as it's class-level
 | |
|                         else:
 | |
|                             previous_indentation = next_indentation
 | |
|                             function_regex = STATIC_FUNCTION_REGEX
 | |
|                             match = re.search(function_regex, next_row)
 | |
|                             if match:
 | |
|                                 function_name = match.group(4)
 | |
|                                 if show_private or not is_member_excluded(function_name, member_exclusion_regexes):
 | |
|                                     function_args_string = match.group(6)
 | |
|                                     function_args_list = []
 | |
|                                     if function_args_string:
 | |
|                                         function_args_list = function_args_string.split(",")
 | |
|                                     for i in range(len(function_args_list)):
 | |
|                                         # Fix each one up...
 | |
|                                         next_arg = function_args_list[i]
 | |
|                                         next_arg = next_arg.strip()
 | |
|                                         next_arg = re.sub("[^a-zA-Z0-9_$].*", "", next_arg)
 | |
|                                         function_args_list[i] = next_arg
 | |
|                                     function_alias = get_method_completion_alias(function_name, function_args_list, is_inherited)
 | |
|                                     function_insertion = get_method_completion_insertion(function_name, function_args_list)
 | |
|                                     function_completion = (function_alias, function_insertion)
 | |
|                                     if function_completion not in function_completions:
 | |
|                                         function_completions.append(function_completion)
 | |
|                             else:
 | |
|                                 # Look for static assignment
 | |
|                                 assignment_regex = STATIC_ASSIGNMENT_REGEX
 | |
|                                 match = re.search(assignment_regex, next_row)
 | |
|                                 if match:
 | |
|                                     prop = match.group(3)
 | |
|                                     if show_private or not is_member_excluded(prop, member_exclusion_regexes):
 | |
|                                         prop_completion_alias = get_property_completion_alias(prop, is_inherited)
 | |
|                                         prop_completion_insertion = get_property_completion_insertion(prop)
 | |
|                                         prop_completion = (prop_completion_alias, prop_completion_insertion)
 | |
|                                         if prop_completion not in property_completions:
 | |
|                                             property_completions.append(prop_completion)
 | |
|                     else:
 | |
|                         # Indentation limit hit. We're not in the class anymore.
 | |
|                         break
 | |
| 
 | |
|     completions = property_completions + function_completions
 | |
|     completion_tuple = (class_found, completions, extended_class)
 | |
|     return completion_tuple
 | |
| 
 | |
| 
 | |
| def get_property_completion_alias(property_name, is_inherited=False):
 | |
|     indicator = PROPERTY_INDICATOR
 | |
|     if is_inherited:
 | |
|         indicator = INHERITED_INDICATOR + indicator
 | |
|     completion_string = indicator + " " + property_name
 | |
|     return completion_string
 | |
| 
 | |
| 
 | |
| def get_property_completion_insertion(property_name):
 | |
|     completion_string = property_name
 | |
|     completion_string = re.sub("[$]", "\$", completion_string)
 | |
|     return completion_string
 | |
| 
 | |
| 
 | |
| def get_property_completion_tuple(property_name, is_inherited=False):
 | |
|     completion_tuple = (get_property_completion_alias(property_name, is_inherited), get_property_completion_insertion(property_name))
 | |
|     return completion_tuple
 | |
| 
 | |
| 
 | |
| def get_method_completion_alias(method_name, args, is_inherited=False):
 | |
|     indicator = METHOD_INDICATOR
 | |
|     if is_inherited:
 | |
|         indicator = INHERITED_INDICATOR + indicator
 | |
|     completion_string = indicator + " " + method_name + "("
 | |
|     for i in range(len(args)):
 | |
|         completion_string = completion_string + args[i]
 | |
|         if i < len(args) - 1:
 | |
|             completion_string = completion_string + ", "
 | |
|     completion_string = completion_string + ")"
 | |
|     return completion_string
 | |
| 
 | |
| 
 | |
| def get_method_completion_insertion(method_name, args):
 | |
| 
 | |
|     no_parens = False
 | |
| 
 | |
|     completion_string = re.sub("[$]", "\$", method_name)
 | |
| 
 | |
|     if len(args) == 1:
 | |
|         function_match = re.search(r".*?[=\-]>.*", args[0])
 | |
|         if function_match:
 | |
|             no_parens = True
 | |
| 
 | |
|     if no_parens:
 | |
|         completion_string = completion_string + " "
 | |
|     else:
 | |
|         completion_string = completion_string + "("
 | |
| 
 | |
|     for i in range(len(args)):
 | |
|         escaped_arg = re.sub("[$]", "\$", args[i])
 | |
|         completion_string = completion_string + "${" + str(i + 1) + ":" + escaped_arg + "}"
 | |
|         if i < len(args) - 1:
 | |
|             completion_string = completion_string + ", "
 | |
| 
 | |
|     if not no_parens:
 | |
|         completion_string = completion_string + ")"
 | |
| 
 | |
|     return completion_string
 | |
| 
 | |
| 
 | |
| def get_method_completion_tuple(method_name, arg_names, arg_insertions, is_inherited=False):
 | |
|     completion_tuple = (get_method_completion_alias(method_name, arg_names, is_inherited), get_method_completion_insertion(method_name, arg_insertions))
 | |
|     return completion_tuple
 | |
| 
 | |
| 
 | |
| def get_view_contents(view):
 | |
|     contents = ""
 | |
|     start = 0
 | |
|     end = view.size() - 1
 | |
|     if end > start:
 | |
|         entire_doc_region = sublime.Region(start, end)
 | |
|         contents = view.substr(entire_doc_region)
 | |
|     return contents
 | |
| 
 | |
| 
 | |
| def convert_file_contents_to_lines(contents):
 | |
|     lines = contents.split("\n")
 | |
|     count = len(lines)
 | |
|     for i in range(count):
 | |
|         # Don't add to the last one--that would put an extra \n
 | |
|         if i < count - 1:
 | |
|             lines[i] = lines[i] + "\n"
 | |
|     return lines
 | |
| 
 | |
| 
 | |
| def get_view_content_lines(view):
 | |
|     return convert_file_contents_to_lines(get_view_contents(view))
 | |
| 
 | |
| 
 | |
| def is_autocomplete_trigger(text):
 | |
|     trigger = False
 | |
|     trigger = trigger or text == THIS_SUGAR_SYMBOL
 | |
|     trigger = trigger or text == PERIOD_OPERATOR
 | |
|     return trigger
 | |
| 
 | |
| 
 | |
| def is_member_excluded(member, exclusion_regexes):
 | |
|     excluded = False
 | |
|     for next_exclusion_regex in exclusion_regexes:
 | |
|         if re.search(next_exclusion_regex, member):
 | |
|             excluded = True
 | |
|     return excluded
 |