def run(self, edit, results_panel_name, find_results, symbol_occurences): logger.debug("The helper command '{}' has been triggered.".format( self.__class__.__name__)) # Fill the view with the results self.view.insert(edit, 0, find_results) # Assure the results navigation starts from the beggining self.view.sel().clear() # Convert symbol occurences to regions regions_to_highlight = [ sublime.Region(self.view.text_point(row, col_start), self.view.text_point(row, col_end)) for row, col_start, col_end in symbol_occurences ] # Highlight all symbol occurences flags = (sublime.DRAW_STIPPLED_UNDERLINE | sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE) self.view.add_regions("symbol", regions_to_highlight, scope="storage.type", flags=flags) sublime.active_window().run_command( "show_panel", {"panel": "output." + results_panel_name}) sublime.active_window().focus_view(self.view)
def _create_results_panel(self, find_results, symbol_occurences): """ Create a Find Results panel, fill it with `find_results`, and highlight all `symbol_occurences`. """ logger.debug("Preparing results panel.") syntax = "Packages/RTags/Find References Results.hidden-tmLanguage" file_regex = '^([^ \t].*):$' line_regex = '^ +([0-9]+):' working_dir = '' results_panel_name = "RTags: References" results_view = self.view.window().create_output_panel( results_panel_name) results_view.settings().set("result_file_regex", file_regex) results_view.settings().set("result_line_regex", line_regex) results_view.settings().set("result_base_dir", working_dir) results_view.settings().set("word_wrap", False) results_view.settings().set("line_numbers", False) results_view.settings().set("gutter", True) results_view.settings().set("scroll_past_end", False) results_view.settings().set("rulers", []) results_view.settings().set("translate_tabs_to_spaces", False) results_view.settings().set("highlight_line", True) results_view.assign_syntax(syntax) results_view.run_command( "publish_results_to_panel", { "results_panel_name": results_panel_name, "find_results": find_results, "symbol_occurences": symbol_occurences })
def _log_rc_success(output): """ Log about a successful execution of an rc command. Params: output - UTF-8 decoded output of that command """ success_msg_format = "Successfully executed rc command:\n \"{}\"" success_msg = success_msg_format.format(output) logger.debug(success_msg)
def _set_cursor_location_different_view(source_view, dest_file_name, dest_row, dest_col): """ Navigate to a different view directly to the desired position. """ logger.debug("Navigating to location within a different view.") target_location = ':'.join( [dest_file_name, str(dest_row), str(dest_col)]) # Last cursor location is automatically saved for future use of # 'jump_back' command. source_view.window().open_file(target_location, sublime.ENCODED_POSITION)
def _parse_output(self, output): """ Parse `output` received from "rc" to a useful form. Assuming output format is "{target_filename}:{row}:{col}:". """ logger.debug("Parsing output returned from rc call.") # Extract target location from output (remove trailing colon) target_location = output[:-1] # "{target_location}:" # Split target location to target file name, row and col delimiter = ':' # "{target_filename}:{row}:{col}" target_filename, row, col = target_location.split(delimiter) return target_filename, (int)(row), (int)(col)
def extract_single_location(view, avoid_word_end=False): """ Return a single cursor's absolute location ({file}:{row}:{col}) Extract the location of the cursor from a "view" object in format {file}:{row}:{col}. In case `view` contains more than a single location, empty string ("") is returned. In case `avoid_word_end`=True, identify the word in which the cursor is in, and if the cursor is at the end of that word, return the location of the beginning of it. This option is useful in case the location is to be used in a call for rc, since it has a bug when handling such locations. """ # Assure there is only a single cursor if CursorLocationHelper._is_single_cursor(view): logger.debug( "Can't extract single location from multiple cursors.") return "" # Assure that cursor (which represented by region) is not a selection region = view.sel()[0] if CursorLocationHelper._is_selection(region): logger.debug( "Can't extract single location from non-empty selection.") return "" # Extract cursor's point from the cursor's region point = region.a if avoid_word_end: if CursorLocationHelper._is_end_of_word(view, point): # Reassign point to the beggining of the word point = view.find_by_class( pt=point, forward=False, # Support operator overloading (words of only punctuations) classes=sublime.CLASS_WORD_START | sublime.CLASS_PUNCTUATION_START) # Convert current cursor location from "point" to "row" and "col" zero_based_row, zero_based_col = view.rowcol(point) row, col = zero_based_row + 1, zero_based_col + 1 return CursorLocationHelper._to_absolute_location( view.file_name(), row, col)
def _set_cursor_location_same_view(source_view, dest_row, dest_col): """ Set `source_view`'s cursor/s to a new location at the same view. """ logger.debug("Navigating to location within the same view.") # Save last cursor location for future use of 'jump_back' command cursor_locations_history = history_list.get_jump_history( source_view.window().id()) cursor_locations_history.push_selection(source_view) # Modify the view's selection (cursors) by clearing and setting new one sel = source_view.sel() sel.clear() zero_based_row, zero_based_col = dest_row - 1, dest_col - 1 point = source_view.text_point(zero_based_row, zero_based_col) sel.add(point) source_view.show(point)
def run(self): # Clear result of previous rc call self.received_output = "" self.rc_returned_successfully = False command = self._build_command() logger.debug("About to execute rc command: \"{}\"".format(command)) # Try to execute rc command, and handle the result try: binary_output = subprocess.check_output( command.split(), timeout=self._timeout, shell=False) except subprocess.CalledProcessError as e: self._log_rc_error(command, e) else: str_output = binary_output.decode('UTF-8').strip() self._log_rc_success(str_output) self.received_output = str_output self.rc_returned_successfully = True
def run(self): logger.info("The functional command '{}' has been triggered.".format( self.__class__.__name__)) # Find all "compile_commands.json" files located in open folders compile_commands_file_path_results = [ os.path.join(folder, file) for folder in self.window.folders() for file in os.listdir(folder) if file == "compile_commands.json" ] logger.debug("Found compile_commands.json files at:\n {}".format( compile_commands_file_path_results)) if not compile_commands_file_path_results: sublime.error_message( "[RTags error]\n\n" + "There is no compile_commands.json in your environment.") return if len(compile_commands_file_path_results) > 1: sublime.error_message( "[RTags error]\n\n" + "Multiple compile_commands.json files found in environment.\n" "I don't know which one to load, so I'll leave it to you.") return # Execute rc command compile_commands_path = compile_commands_file_path_results[0] rc_params = "--load-compile-commands {}".format(compile_commands_path) rc_thread = RCCall() rc_thread.execute_rc(rc_params) rc_thread.join() if rc_thread.rc_returned_successfully: self.window.status_message( "RTags: The compilation database was loaded successfully.") else: sublime.error_message( "[RTags error]\n\n" + "RC failed to load the compilation database.\n" "RTags can't work without it.\n" "Please refer to @StavE.")
def _get_symbol_column_range(symbol_location): """Figure out the start and end columns of the referenced symbol. """ rc_params = "--json --symbol-info {}".format(symbol_location) rc_thread = RCCall(self.view.file_name()) rc_thread.execute_rc(rc_params) rc_thread.join() if not rc_thread.rc_returned_successfully: raise RuntimeError( "Failed to get referenced symbol information") output_without_whitesapces = ''.join( rc_thread.received_output.split()) output_as_dict = json.loads(output_without_whitesapces) if output_as_dict["endLine"] != output_as_dict["startLine"]: logger.debug("Symbol at \"{}\" is spread over multiple lines, " "skipping highlight.".format(symbol_location)) return (0, 0) return output_as_dict["startColumn"], output_as_dict["endColumn"]
def set_cursor_location(source_view, dest_file_name, dest_row, dest_col): """ Move `source_view`'s cursor/s location to a new destination. Params: source_view - sublime.view instance to modify its cursor dest_file_name - destination file name (absolute path) dest_row - destination row number (nonzero based) dest_col - destination column number (nonzero based) Note: - This method affects sublime's cursor locations history. - `source_view`'s cursors will be modified only in case the destination cursor location is in the same view. """ logger.debug("About to navigate to a new location.") # New cursor location is in the source view if os.path.realpath(source_view.file_name()) == dest_file_name: CursorLocationHelper._set_cursor_location_same_view( source_view, dest_row, dest_col) # New cursor location is in a different view else: try: # Fix project/folder path prefix within absolute path, in case # the directed file is actually a part of it. dest_file_name = ( CursorLocationHelper._to_project_path(dest_file_name)) logger.debug("New location is within the project path.") except CursorLocationHelper.FileOutsideProjectPathError: # No modification should be done in this case logger.debug("New location is outside the project path.") pass CursorLocationHelper._set_cursor_location_different_view( source_view, dest_file_name, dest_row, dest_col)
def _parse_output(self, output): """ Parse `output` received from "rc" to a useful form. Returns: - String containing the results in a format matching the appropriate syntax definition (See `_create_results_panel()`) - Occurences list of the referenced symbol (row, col_start, col_end) - Statistics about the results (number of references, number of files) Assuming output is multiple lines of the format: "{target_filename}:{row}:{col}:{line_content}" The output is converted to match Sublime Text's "Find Results" syntax: {target_filename}: {row}: {line_content} """ def _get_symbol_column_range(symbol_location): """Figure out the start and end columns of the referenced symbol. """ rc_params = "--json --symbol-info {}".format(symbol_location) rc_thread = RCCall() rc_thread.execute_rc(rc_params) rc_thread.join() if not rc_thread.rc_returned_successfully: raise RuntimeError( "Failed to get referenced symbol information") output_without_whitesapces = ''.join( rc_thread.received_output.split()) output_as_dict = json.loads(output_without_whitesapces) if output_as_dict["endLine"] != output_as_dict["startLine"]: raise Exception( "A symbol is not supposed to spread over multiple lines.") return output_as_dict["startColumn"], output_as_dict["endColumn"] logger.debug("Parsing output returned from rc call.") # Will contain the result of the parse find_results = '' # Will contain statistics about the find results number_of_results = 0 number_of_files = 0 # List of (row, col_start, col_end) tuples of the referenced symbol # occurences. note: row and col are zero based symbol_occurences = [] # Every file name should be mentioned once, so duplications are removed previous_target_filename = '' previous_row = 0 line_number = 0 for line in output.splitlines(): # Split output line to target file name, row, col and context delimiter = ':' target_filename, row, col, context = line.split(delimiter, maxsplit=3) # Whether current result belongs to a different file (against prev) if target_filename != previous_target_filename: # Bind future results to current target file name find_results += target_filename + ':\n' # Append current result line find_results += ' ' + row + ':' + context + '\n' previous_row = row previous_target_filename = target_filename number_of_files += 1 line_number += 1 elif row != previous_row: # No need to print multiple results for the same line number # Append current result line find_results += ' ' + row + ':' + context + '\n' previous_row = row else: # Cancel line number increase from previous iteration line_number -= 1 # Calculate the column offset for the referenced symbol occurence # The offset considers: " {row}:<tabstop>" offset = 1 + len(row) + 1 # Used to calculate the end column of the referenced symbol symbol_location = ':'.join((target_filename, row, col)) col_start, col_end = _get_symbol_column_range(symbol_location) col_start += offset col_end += offset # Add the referenced symbol occurence to the list symbol_occurences.append((line_number, col_start, col_end)) number_of_results += 1 line_number += 1 return (find_results, symbol_occurences, number_of_results, number_of_files)
def _log_rc_error(cmd, e): """ Log about an error in rc execution. Params: cmd - rc command that caused the error e - CalledProcessError exception RC exit codes: success - 0 general_failure - 32 network_failure - 33 timeout_failure - 34 not_indexed - 35 connection_failure - 36 protocol_failure - 37 argument_parse_error - 38 """ # Errors that should be supressed from the normal user rc_exit_codes_unrelevant_to_user = { "general_failure": 32, "network_failure": 33, "timeout_failure": 34, "argument_parse_error": 38 } # Errors that the user must be notified about rc_exit_codes_very_relevant_to_user = { "connection_failure": { "exit_code": 36, "user_friendly_message": "Failed to connect to rdm server.\n" "Are you sure it is running?" }, "protocol_failure": { "exit_code": 37, "user_friendly_message": "Protocol version mismatch. \n" "This plugin version does not support your RTags installation " "version." }, } error_msg_format = ( "Executing rc resulted in an error (return code {}).\n" " Command: \"{}\"\n" " Output: \"{}\"") error_msg = error_msg_format.format( e.returncode, cmd, e.output.decode('UTF-8').strip()) # Classify the exit code by how relevant it is for the user if e.returncode not in rc_exit_codes_unrelevant_to_user.values(): for very_relevant_error in ( rc_exit_codes_very_relevant_to_user.values()): if e.returncode == very_relevant_error["exit_code"]: sublime.error_message( "[RTags error]\n" + very_relevant_error["user_friendly_message"]) break logger.error(error_msg) else: error_msg = "RC error supressed from user:\n" + error_msg logger.debug(error_msg)