diff --git a/CHANGELOG.md b/CHANGELOG.md index d348cf67..19605d73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,14 @@ Changes prior to the next official version change will appear here. +* Serena core: + * bugfix in find_symbol tool (a bug fixed in LS) + * merged the two overview tools (for dir and file) int a single one + * one-click setup for Cline enabled + * Language Servers: - * Add further file extensions considered by the language servers for Python (.pyi), JavaScript (.jsx) and TypeScript (.tsx, .jsx) + * Add further file extensions considered by the language servers for Python (.pyi), JavaScript (.jsx) and TypeScript (.tsx, .jsx) + # 2025-04-07 diff --git a/README.md b/README.md index 595189d1..cff69a7f 100644 --- a/README.md +++ b/README.md @@ -586,8 +586,7 @@ Here is the full list of Serena's tools with a short description (output of `uv * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). * `get_active_project`: Gets the name of the currently active project (if any) and lists existing projects -* `get_dir_overview`: Gets an overview of the top-level symbols defined in all files within a given directory. -* `get_document_overview`: Gets an overview of the top-level symbols defined in a given file. +* `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file or directory. * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. * `insert_at_line`: Inserts content at a given line in a file. * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. diff --git a/llms-install.md b/llms-install.md new file mode 100644 index 00000000..b02711c7 --- /dev/null +++ b/llms-install.md @@ -0,0 +1,29 @@ +# MCP Installation instructions + +This document is mainly used as instructions for AI-assistants like Cline and others that +try to do an automatic install based on freeform instructions. + +0. Make sure `uv` is installed. If not, install it using either `curl -LsSf https://astral.sh/uv/install.sh | sh` (macOS, Linux) or + `powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"` (Windows). Find the path to the `uv` executable, + you'll need it later. +1. Clone the repo with `git clone git@github.com:oraios/serena.git` and change into its dir (e.g., `cd serena`) +2. Check if `serena_config.yml` exists. If not, create it with `cp serena_config.template.yml serena_config.yml`. Read the instructions in the config. +3. In the config, check if the path to your project was added. If not, add it to the `projects` section +4. In your project, create a `.serena` if needed and check whether `project.yml` exists there. +5. If no `project.yml` was found, create it using `cp /path/to/serena/myproject.template.yml /path/to/your/project/.serena/project.yml` +6. Read the instructions in `project.yml`. Make sure the `project.yml` has the correct project language configured. + Remove the project_root entry there. +7. Finally, add the Serena MCP server config like this: + +```json + { + "mcpServers": { + ... + "serena": { + "command": "/abs/path/to/uv", + "args": ["run", "--directory", "/abs/path/to/serena", "serena-mcp-server", "/path/to/your/project/.serena/project.yml"] + } + } + } + +``` diff --git a/serena_config.template.yml b/serena_config.template.yml index 5aa220b9..90853f01 100644 --- a/serena_config.template.yml +++ b/serena_config.template.yml @@ -31,7 +31,7 @@ projects: # # Limitations: doesn't seem to work with the community version of Claude Desktop for Linux # Might also cause problems with some MCP clients - if you have any issues, try disabling this -gui_log_window: True +gui_log_window: False # minimum log level for the GUI log window (10 = debug, 20 = info, 30 = warning, 40 = error) gui_log_level: 20 diff --git a/src/multilspy/language_server.py b/src/multilspy/language_server.py index d60f6797..091e7e62 100644 --- a/src/multilspy/language_server.py +++ b/src/multilspy/language_server.py @@ -886,13 +886,18 @@ class LanguageServer: :return: A list of root symbols representing the top-level packages/modules in the project. """ - if within_relative_path is not None and os.path.isfile(within_relative_path): - if self.should_ignore_path(within_relative_path): - self.logger.log(f"You passed a file explicitly, but it is ignored. This is probably an error. File: {within_relative_path}", logging.ERROR) - return [] - _, root_nodes = await self.request_document_symbols(within_relative_path, include_body=include_body) - return root_nodes + if within_relative_path is not None: + within_abs_path = os.path.join(self.repository_root_path, within_relative_path) + if not os.path.exists(within_abs_path): + raise FileNotFoundError(f"File or directory not found: {within_abs_path}") + if os.path.isfile(within_abs_path): + if self.should_ignore_path(within_relative_path): + self.logger.log(f"You passed a file explicitly, but it is ignored. This is probably an error. File: {within_relative_path}", logging.ERROR) + return [] + else: + _, root_nodes = await self.request_document_symbols(within_relative_path, include_body=include_body) + return root_nodes # Helper function to recursively process directories async def process_directory(dir_path: str) -> List[multilspy_types.UnifiedSymbolInformation]: @@ -1040,6 +1045,23 @@ class LanguageServer: for root in document_roots ] + async def request_overview(self, within_relative_path: str) -> dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]]: + """ + An overview of all symbols in the given file or directory. + + :param within_relative_path: the relative path to the file or directory to get the overview of. + :return: A mapping of all relative paths analyzed to lists of tuples (name, kind, line, column) of all top-level symbols in the corresponding file. + """ + abs_path = (Path(self.repository_root_path) / within_relative_path).resolve() + if not abs_path.exists(): + raise FileNotFoundError(f"File or directory not found: {abs_path}") + + if abs_path.is_file(): + symbols_overview = await self.request_document_overview(within_relative_path) + return {within_relative_path: symbols_overview} + else: + return await self.request_dir_overview(within_relative_path) + async def request_hover(self, relative_file_path: str, line: int, column: int) -> Union[multilspy_types.Hover, None]: """ Raise a [textDocument/hover](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover) request to the Language Server @@ -1788,6 +1810,19 @@ class SyncLanguageServer: ).result(timeout=self.timeout) return result + def request_overview(self, within_relative_path: str) -> dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]]: + """ + An overview of all symbols in the given file or directory. + + :param within_relative_path: the relative path to the file or directory to get the overview of. + :return: A mapping of all relative paths analyzed to lists of tuples (name, kind, line, column) of all top-level symbols in the corresponding file. + """ + assert self.loop + result = asyncio.run_coroutine_threadsafe( + self.language_server.request_overview(within_relative_path), self.loop + ).result() + return result + def request_hover(self, relative_file_path: str, line: int, column: int) -> Union[multilspy_types.Hover, None]: """ Raise a [textDocument/hover](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover) request to the Language Server diff --git a/src/serena/agent.py b/src/serena/agent.py index 769429f5..e13b1afb 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -125,7 +125,7 @@ class SerenaConfig: self.projects[project_config.project_name] = project_config self.project_names = list(self.projects.keys()) - self.gui_log_window_enabled = config_yaml.get("gui_log_window", True) + self.gui_log_window_enabled = config_yaml.get("gui_log_window", False) self.gui_log_window_level = config_yaml.get("gui_log_level", logging.INFO) self.enable_project_activation = config_yaml.get("enable_project_activation", True) @@ -590,25 +590,26 @@ class ListDirTool(Tool): return self._limit_length(result, max_answer_chars) -class GetDirOverviewTool(Tool): +class GetSymbolsOverviewTool(Tool): """ - Gets an overview of the top-level symbols defined in all files within a given directory. + Gets an overview of the top-level symbols defined in a given file or directory. """ def apply(self, relative_path: str, max_answer_chars: int = _DEFAULT_MAX_ANSWER_LENGTH) -> str: """ - Gets an overview of the given directory. - For each file in the directory, we list the top-level symbols in the file (name, kind, line). - Use this tool to get a high-level understanding of the code symbols inside a directory. + 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). + 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. - :param relative_path: the relative path to the directory to get the overview of + :param relative_path: the relative path to the file or directory to get the overview of :param max_answer_chars: if the overview is longer than this number of characters, 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). """ - path_to_symbol_infos = self.language_server.request_dir_overview(relative_path) + 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] @@ -617,28 +618,6 @@ class GetDirOverviewTool(Tool): return self._limit_length(result_json_str, max_answer_chars) -class GetDocumentOverviewTool(Tool): - """ - Gets an overview of the top-level symbols defined in a given file. - """ - - def apply(self, relative_path: str, max_answer_chars: int = _DEFAULT_MAX_ANSWER_LENGTH) -> str: - """ - Use this tool to get a high-level understanding of the code symbols in a file. It often makes sense - to call this before targeted reading, searching or editing operations on the code symbols in the file, - as the output will contain a lot of information about names and lines. - - :param relative_path: the relative path to the file to get the overview of - :param max_answer_chars: if the overview is longer than this number of characters, - no content will be returned. Don't adjust unless there is really no other way to get the content - required for the task. - :return: a JSON object with the info (name, kind, line, column) of all top-level symbols in the file. - """ - result = self.language_server.request_document_overview(relative_path) - result_json_str = json.dumps([_tuple_to_info(*symbol_info) for symbol_info in result]) - return self._limit_length(result_json_str, max_answer_chars) - - class FindSymbolTool(Tool): """ Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).