mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 14:16:21 +00:00
Merge branch 'main' of github.com:oraios/serena
This commit is contained in:
commit
2ad1b7b31f
18 files changed
+244
-132
No files matched your search
@@ -102,6 +102,10 @@ jobs:
|
||||
swiftly use 6.1.2
|
||||
echo "~/.swiftly/bin" >> $GITHUB_PATH
|
||||
echo "Swiftly installed successfully"
|
||||
# Verify sourcekit-lsp is working before proceeding
|
||||
echo "=== Verifying sourcekit-lsp installation ==="
|
||||
which sourcekit-lsp || echo "Warning: sourcekit-lsp not found in PATH"
|
||||
sourcekit-lsp --help || echo "Warning: sourcekit-lsp not responding"
|
||||
- name: Install Swift with swiftly (Ubuntu)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
@@ -119,6 +123,10 @@ jobs:
|
||||
echo "=== Adding Swift toolchain to PATH ==="
|
||||
echo "$HOME/.local/share/swiftly/bin" >> $GITHUB_PATH
|
||||
echo "Swiftly installed successfully!"
|
||||
# Verify sourcekit-lsp is working before proceeding
|
||||
echo "=== Verifying sourcekit-lsp installation ==="
|
||||
which sourcekit-lsp || echo "Warning: sourcekit-lsp not found in PATH"
|
||||
sourcekit-lsp --help || echo "Warning: sourcekit-lsp not responding"
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
|
||||
@@ -19,6 +19,7 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
* General:
|
||||
* Various fixes related to indexing, special paths and determation of ignored paths
|
||||
* Decreased `TOOL_DEFAULT_MAX_ANSWER_LENGTH` to be in accordance with (below) typical max-tokens configurations
|
||||
* Allow passing language server specific settings through `ls_specific_settings` field (in `serena_config.yml`)
|
||||
|
||||
# 0.1.4
|
||||
|
||||
|
||||
@@ -132,10 +132,11 @@ Several videos and blog posts have talked about Serena:
|
||||
- [Quick Start](#quick-start)
|
||||
* [Running the Serena MCP Server](#running-the-serena-mcp-server)
|
||||
+ [Usage](#usage)
|
||||
- [Using uvx](#using-uvx)
|
||||
* [Local Installation](#local-installation)
|
||||
- [Using Docker (Experimental)](#using-docker-experimental)
|
||||
+ [SSE Mode](#sse-mode)
|
||||
+ [Using uvx](#using-uvx)
|
||||
+ [Local Installation](#local-installation)
|
||||
+ [Using Docker (Experimental)](#using-docker-experimental)
|
||||
+ [Using Nix](#using-nix)
|
||||
+ [Streamable HTTP Mode](#streamable-http-mode)
|
||||
+ [Command-Line Arguments](#command-line-arguments)
|
||||
* [Configuration](#configuration)
|
||||
* [Project Activation & Indexing](#project-activation--indexing)
|
||||
@@ -158,16 +159,16 @@ Several videos and blog posts have talked about Serena:
|
||||
+ [Start from a Clean State](#start-from-a-clean-state)
|
||||
+ [Logging, Linting, and Automated Tests](#logging-linting-and-automated-tests)
|
||||
* [Prompting Strategies](#prompting-strategies)
|
||||
* [Potential Issues in Code Editing](#potential-issues-in-code-editing)
|
||||
* [Running Out of Context](#running-out-of-context)
|
||||
* [Combining Serena with Other MCP Servers](#combining-serena-with-other-mcp-servers)
|
||||
* [Serena's Logs: The Dashboard and GUI Tool](#serenas-logs-the-dashboard-and-gui-tool)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
- [Comparison with Other Coding Agents](#comparison-with-other-coding-agents)
|
||||
* [Subscription-Based Coding Agents](#subscription-based-coding-agents)
|
||||
* [API-Based Coding Agents](#api-based-coding-agents)
|
||||
* [Other MCP-Based Coding Agents](#other-mcp-based-coding-agents)
|
||||
- [Acknowledgements](#acknowledgements)
|
||||
* [Sponsors](#sponsors)
|
||||
* [Community Contributions](#community-contributions)
|
||||
* [Technologies](#technologies)
|
||||
- [Customizing and Extending Serena](#customizing-and-extending-serena)
|
||||
- [List of Tools](#list-of-tools)
|
||||
|
||||
@@ -184,7 +185,7 @@ Serena can be used in various ways, below you will find instructions for selecte
|
||||
* You can use Serena as a library for building your own applications. We try to keep the public API stable, but you should still
|
||||
expect breaking changes and pin Serena to a fixed version if you use it as a dependency.
|
||||
|
||||
Serena is managed by `uv`, so you will need to [install it](https://docs.astral.sh/uv/getting-started/installation/)).
|
||||
Serena is managed by `uv`, so you will need to [install it](https://docs.astral.sh/uv/getting-started/installation/).
|
||||
|
||||
### Running the Serena MCP Server
|
||||
|
||||
@@ -195,14 +196,14 @@ You have several options for running the MCP server, which are explained in the
|
||||
The typical usage involves the client (Claude Code, Claude Desktop, etc.) running
|
||||
the MCP server as a subprocess (using stdio communication),
|
||||
so the client needs to be provided with the command to run the MCP server.
|
||||
(Alternatively, you can run the MCP server in SSE mode and tell your client
|
||||
(Alternatively, you can run the MCP server in Streamable HTTP or SSE mode and tell your client
|
||||
how to connect to it.)
|
||||
|
||||
Note that no matter how you run the MCP server, Serena will, by default, start a small web-based dashboard on localhost that will display logs and allow shutting down the
|
||||
MCP server (since many clients fail to clean up processes correctly).
|
||||
This and other settings can be adjusted in the [configuration](#configuration) and/or by providing [command-line arguments](#command-line-arguments).
|
||||
|
||||
##### Using uvx
|
||||
#### Using uvx
|
||||
|
||||
`uvx` can be used to run the latest version of Serena directly from the repository, without an explicit local installation.
|
||||
|
||||
@@ -212,7 +213,7 @@ uvx --from git+https://github.com/oraios/serena serena start-mcp-server
|
||||
|
||||
Explore the CLI to see some of the customization options that serena provides (more info on them below).
|
||||
|
||||
###### Local Installation
|
||||
#### Local Installation
|
||||
|
||||
1. Clone the repository and change into it.
|
||||
|
||||
@@ -240,7 +241,7 @@ Explore the CLI to see some of the customization options that serena provides (m
|
||||
uv run --directory /abs/path/to/serena serena start-mcp-server
|
||||
```
|
||||
|
||||
##### Using Docker (Experimental)
|
||||
#### Using Docker (Experimental)
|
||||
|
||||
⚠️ Docker support is currently experimental with several limitations. Please read the [Docker documentation](DOCKER.md) for important caveats before using it.
|
||||
|
||||
@@ -261,7 +262,7 @@ Alternatively, use docker compose with the `compose.yml` file provided in the re
|
||||
|
||||
See the [Docker documentation](DOCKER.md) for detailed setup instructions, configuration options, and known limitations.
|
||||
|
||||
##### Using Nix
|
||||
#### Using Nix
|
||||
|
||||
If you are using Nix and [have enabled the `nix-command` and `flakes` features](https://nixos.wiki/wiki/flakes), you can run Serena using the following command:
|
||||
|
||||
@@ -271,29 +272,32 @@ nix run github:oraios/serena -- start-mcp-server --transport stdio
|
||||
|
||||
You can also install Serena by referencing this repo (`github:oraios/serena`) and using it in your Nix flake. The package is exported as `serena`.
|
||||
|
||||
#### SSE Mode
|
||||
#### Streamable HTTP Mode
|
||||
|
||||
ℹ️ Note that MCP servers which use stdio as a protocol are somewhat unusual as far as client/server architectures go, as the server
|
||||
necessarily has to be started by the client in order for communication to take place via the server's standard input/output stream.
|
||||
In other words, you do not need to start the server yourself. The client application (e.g. Claude Desktop) takes care of this and
|
||||
therefore needs to be configured with a launch command.
|
||||
|
||||
When using instead the SSE mode, which uses HTTP-based communication, you control the server lifecycle yourself,
|
||||
When using instead the *Streamable HTTP* mode, you control the server lifecycle yourself,
|
||||
i.e. you start the server and provide the client with the URL to connect to it.
|
||||
|
||||
Simply provide `start-mcp-server` with the `--transport sse` option and optionally provide the port.
|
||||
For example, to run the Serena MCP server in SSE mode on port 9121 using a local installation,
|
||||
Simply provide `start-mcp-server` with the `--transport streamable-http` option and optionally provide the port.
|
||||
For example, to run the Serena MCP server in Streamable HTTP mode on port 9121 using a local installation,
|
||||
you would run this command from the Serena directory,
|
||||
|
||||
```shell
|
||||
uv run serena start-mcp-server --transport sse --port 9121
|
||||
uv run serena start-mcp-server --transport streamable-http --port 9121
|
||||
```
|
||||
|
||||
and then configure your client to connect to `http://localhost:9121/sse`.
|
||||
and then configure your client to connect to `http://localhost:9121/mcp`.
|
||||
|
||||
ℹ️ Note that SSE transport is supported as well, but its use is discouraged.
|
||||
Use Streamable HTTP instead.
|
||||
|
||||
#### Command-Line Arguments
|
||||
|
||||
The Serena MCP server supports a wide range of additional command-line options, including the option to run in SSE mode
|
||||
The Serena MCP server supports a wide range of additional command-line options, including the option to run in Streamable HTTP or SSE mode
|
||||
and to adapt Serena to various [contexts and modes of operation](#modes-and-contexts).
|
||||
|
||||
Run with parameter `--help` to get a list of available options.
|
||||
@@ -528,12 +532,16 @@ autonomously.
|
||||
|
||||
#### Shell Execution and Editing Tools
|
||||
|
||||
However, it should be noted that the `execute_shell_command` tool allows for arbitrary code execution.
|
||||
Many clients have their own shell execution tool, and by default Serena's shell tool will be disabled in them
|
||||
(e.g., when using the `ide-assistant` or `codex` context). However, when using Serena through something like
|
||||
Claude Desktop or ChatGPT, it is recommended to enable Serena's `execute_shell_command` tool to allow
|
||||
agentic behavior.
|
||||
|
||||
It should be noted that the `execute_shell_command` tool allows for arbitrary code execution.
|
||||
When using Serena as an MCP Server, clients will typically ask the user for permission
|
||||
before executing a tool, so as long as the user inspects execution parameters beforehand,
|
||||
this should not be a problem.
|
||||
However, if you have concerns, you can choose to disable certain commands in your project's
|
||||
.yml configuration file.
|
||||
However, if you have concerns, you can choose to disable certain commands in your project's configuration file.
|
||||
If you only want to use Serena purely for analyzing code and suggesting implementations
|
||||
without modifying the codebase, you can enable read-only mode by setting `read_only: true` in your project configuration file.
|
||||
This will automatically disable all editing tools and prevent any modifications to your codebase while still
|
||||
@@ -673,18 +681,6 @@ better results and in increasing the feeling of control and staying in the loop.
|
||||
make a detailed plan in one session, where Serena may read a lot of your code to build up the context,
|
||||
and then continue with the implementation in another (potentially after creating suitable memories).
|
||||
|
||||
### Potential Issues in Code Editing
|
||||
|
||||
In our experience, LLMs are bad at counting, i.e. they have problems
|
||||
inserting blocks of code in the right place. Most editing operations can be performed
|
||||
at the symbolic level, allowing this problem is overcome. However, sometimes,
|
||||
line-level insertions are useful.
|
||||
|
||||
Serena is instructed to double-check the line numbers and any code blocks that it will
|
||||
edit, but you may find it useful to explicitly tell it how to edit code if you run into
|
||||
problems.
|
||||
We are working on making Serena's editing capabilities more robust.
|
||||
|
||||
### Running Out of Context
|
||||
|
||||
For long and complicated tasks, or tasks where Serena has read a lot of content, you
|
||||
@@ -702,14 +698,6 @@ Moreover, Serena is instructed to be frugal with context
|
||||
but we found that Claude is not always very good in being frugal (Gemini seemed better at it).
|
||||
You can explicitly instruct it to not read the bodies if you know that it's not needed.
|
||||
|
||||
### Combining Serena with Other MCP Servers
|
||||
|
||||
When using Serena through an MCP Client, you can use it together with other MCP servers.
|
||||
However, beware of tool name collisions! See info on that above.
|
||||
|
||||
Currently, there is a collision with the popular Filesystem MCP Server. Since Serena also provides
|
||||
filesystem operations, there is likely no need to ever enable these two simultaneously.
|
||||
|
||||
### Serena's Logs: The Dashboard and GUI Tool
|
||||
|
||||
Serena provides two convenient ways of accessing the logs of the current session:
|
||||
@@ -732,17 +720,6 @@ In addition to viewing logs, both tools allow to shut down the Serena agent.
|
||||
This function is provided, because clients like Claude Desktop may fail to terminate the MCP server subprocess
|
||||
when they themselves are closed.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
Support for MCP Servers in Claude Desktop and the various MCP Server SDKs are relatively new developments and may display instabilities.
|
||||
|
||||
The working configuration of an MCP server may vary from platform to
|
||||
platform and from client to client. We recommend always using absolute paths, as relative paths may be sources of
|
||||
errors. The language server is running in a separate sub-process and is called with asyncio – sometimes
|
||||
a client may make it crash. If you have Serena's log window enabled, and it disappears, you'll know what happened.
|
||||
|
||||
Some clients may not properly terminate MCP servers, look out for hanging python processes and terminate them manually, if needed.
|
||||
|
||||
## Comparison with Other Coding Agents
|
||||
|
||||
To our knowledge, Serena is the first fully-featured coding agent where the
|
||||
@@ -807,6 +784,30 @@ larger codebases.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
### Sponsors
|
||||
|
||||
We are very grateful to our [sponsors](https://github.com/sponsors/oraios) who help us drive Serena's development. The core team
|
||||
(the founders of [Oraios AI](https://oraios-ai.de/)) put in a lot of work in order to turn Serena into a useful open source project.
|
||||
So far, there is no business model behind this project, and sponsors are our only source of income from it.
|
||||
|
||||
Sponsors help us dedicating more time to the project, managing contributions, and working on larger features (like better tooling based on more advanced
|
||||
LSP features, VSCode integration, debugging via the DAP, and several others).
|
||||
If you find this project useful to your work, or would like to accelerate the development of Serena, consider becoming a sponsor.
|
||||
|
||||
We are proud to announce that the Visual Studio Code team, together with Microsoft’s Open Source Programs Office and GitHub Open Source
|
||||
have decided to sponsor Serena with a one-time contribution!
|
||||
|
||||
<p align="center">
|
||||
<img src="resources/vscode_sponsor_logo.png" alt="Visual Studio Code sponsor logo" width="220">
|
||||
</p>
|
||||
|
||||
### Community Contributions
|
||||
|
||||
A significant part of Serena, especially support for various languages, was contributed by the open source community.
|
||||
We are very grateful for the many contributors who made this possible and who played an important role in making Serena
|
||||
what it is today.
|
||||
|
||||
### Technologies
|
||||
We built Serena on top of multiple existing open-source technologies, the most important ones being:
|
||||
|
||||
1. [multilspy](https://github.com/microsoft/multilspy).
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
+8
-2
@@ -119,7 +119,13 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
show_default=True,
|
||||
help="Built-in mode names or paths to custom mode YAMLs.",
|
||||
)
|
||||
@click.option("--transport", type=click.Choice(["stdio", "sse"]), default="stdio", show_default=True, help="Transport protocol.")
|
||||
@click.option(
|
||||
"--transport",
|
||||
type=click.Choice(["stdio", "sse", "streamable-http"]),
|
||||
default="stdio",
|
||||
show_default=True,
|
||||
help="Transport protocol.",
|
||||
)
|
||||
@click.option("--host", type=str, default="0.0.0.0", show_default=True)
|
||||
@click.option("--port", type=int, default=8000, show_default=True)
|
||||
@click.option("--enable-web-dashboard", type=bool, is_flag=False, default=None, help="Override dashboard setting in config.")
|
||||
@@ -137,7 +143,7 @@ class TopLevelCommands(AutoRegisteringGroup):
|
||||
project_file_arg: str | None,
|
||||
context: str,
|
||||
modes: tuple[str, ...],
|
||||
transport: Literal["stdio", "sse"],
|
||||
transport: Literal["stdio", "sse", "streamable-http"],
|
||||
host: str,
|
||||
port: int,
|
||||
enable_web_dashboard: bool | None,
|
||||
|
||||
@@ -309,6 +309,6 @@ class Project:
|
||||
solidlsp_settings=SolidLSPSettings(
|
||||
solidlsp_dir=SERENA_MANAGED_DIR_IN_HOME,
|
||||
project_data_relative_path=SERENA_MANAGED_DIR_NAME,
|
||||
ls_specific_settings=ls_specific_settings,
|
||||
ls_specific_settings=ls_specific_settings or {},
|
||||
),
|
||||
)
|
||||
@@ -37,8 +37,9 @@ ls_specific_settings: {}
|
||||
# Added on 23.08.2025
|
||||
# Advanced configuration option allowing to configure language server implementation specific options. Maps the language
|
||||
# (same entry as in project.yml) to the options.
|
||||
# Have a look at the docstring of the constructors of the LS implementations within solidlsp to see which options are available.
|
||||
# No documentation on options means no options are available
|
||||
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
|
||||
# No documentation on options means no options are available.
|
||||
#
|
||||
|
||||
tool_timeout: 240
|
||||
# timeout, in seconds, after which tool executions are terminated
|
||||
|
||||
@@ -485,14 +485,14 @@ class LanguageServerSymbolRetriever:
|
||||
return symbols
|
||||
|
||||
def get_document_symbols(self, relative_path: str) -> list[LanguageServerSymbol]:
|
||||
symbol_dicts, roots = self._lang_server.request_document_symbols(relative_path, include_body=False)
|
||||
symbol_dicts, _roots = self._lang_server.request_document_symbols(relative_path, include_body=False)
|
||||
symbols = [LanguageServerSymbol(s) for s in symbol_dicts]
|
||||
return symbols
|
||||
|
||||
def find_by_location(self, location: LanguageServerSymbolLocation) -> LanguageServerSymbol | None:
|
||||
if location.relative_path is None:
|
||||
return None
|
||||
symbol_dicts, roots = self._lang_server.request_document_symbols(location.relative_path, include_body=False)
|
||||
symbol_dicts, _roots = self._lang_server.request_document_symbols(location.relative_path, include_body=False)
|
||||
for symbol_dict in symbol_dicts:
|
||||
symbol = LanguageServerSymbol(symbol_dict)
|
||||
if symbol.location == location:
|
||||
|
||||
@@ -146,7 +146,7 @@ class FindFileTool(Tool):
|
||||
filename = os.path.basename(abs_path)
|
||||
return not fnmatch(filename, file_mask)
|
||||
|
||||
dirs, files = scan_directory(
|
||||
_dirs, files = scan_directory(
|
||||
path=dir_to_scan,
|
||||
recursive=True,
|
||||
is_ignored_dir=self.project.is_ignored_path,
|
||||
@@ -379,7 +379,7 @@ class SearchForPatternTool(Tool):
|
||||
if os.path.isfile(abs_path):
|
||||
rel_paths_to_search = [relative_path]
|
||||
else:
|
||||
dirs, rel_paths_to_search = scan_directory(
|
||||
_dirs, rel_paths_to_search = scan_directory(
|
||||
path=abs_path,
|
||||
recursive=True,
|
||||
is_ignored_dir=self.project.is_ignored_path,
|
||||
|
||||
@@ -89,7 +89,7 @@ class ClangdLanguageServer(SolidLanguageServer):
|
||||
)
|
||||
|
||||
clangd_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "clangd")
|
||||
dep = deps.single_for_current_platform()
|
||||
dep = deps.get_single_dep_for_current_platform()
|
||||
clangd_executable_path = deps.binary_path(clangd_ls_dir)
|
||||
if not os.path.exists(clangd_executable_path):
|
||||
logger.log(
|
||||
|
||||
@@ -112,7 +112,7 @@ class ClojureLSP(SolidLanguageServer):
|
||||
"""Setup runtime dependencies for clojure-lsp and return the command to start the server."""
|
||||
verify_clojure_cli()
|
||||
deps = ClojureLSP.runtime_dependencies
|
||||
dependency = deps.single_for_current_platform()
|
||||
dependency = deps.get_single_dep_for_current_platform()
|
||||
|
||||
clojurelsp_ls_dir = cls.ls_resources_dir(solidlsp_settings)
|
||||
clojurelsp_executable_path = deps.binary_path(clojurelsp_ls_dir)
|
||||
|
||||
@@ -4,8 +4,9 @@ import logging
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
from collections.abc import Iterable, Mapping, Sequence
|
||||
from dataclasses import dataclass, replace
|
||||
from typing import Any, cast
|
||||
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import FileUtils, PlatformUtils
|
||||
@@ -33,23 +34,47 @@ class RuntimeDependency:
|
||||
class RuntimeDependencyCollection:
|
||||
"""Utility to handle installation of runtime dependencies."""
|
||||
|
||||
def __init__(self, dependencies: Sequence[RuntimeDependency]):
|
||||
self._dependencies = list(dependencies)
|
||||
def __init__(self, dependencies: Sequence[RuntimeDependency], overrides: Iterable[Mapping[str, Any]] = ()) -> None:
|
||||
"""Initialize the collection with a list of dependencies and optional overrides.
|
||||
|
||||
def for_platform(self, platform_id: str) -> list[RuntimeDependency]:
|
||||
return [d for d in self._dependencies if d.platform_id in (platform_id, "any", "platform-agnostic", None)]
|
||||
:param dependencies: List of base RuntimeDependency instances. The combination of 'id' and 'platform_id' must be unique.
|
||||
:param overrides: List of dictionaries which represent overrides or additions to the base dependencies.
|
||||
Each entry must contain at least the 'id' key, and optionally 'platform_id' to uniquely identify the dependency to override.
|
||||
"""
|
||||
self._id_and_platform_id_to_dep: dict[tuple[str, str | None], RuntimeDependency] = {}
|
||||
for dep in dependencies:
|
||||
dep_key = (dep.id, dep.platform_id)
|
||||
if dep_key in self._id_and_platform_id_to_dep:
|
||||
raise ValueError(f"Duplicate runtime dependency with id '{dep.id}' and platform_id '{dep.platform_id}':\n{dep}")
|
||||
self._id_and_platform_id_to_dep[dep_key] = dep
|
||||
|
||||
def for_current_platform(self) -> list[RuntimeDependency]:
|
||||
return self.for_platform(PlatformUtils.get_platform_id().value)
|
||||
for dep_values_override in overrides:
|
||||
override_key = cast(tuple[str, str | None], (dep_values_override["id"], dep_values_override.get("platform_id")))
|
||||
base_dep = self._id_and_platform_id_to_dep.get(override_key)
|
||||
if base_dep is None:
|
||||
new_runtime_dep = RuntimeDependency(**dep_values_override)
|
||||
self._id_and_platform_id_to_dep[override_key] = new_runtime_dep
|
||||
else:
|
||||
self._id_and_platform_id_to_dep[override_key] = replace(base_dep, **dep_values_override)
|
||||
|
||||
def single_for_current_platform(self) -> RuntimeDependency:
|
||||
deps = self.for_current_platform()
|
||||
def get_dependencies_for_platform(self, platform_id: str) -> list[RuntimeDependency]:
|
||||
return [d for d in self._id_and_platform_id_to_dep.values() if d.platform_id in (platform_id, "any", "platform-agnostic", None)]
|
||||
|
||||
def get_dependencies_for_current_platform(self) -> list[RuntimeDependency]:
|
||||
return self.get_dependencies_for_platform(PlatformUtils.get_platform_id().value)
|
||||
|
||||
def get_single_dep_for_current_platform(self, dependency_id: str | None = None) -> RuntimeDependency:
|
||||
deps = self.get_dependencies_for_current_platform()
|
||||
if dependency_id is not None:
|
||||
deps = [d for d in deps if d.id == dependency_id]
|
||||
if len(deps) != 1:
|
||||
raise RuntimeError(f"Expected exactly one runtime dependency for {PlatformUtils.get_platform_id().value}, found {len(deps)}")
|
||||
raise RuntimeError(
|
||||
f"Expected exactly one runtime dependency for platform-{PlatformUtils.get_platform_id().value} and {dependency_id=}, found {len(deps)}"
|
||||
)
|
||||
return deps[0]
|
||||
|
||||
def binary_path(self, target_dir: str) -> str:
|
||||
dep = self.single_for_current_platform()
|
||||
dep = self.get_single_dep_for_current_platform()
|
||||
if not dep.binary_name:
|
||||
return target_dir
|
||||
return os.path.join(target_dir, dep.binary_name)
|
||||
@@ -61,7 +86,7 @@ class RuntimeDependencyCollection:
|
||||
"""
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
results: dict[str, str] = {}
|
||||
for dep in self.for_current_platform():
|
||||
for dep in self.get_dependencies_for_current_platform():
|
||||
if dep.url:
|
||||
self._install_from_url(dep, logger, target_dir)
|
||||
if dep.command:
|
||||
@@ -107,6 +132,7 @@ class RuntimeDependencyCollection:
|
||||
|
||||
@staticmethod
|
||||
def _install_from_url(dep: RuntimeDependency, logger: LanguageServerLogger, target_dir: str) -> None:
|
||||
assert dep.url is not None
|
||||
if dep.archive_type == "gz" and dep.binary_name:
|
||||
dest = os.path.join(target_dir, dep.binary_name)
|
||||
FileUtils.download_and_extract_archive(logger, dep.url, dest, dep.archive_type)
|
||||
|
||||
@@ -13,7 +13,7 @@ import threading
|
||||
import urllib.request
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
from typing import Any, cast
|
||||
|
||||
from overrides import override
|
||||
|
||||
@@ -27,10 +27,9 @@ from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
from solidlsp.util.zip import SafeZipExtractor
|
||||
|
||||
from .common import RuntimeDependency
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
# Runtime dependencies configuration
|
||||
RUNTIME_DEPENDENCIES = [
|
||||
_RUNTIME_DEPENDENCIES = [
|
||||
RuntimeDependency(
|
||||
id="CSharpLanguageServer",
|
||||
description="Microsoft.CodeAnalysis.LanguageServer for Windows (x64)",
|
||||
@@ -107,6 +106,14 @@ RUNTIME_DEPENDENCIES = [
|
||||
archive_type="tar.gz",
|
||||
binary_name="dotnet",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="DotNetRuntime",
|
||||
description=".NET 9 Runtime for Linux (ARM64)",
|
||||
url="https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.6/dotnet-runtime-9.0.6-linux-arm64.tar.gz",
|
||||
platform_id="linux-arm64",
|
||||
archive_type="tar.gz",
|
||||
binary_name="dotnet",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="DotNetRuntime",
|
||||
description=".NET 9 Runtime for macOS (x64)",
|
||||
@@ -172,11 +179,22 @@ def find_solution_or_project_file(root_dir) -> str | None:
|
||||
|
||||
class CSharpLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Provides C# specific instantiation of the LanguageServer class using Microsoft.CodeAnalysis.LanguageServer.
|
||||
This is the official Roslyn-based language server from Microsoft.
|
||||
Provides C# specific instantiation of the LanguageServer class using `Microsoft.CodeAnalysis.LanguageServer`,
|
||||
the official Roslyn-based language server from Microsoft.
|
||||
|
||||
You can pass the following entries in ls_specific_settings["csharp"]:
|
||||
- dotnet_runtime_url: will override the URL from RUNTIME_DEPENDENCIES
|
||||
You can pass a list of runtime dependency overrides in ls_specific_settings["csharp"]. This is a list of
|
||||
dicts, each containing at least the "id" key, and optionally "platform_id" to uniquely identify the dependency to override.
|
||||
For example, to override the URL of the .NET runtime on windows-x64, add the entry:
|
||||
|
||||
```
|
||||
{
|
||||
"id": "DotNetRuntime",
|
||||
"platform_id": "win-x64",
|
||||
"url": "https://example.com/custom-dotnet-runtime.zip"
|
||||
}
|
||||
```
|
||||
|
||||
See the `_RUNTIME_DEPENDENCIES` variable above for the available dependency ids and platform_ids.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -229,52 +247,43 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
Ensure .NET runtime and Microsoft.CodeAnalysis.LanguageServer are available.
|
||||
Returns a tuple of (dotnet_path, language_server_dll_path).
|
||||
"""
|
||||
runtime_id = CSharpLanguageServer._get_runtime_id()
|
||||
lang_server_dep, dotnet_runtime_dep = CSharpLanguageServer._get_runtime_dependencies(runtime_id)
|
||||
language_enum = cls.get_language_enum_instance()
|
||||
ls_specific_settings = solidlsp_settings.ls_specific_settings or {}
|
||||
language_specific_config = ls_specific_settings.get(language_enum, {})
|
||||
runtime_dependency_overrides = cast(list[dict[str, Any]], language_specific_config.get("runtime_dependencies", []))
|
||||
|
||||
logger.log("Resolving runtime dependencies", logging.DEBUG)
|
||||
if language_specific_config:
|
||||
logger.log(
|
||||
f"Language-specific config for {language_enum}: {language_specific_config}",
|
||||
logging.DEBUG,
|
||||
)
|
||||
|
||||
runtime_dependencies = RuntimeDependencyCollection(
|
||||
_RUNTIME_DEPENDENCIES,
|
||||
overrides=runtime_dependency_overrides,
|
||||
)
|
||||
|
||||
logger.log(
|
||||
f"Available runtime dependencies: {runtime_dependencies.get_dependencies_for_current_platform}",
|
||||
logging.DEBUG,
|
||||
)
|
||||
|
||||
# Find the dependencies for our platform
|
||||
lang_server_dep = runtime_dependencies.get_single_dep_for_current_platform("CSharpLanguageServer")
|
||||
dotnet_runtime_dep = runtime_dependencies.get_single_dep_for_current_platform("DotNetRuntime")
|
||||
dotnet_path = CSharpLanguageServer._ensure_dotnet_runtime(logger, dotnet_runtime_dep, solidlsp_settings)
|
||||
server_dll_path = CSharpLanguageServer._ensure_language_server(logger, lang_server_dep, solidlsp_settings)
|
||||
|
||||
return dotnet_path, server_dll_path
|
||||
|
||||
@staticmethod
|
||||
def _get_runtime_id() -> str:
|
||||
"""Determine the runtime ID based on the platform."""
|
||||
system = platform.system().lower()
|
||||
machine = platform.machine().lower()
|
||||
|
||||
if system == "windows":
|
||||
return "win-x64" if machine in ["amd64", "x86_64"] else "win-arm64"
|
||||
elif system == "darwin":
|
||||
return "osx-x64" if machine in ["x86_64"] else "osx-arm64"
|
||||
elif system == "linux":
|
||||
return "linux-x64" if machine in ["x86_64", "amd64"] else "linux-arm64"
|
||||
else:
|
||||
raise SolidLSPException(f"Unsupported platform: {system} {machine}")
|
||||
|
||||
@staticmethod
|
||||
def _get_runtime_dependencies(runtime_id: str) -> tuple[RuntimeDependency, RuntimeDependency]:
|
||||
"""Get the language server and .NET runtime dependencies for the platform."""
|
||||
lang_server_dep = None
|
||||
dotnet_runtime_dep = None
|
||||
|
||||
for dep in RUNTIME_DEPENDENCIES:
|
||||
if dep.id == "CSharpLanguageServer" and dep.platform_id == runtime_id:
|
||||
lang_server_dep = dep
|
||||
elif dep.id == "DotNetRuntime" and dep.platform_id == runtime_id:
|
||||
dotnet_runtime_dep = dep
|
||||
|
||||
if not lang_server_dep:
|
||||
raise SolidLSPException(f"No C# language server dependency found for platform {runtime_id}")
|
||||
if not dotnet_runtime_dep:
|
||||
raise SolidLSPException(f"No .NET runtime dependency found for platform {runtime_id}")
|
||||
|
||||
return lang_server_dep, dotnet_runtime_dep
|
||||
|
||||
@classmethod
|
||||
def _ensure_dotnet_runtime(
|
||||
cls, logger: LanguageServerLogger, dotnet_runtime_dep: RuntimeDependency, solidlsp_settings: SolidLSPSettings
|
||||
) -> str:
|
||||
"""Ensure .NET runtime is available and return the dotnet executable path."""
|
||||
# TODO: use RuntimeDependency util methods instead of custom validation/download logic
|
||||
|
||||
# Check if dotnet is already available on the system
|
||||
system_dotnet = shutil.which("dotnet")
|
||||
if system_dotnet:
|
||||
@@ -413,6 +422,8 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
Ensure .NET 9 runtime is available using runtime dependency configuration.
|
||||
Returns the path to the dotnet executable.
|
||||
"""
|
||||
# TODO: use RuntimeDependency util methods instead of custom download logic
|
||||
|
||||
# Check if dotnet is already available on the system
|
||||
system_dotnet = shutil.which("dotnet")
|
||||
if system_dotnet:
|
||||
|
||||
@@ -62,6 +62,7 @@ class SourceKitLSP(SolidLanguageServer):
|
||||
self.server_ready = threading.Event()
|
||||
self.request_id = 0
|
||||
self._did_sleep_before_requesting_references = False
|
||||
self._initialization_timestamp = None
|
||||
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
@@ -345,14 +346,40 @@ class SourceKitLSP(SolidLanguageServer):
|
||||
self.server_ready.set()
|
||||
self.server_ready.wait()
|
||||
|
||||
# Mark initialization timestamp for smarter delay calculation
|
||||
self._initialization_timestamp = time.time()
|
||||
|
||||
@override
|
||||
def request_references(self, relative_file_path: str, line: int, column: int) -> list[ls_types.Location]:
|
||||
# SourceKit LSP needs a short initialization period after startup
|
||||
# SourceKit LSP needs initialization + indexing time after startup
|
||||
# before it can provide accurate reference information. This sleep
|
||||
# prevents race conditions where references might not be available yet.
|
||||
# Unfortunately, sourcekit doesn't send a signal when it's really ready
|
||||
# CI environments need extra time for project indexing and cross-file analysis
|
||||
if not self._did_sleep_before_requesting_references:
|
||||
self.logger.log("Sleeping 5s before requesting references for the first time", logging.DEBUG)
|
||||
time.sleep(5)
|
||||
# Calculate minimum delay based on how much time has passed since initialization
|
||||
if self._initialization_timestamp:
|
||||
elapsed = time.time() - self._initialization_timestamp
|
||||
# Increased CI delay for project indexing: 15s CI, 5s local
|
||||
base_delay = 15 if os.getenv("CI") else 5
|
||||
remaining_delay = max(2, base_delay - elapsed)
|
||||
else:
|
||||
# Fallback if initialization timestamp is missing
|
||||
remaining_delay = 15 if os.getenv("CI") else 5
|
||||
|
||||
self.logger.log(
|
||||
f"Sleeping {remaining_delay:.1f}s before requesting references for the first time (CI needs extra indexing time)",
|
||||
logging.INFO,
|
||||
)
|
||||
time.sleep(remaining_delay)
|
||||
self._did_sleep_before_requesting_references = True
|
||||
return super().request_references(relative_file_path, line, column)
|
||||
|
||||
# Get references with retry logic for CI stability
|
||||
references = super().request_references(relative_file_path, line, column)
|
||||
|
||||
# In CI, if no references found, retry once after additional delay
|
||||
if os.getenv("CI") and not references:
|
||||
self.logger.log("No references found in CI - retrying after additional 5s delay", logging.INFO)
|
||||
time.sleep(5)
|
||||
references = super().request_references(relative_file_path, line, column)
|
||||
|
||||
return references
|
||||
@@ -108,7 +108,7 @@ class TerraformLS(SolidLanguageServer):
|
||||
),
|
||||
]
|
||||
)
|
||||
dependency = deps.single_for_current_platform()
|
||||
dependency = deps.get_single_dep_for_current_platform()
|
||||
|
||||
terraform_ls_executable_path = deps.binary_path(cls.ls_resources_dir(solidlsp_settings))
|
||||
if not os.path.exists(terraform_ls_executable_path):
|
||||
|
||||
+24
-1
@@ -1105,7 +1105,30 @@ class SolidLanguageServer(ABC):
|
||||
if symbol["kind"] == ls_types.SymbolKind.File:
|
||||
# For file symbols, process their children (top-level symbols)
|
||||
for child in symbol["children"]:
|
||||
path = Path(child["location"]["absolutePath"]).resolve().relative_to(self.repository_root_path)
|
||||
# Handle cross-platform path resolution (fixes Docker/macOS path issues)
|
||||
absolute_path = Path(child["location"]["absolutePath"]).resolve()
|
||||
repository_root = Path(self.repository_root_path).resolve()
|
||||
|
||||
# Try pathlib first, fallback to alternative approach if paths are incompatible
|
||||
try:
|
||||
path = absolute_path.relative_to(repository_root)
|
||||
except ValueError:
|
||||
# If paths are from different roots (e.g., /workspaces vs /Users),
|
||||
# use the relativePath from location if available, or extract from absolutePath
|
||||
if "relativePath" in child["location"] and child["location"]["relativePath"]:
|
||||
path = Path(child["location"]["relativePath"])
|
||||
else:
|
||||
# Extract relative path by finding common structure
|
||||
# Example: /workspaces/.../test_repo/file.py -> test_repo/file.py
|
||||
path_parts = absolute_path.parts
|
||||
|
||||
# Find the last common part or use a fallback
|
||||
if "test_repo" in path_parts:
|
||||
test_repo_idx = path_parts.index("test_repo")
|
||||
path = Path(*path_parts[test_repo_idx:])
|
||||
else:
|
||||
# Last resort: use filename only
|
||||
path = Path(absolute_path.name)
|
||||
result[str(path)].append(child)
|
||||
# For package/directory symbols, process their children
|
||||
for child in symbol["children"]:
|
||||
|
||||
@@ -5,7 +5,10 @@ Defines settings for Solid-LSP
|
||||
import os
|
||||
import pathlib
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -20,8 +23,12 @@ class SolidLSPSettings:
|
||||
For instance, if this is ".solidlsp" and the project is located at "/home/user/myproject",
|
||||
then Solid-LSP will store project-specific data in "/home/user/myproject/.solidlsp".
|
||||
"""
|
||||
ls_specific_settings: dict[str, Any] = field(default_factory=dict)
|
||||
"""Mapping from language server class names to any specifics that the language server may make use of."""
|
||||
ls_specific_settings: dict["Language", Any] = field(default_factory=dict)
|
||||
"""
|
||||
Advanced configuration option allowing to configure language server implementation specific options.
|
||||
Have a look at the docstring of the constructors of the corresponding LS implementations within solidlsp to see which options are available.
|
||||
No documentation on options means no options are available.
|
||||
"""
|
||||
|
||||
def __post_init__(self):
|
||||
os.makedirs(str(self.solidlsp_dir), exist_ok=True)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
.gradle/
|
||||
Reference in new issue
Block a user