diff --git a/config/modes/editing.yml b/config/modes/editing.yml index adb02775..431fe55b 100644 --- a/config/modes/editing.yml +++ b/config/modes/editing.yml @@ -5,8 +5,36 @@ prompt: | Use symbolic editing tools whenever possible for precise code modifications. If no editing task has yet been provided, wait for the user to provide one. - Your primary tool for editing code is a regex-based replacement. You use other tools to find the relevant content and - then use your knowledge of the codebase to write the regex. + You have two main approaches for editing code - editing by regex and editing by symbol. + The symbol-based approach is appropriate if you need to adjust an entire symbol, e.g. a method, a class, a function, etc. + But it is not appropriate if you need to adjust just a few lines of code within a symbol, for that you should + use the regex-based approach that is described below. + + Let us first discuss the symbol-based approach. + Symbols are identified by their name path and relative file path, see the description of the `find_symbols` tool for more details + on how the `name_path` matches symbols. + You can get information about available symbols by using the `get_symbols_overview` tool for finding top-level symbols in a file + or directory, or by using `find_symbol` if you already know the symbol's name path. You generally try to read as little code as possible + while still solving your task, meaning you only read the bodies when you need to, and after you have found the symbol you want to edit. + For example, if you are working with python code and alread know that you need to read the body of the constructor of the class Foo, you can directly + use `find_symbol` with the name path `Foo/__init__` and `include_body=True`. If you don't know yet which methods in `Foo` you need to read or edit, + you can use `find_symbol` with the name path `Foo`, `include_body=False` and `depth=1` to get all (top-level) methods of `Foo` before proceeding + to read the desired methods with `include_body=True`. + Note that you never need to add additional indentation, as all symbol editing tools will automatically add the indentation of the symbol that + you are replacing or inserting above or below. In particular, keep in mind the description of the `replace_symbol_body` tool. If you want to add some new code at the end of the file, you should + use the `insert_after_symbol` tool with the last top-level symbol in the file. If you want to add an import, often a good strategy is to use + `insert_before_symbol` with the first top-level symbol in the file. + You can unterstand relationships between symbols by using the `find_referencing_symbols` tool. If not explicitly requested otherwise by a user, + you make sure that when you edit a symbol, it is either done in a backward-compatible way, or you find and adjust the references as needed. + The `find_referencing_symbols` tool will give you code snippets around the references, as well as symbolic information. + You will generally be able to use the info from the snippets and the regex-based approach to adjust the references as well. + You can assume that all symbol editing tools are reliable, so you don't need to verify the results if the tool returns without error. + + Now let us discuss the regex-based approach. + The regex-based approach is your primary tool for editing code whenever replacing or deleting a whole symbol would be a more expensive operation. + This is the case if you need to adjust just a few lines of code within a method, or a chunk that is much smaller than a whole symbol. + You use other tools to find the relevant content and + then use your knowledge of the codebase to write the regex, if you haven't collected enough information of this content yet. You are extremely good at regex, so you never need to check whether the replacement produced the correct result. In particular, you know what to escape and what not to escape, and you know how to use wildcards. Moreover, the replacement tool will fail if it can't perform the desired replacement, and this is all the feedback you need. diff --git a/src/serena/agent.py b/src/serena/agent.py index 3a27991c..8985b65c 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -1109,7 +1109,7 @@ class GetSymbolsOverviewTool(Tool): def apply(self, relative_path: str, max_answer_chars: int = _DEFAULT_MAX_ANSWER_LENGTH) -> str: """ Gets an overview of the given file or directory. - For each analyzed file, we list the top-level symbols in the file (name, kind, line). + For each analyzed file, we list the top-level symbols in the file (name_path, kind). Use this tool to get a high-level understanding of the code symbols. Calling this is often a good idea before more targeted reading, searching or editing operations on the code symbols. @@ -1118,12 +1118,14 @@ class GetSymbolsOverviewTool(Tool): no content will be returned. Don't adjust unless there is really no other way to get the content required for the task. If the overview is too long, you should use a smaller directory instead, (e.g. a subdirectory). - :return: a JSON object mapping relative paths of all contained files to info about top-level symbols in the file (name, kind, line, column). + :return: a JSON object mapping relative paths of all contained files to info about top-level symbols in the file (name_path, kind). """ path_to_symbol_infos = self.language_server.request_overview(relative_path) result = {} for file_path, symbols in path_to_symbol_infos.items(): - result[file_path] = [_tuple_to_info(*symbol_info) for symbol_info in symbols] + # TODO: maybe include not just top-level symbols? We could filter by kind to exclude variables + # The language server methods would need to be adjusted for this. + result[file_path] = [{"name_path": symbol[0], "kind": int(symbol[1])} for symbol in symbols] result_json_str = json.dumps(result) return self._limit_length(result_json_str, max_answer_chars) @@ -1268,16 +1270,26 @@ class ReplaceSymbolBodyTool(Tool, ToolMarkerCanEdit): ) -> str: """ Replaces the body of the symbol with the given `name_path`. + IMPORTANT: + You don't need to provide an adjusted indentation, + as the tool will automatically add the indentation of the original symbol to each line. For example, + for replacing a method in python, you can just write (using the standard python indentation): + body="def my_method_replacement(self, ...):\n first_line\n second_line...". So each line after the first line only has + an indentation of 4 (the indentation relative to the first characted), + since the additional indentation will be added by the tool. Same for more deeply nested + cases. You always only need to write the relative indentation to the first character of the first line, and that + in turn should not have any indentation. + ALWAYS REMEMBER TO USE THE CORRECT INDENTATION IN THE BODY! :param name_path: for finding the symbol to replace, same logic as in the `find_symbol` tool. :param relative_path: the relative path to the file containing the symbol - :param body: the new symbol body. Important: Provide the correct level of indentation - (as the original body). Note that the first line must not be indented (i.e. no leading spaces). + :param body: the new symbol body. """ self.symbol_manager.replace_body( name_path, relative_file_path=relative_path, body=body, + use_same_indentation=True, ) return SUCCESS_RESULT @@ -1300,12 +1312,13 @@ class InsertAfterSymbolTool(Tool, ToolMarkerCanEdit): :param name_path: for finding the symbol to insert after, same logic as in the `find_symbol` tool. :param relative_path: the relative path to the file containing the symbol :param body: the body/content to be inserted. Important: the insterted code will automatically have the - same indentation as the symbol's body, so you do not need to provide any indentation. + same indentation as the symbol's body, so you do not need to provide any additional indentation. """ self.symbol_manager.insert_after_symbol( name_path, relative_file_path=relative_path, body=body, + use_same_indentation=True, ) return SUCCESS_RESULT @@ -1329,12 +1342,13 @@ class InsertBeforeSymbolTool(Tool, ToolMarkerCanEdit): :param name_path: for finding the symbol to insert before, same logic as in the `find_symbol` tool. :param relative_path: the relative path to the file containing the symbol :param body: the body/content to be inserted. Important: the insterted code will automatically have the - same indentation as the symbol's body, so you do not need to provide any indentation. + same indentation as the symbol's body, so you do not need to provide any additional indentation. """ self.symbol_manager.insert_before_symbol( name_path, relative_file_path=relative_path, body=body, + use_same_indentation=True, ) return SUCCESS_RESULT @@ -1955,7 +1969,3 @@ class ToolRegistry: for tool_name in sorted(tool_dict.keys()): tool_class = tool_dict[tool_name] print(f" * `{tool_name}`: {tool_class.get_tool_description().strip()}") - - -def _tuple_to_info(name: str, symbol_type: SymbolKind, line: int, column: int) -> dict[str, int | str]: - return {"name": name, "symbol_kind": symbol_type, "line": line, "column": column}