mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 04:15:11 +00:00
66 files changed
+1387
-845
No files matched your search
@@ -28,6 +28,12 @@ jobs:
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '>=1.17.0'
|
||||
- name: Ensure cached directory exist before calling cache-related actions
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p $HOME/.serena/language_servers/static
|
||||
mkdir -p $HOME/.cache/go-build
|
||||
mkdir -p $HOME/go/bin
|
||||
# Add Go bin directory to PATH for this workflow
|
||||
# GITHUB_PATH is a special file that GitHub Actions uses to modify PATH
|
||||
# Writing to this file adds the directory to the PATH for subsequent steps
|
||||
@@ -49,7 +55,6 @@ jobs:
|
||||
with:
|
||||
elixir-version: '1.18.4'
|
||||
otp-version: '26.1'
|
||||
|
||||
- name: Prepare java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
@@ -77,7 +82,7 @@ jobs:
|
||||
id: cache-language-servers
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: src/solidlsp/language_servers/static
|
||||
path: ~/.serena/language_servers/static
|
||||
key: language-servers-${{ runner.os }}-v1
|
||||
restore-keys: |
|
||||
language-servers-${{ runner.os }}-
|
||||
|
||||
@@ -2,8 +2,15 @@
|
||||
|
||||
Status of the `main` branch. Changes prior to the next official version change will appear here.
|
||||
|
||||
* Language servers:
|
||||
* Reliably detect language server termination and propagate the respective error all the way
|
||||
back to the tool application, where an unexpected termination is handled by restarting the language server
|
||||
and subsequently retrying the tool application.
|
||||
|
||||
Fixes:
|
||||
* Ignore `.git` in check for ignored paths and improve performance of `find_all_non_ignored_files`
|
||||
* Fix language server startup issues on Windows when using Claude Code (which was due to
|
||||
default shell reconfiguration imposed by Claude Code)
|
||||
|
||||
# 0.1.3
|
||||
|
||||
|
||||
@@ -195,6 +195,16 @@ Note that no matter how you run the MCP server, Serena will, by default, start a
|
||||
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
|
||||
|
||||
`uvx` can be used to run the latest version of Serena directly from the repository, without an explicit local installation.
|
||||
|
||||
```shell
|
||||
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
|
||||
|
||||
1. Clone the repository and change into it.
|
||||
@@ -202,38 +212,19 @@ This and other settings can be adjusted in the [configuration](#configuration) a
|
||||
git clone https://github.com/oraios/serena
|
||||
cd serena
|
||||
```
|
||||
2. Optionally create the configuration file in your home directory, i.e.
|
||||
|
||||
* `~/.serena/serena_config.yml` on Linux and macOS, or
|
||||
* `%USERPROFILE%\.serena\serena_config.yml` on Windows.
|
||||
|
||||
by copying the template and then adjusting it according to your needs:
|
||||
2. Optionally edit the configuration file in your home directory with
|
||||
```shell
|
||||
mkdir ~/.serena
|
||||
cp src/serena/resources/serena_config.template.yml ~/.serena/serena_config.yml
|
||||
uv run serena config edit
|
||||
```
|
||||
If you just want the default config, you can skip this part, and a config file will be created when you first run Serena.
|
||||
3. Run the server with `uv`:
|
||||
```shell
|
||||
uv run serena-mcp-server
|
||||
uv run serena start-mcp-server
|
||||
```
|
||||
When running from outside the serena installation directory, be sure to pass it, i.e. use
|
||||
When running from outside the serena installation directory, be sure to pass it, i.e., use
|
||||
```shell
|
||||
uv run --directory /abs/path/to/serena serena-mcp-server
|
||||
```
|
||||
|
||||
##### Using uvx
|
||||
|
||||
`uvx` can be used to run the latest version of Serena directly from the repository, without an explicit local installation.
|
||||
|
||||
* Windows:
|
||||
```shell
|
||||
uvx --from git+https://github.com/oraios/serena serena-mcp-server.exe
|
||||
```
|
||||
* Other operating systems:
|
||||
```shell
|
||||
uvx --from git+https://github.com/oraios/serena serena-mcp-server
|
||||
```
|
||||
uv run --directory /abs/path/to/serena serena start-mcp-server
|
||||
```
|
||||
|
||||
##### Using Docker (Experimental)
|
||||
|
||||
@@ -243,7 +234,7 @@ You can run the Serena MCP server directly via docker as follows,
|
||||
assuming that the projects you want to work on are all located in `/path/to/your/projects`:
|
||||
|
||||
```shell
|
||||
docker run --rm -i --network host -v /path/to/your/projects:/workspaces/projects ghcr.io/oraios/serena:latest serena-mcp-server --transport stdio
|
||||
docker run --rm -i --network host -v /path/to/your/projects:/workspaces/projects ghcr.io/oraios/serena:latest serena start-mcp-server --transport stdio
|
||||
```
|
||||
|
||||
Replace `/path/to/your/projects` with the absolute path to your projects directory. The Docker approach provides:
|
||||
@@ -263,15 +254,15 @@ 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,
|
||||
i.e. you start the server and provide the client with the URL to connect to it.
|
||||
|
||||
Simply provide `serena-mcp-server` with the `--transport sse` option and optionally provide the port.
|
||||
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,
|
||||
you would run this command from the Serena directory,
|
||||
|
||||
```shell
|
||||
uv run serena-mcp-server --transport sse --port 9121
|
||||
uv run serena start-mcp-server --transport sse --port 9121
|
||||
```
|
||||
|
||||
and then configure your client to connect to `http://localhost/sse:9121`.
|
||||
and then configure your client to connect to `http://localhost:9121/sse`.
|
||||
|
||||
|
||||
#### Command-Line Arguments
|
||||
@@ -289,12 +280,22 @@ Serena's behavior (active tools and prompts as well as logging configuration, et
|
||||
1. The `serena_config.yml` for general settings that apply to all clients and projects.
|
||||
It is located in your user directory under `.serena/serena_config.yml`.
|
||||
If you do not explicitly create the file, it will be auto-generated when you first run Serena.
|
||||
2. In the arguments passed to the `serena-mcp-server` in your client's config (see below),
|
||||
You can edit it directly or use
|
||||
```shell
|
||||
uvx --from git+https://github.com/oraios/serena serena config edit
|
||||
```
|
||||
(or use the `--directory` command version).
|
||||
2. In the arguments passed to the `start-mcp-server` in your client's config (see below),
|
||||
which will apply to all sessions started by the respective client. In particular, the [context](#contexts) parameter
|
||||
should be set appropriately for Serena to be best adjusted to existing tools and capabilities of your client.
|
||||
See for a detailed explanation. You can override all entries from the `serena_config.yml` through command line arguments.
|
||||
3. In the `.serena/project.yml` file within your project. This will hold project-level configuration that is used whenever
|
||||
that project is activated.
|
||||
that project is activated. This file will be autogenerated when you first use Serena on that project, but you can also
|
||||
generate it explicitly with
|
||||
```shell
|
||||
uvx --from git+https://github.com/oraios/serena serena project generate-yml
|
||||
```
|
||||
(or use the `--directory` command version).
|
||||
4. Through the currently active set of [modes](#modes).
|
||||
|
||||
|
||||
@@ -323,20 +324,17 @@ project, the file `.serena/project.yml` will be generated. You can adjust the la
|
||||
same name.
|
||||
|
||||
If you are mostly working with the same project, you can also configure to always activate a project at startup
|
||||
by passing `--project <path_or_name>` to the `serena-mcp-server` command in your client's MCP config.
|
||||
by passing `--project <path_or_name>` to the `start-mcp-server` command in your client's MCP config.
|
||||
|
||||
ℹ️ For larger projects, we recommend that you index your project to accelerate Serena's tools; otherwise the first
|
||||
tool application may be very slow.
|
||||
To do so, run one of these commands the project directory or pass the path to the project as an argument:
|
||||
To do so, run this from the project directory (or pass the path to the project as an argument):
|
||||
|
||||
* When using a local installation:
|
||||
```shell
|
||||
uv run --directory /abs/path/to/serena index-project
|
||||
```
|
||||
* When using uvx:
|
||||
```shell
|
||||
uvx --from git+https://github.com/oraios/serena index-project
|
||||
```
|
||||
```shell
|
||||
uvx --from git+https://github.com/oraios/serena serena project index
|
||||
```
|
||||
|
||||
(or use the `--directory` command version).
|
||||
|
||||
### Claude Code
|
||||
|
||||
@@ -351,15 +349,16 @@ claude mcp add serena -- <serena-mcp-server> --context ide-assistant --project $
|
||||
where `<serena-mcp-server>` is your way of [running the Serena MCP server](#running-the-serena-mcp-server).
|
||||
For example, when using `uvx`, you would run
|
||||
```shell
|
||||
claude mcp add serena -- uvx --from git+https://github.com/oraios/serena serena-mcp-server --context ide-assistant --project $(pwd)
|
||||
claude mcp add serena -- uvx --from git+https://github.com/oraios/serena serena start-mcp-server --context ide-assistant --project $(pwd)
|
||||
```
|
||||
|
||||
ℹ️ Serena comes with an instruction text, and Claude needs to read it to properly use Serena's tools.
|
||||
Once in Claude Code, you can ask to "read Serena's initial instructions" or run `/mcp__serena__initial_instructions` to load the instruction text.
|
||||
Do this whenever you start a new conversation and after any compacting operation to ensure Claude remains properly configured to use Serena's tools.
|
||||
|
||||
ℹ️ **NEW**: an alternative to the above is adding the instructions as part of the system prompt, then you will not need to run the command above or to remember re-running it after compacting.
|
||||
This can be achieved through starting claude code with `claude --append-system-prompt $(uvx --from git+https://github.com/oraios/serena serena print-system-prompt)`. Note that this is **experimental**, Claude may not understand the instructions correctly in this way, and we haven't thoroughly tested the resulting behavior. Please report any issues you encounter.
|
||||
ℹ️ Serena comes with an instruction text, and Claude needs to read it to properly use Serena's tools.
|
||||
As of version `v1.0.52`, claude code reads the instructions of the MCP server, so this **is handled automatically**.
|
||||
If you are using an older version, or if Claude fails to read the instructions, you can ask it explicitly
|
||||
to "read Serena's initial instructions" or run `/mcp__serena__initial_instructions` to load the instruction text.
|
||||
If you want to make use of that, you will have to enable the corresponding tool explicitly by adding `initial_instructions` to the `included_optional_tools`
|
||||
in your config.
|
||||
Note that you may have to make Claude read the instructions you start a new conversation and after any compacting operation to ensure Claude remains properly configured to use Serena's tools.
|
||||
|
||||
|
||||
### Claude Desktop
|
||||
@@ -374,7 +373,7 @@ Add the `serena` MCP server configuration, using a [run command](#running-the-se
|
||||
"mcpServers": {
|
||||
"serena": {
|
||||
"command": "/abs/path/to/uv",
|
||||
"args": ["run", "--directory", "/abs/path/to/serena", "serena-mcp-server"]
|
||||
"args": ["run", "--directory", "/abs/path/to/serena", "serena", "start-mcp-server"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -385,7 +384,7 @@ Add the `serena` MCP server configuration, using a [run command](#running-the-se
|
||||
"mcpServers": {
|
||||
"serena": {
|
||||
"command": "/abs/path/to/uvx",
|
||||
"args": ["--from", "git+https://github.com/oraios/serena", "serena-mcp-server"]
|
||||
"args": ["--from", "git+https://github.com/oraios/serena", "serena", "start-mcp-server"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -396,7 +395,7 @@ Add the `serena` MCP server configuration, using a [run command](#running-the-se
|
||||
"mcpServers": {
|
||||
"serena": {
|
||||
"command": "docker",
|
||||
"args": ["run", "--rm", "-i", "--network", "host", "-v", "/path/to/your/projects:/workspaces/projects", "ghcr.io/oraios/serena:latest", "serena-mcp-server", "--transport", "stdio"]
|
||||
"args": ["run", "--rm", "-i", "--network", "host", "-v", "/path/to/your/projects:/workspaces/projects", "ghcr.io/oraios/serena:latest", "serena", "start-mcp-server", "--transport", "stdio"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -578,14 +577,20 @@ When launching Serena, specify modes using `--mode <mode-name>`; multiple modes
|
||||
#### Customization
|
||||
|
||||
You can create your own contexts and modes to precisely tailor Serena to your needs in two ways:
|
||||
* **Adding to Serena's configuration directory**: Create new `.yml` files in the `config/contexts/` or `config/modes/` directories within your local Serena repository. These custom contexts/modes will be automatically registered and available for use by their name (filename without the `.yml` extension). They will also appear in listings of available contexts/modes.
|
||||
* **Using external YAML files**: When starting Serena, you can provide an absolute path to a custom `.yml` file for a context or mode.
|
||||
* You can use Serena's CLI to manage modes and contexts. Check out
|
||||
```shell
|
||||
uvx --from git+https://github.com/oraios/serena serena mode --help
|
||||
uvx --from git+https://github.com/oraios/serena serena context --help
|
||||
```
|
||||
*NOTE*: Custom contexts/modes are simply YAML files in `<home>/.serena`, they are automatically registered and available for use by their name (filename without the `.yml` extension). If you don't want to use Serena's CLI, you can create and manage them in any way you see fit.
|
||||
* **Using external YAML files**: When starting Serena, you can also provide an absolute path to a custom `.yml` file for a context or mode.
|
||||
|
||||
A context or mode YAML file typically defines:
|
||||
* `name`: (Optional if filename is used) The name of the context/mode.
|
||||
* `prompt`: A string that will be incorporated into Serena's system prompt.
|
||||
* `description`: (Optional) A brief description.
|
||||
* `description`: (Optional) A brief description, not passed to the LLM.
|
||||
* `excluded_tools`: A list of tool names (strings) to disable when this context/mode is active.
|
||||
* `included_optional_tools`: A list of tool names that are disabled by default and have to be explicitly enabled by the user.
|
||||
|
||||
This customization allows for deep integration and adaptation of Serena to specific project requirements or personal preferences.
|
||||
|
||||
@@ -738,11 +743,10 @@ subscriptions.
|
||||
|
||||
### Subscription-Based Coding Agents
|
||||
|
||||
The most prominent subscription-based coding agents are parts of IDEs like
|
||||
Many prominent subscription-based coding agents are parts of IDEs like
|
||||
Windsurf, Cursor and VSCode.
|
||||
Serena's functionality is similar to Cursor's Agent, Windsurf's Cascade or
|
||||
VSCode's
|
||||
upcoming [agent mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode).
|
||||
VSCode's agent mode.
|
||||
|
||||
Serena has the advantage of not requiring a subscription.
|
||||
A potential disadvantage is that it
|
||||
@@ -750,7 +754,7 @@ is not directly integrated into an IDE, so the inspection of newly written code
|
||||
is not as seamless.
|
||||
|
||||
More technical differences are:
|
||||
* Serena is not bound to a specific IDE.
|
||||
* Serena is not bound to a specific IDE or CLI.
|
||||
Serena's MCP server can be used with any MCP client (including some IDEs),
|
||||
and the Agno-based agent provides additional ways of applying its functionality.
|
||||
* Serena is not bound to a specific large language model or API.
|
||||
@@ -826,7 +830,7 @@ For details on contributing, see [here](/CONTRIBUTING.md).
|
||||
|
||||
## Full List of Tools
|
||||
|
||||
Here is the full list of Serena's tools with a short description (output of `uv run serena-list-tools`):
|
||||
Here is the full list of Serena's tools with a short description (output of `uv run serena tools list`):
|
||||
|
||||
* `activate_project`: Activates a project by name.
|
||||
* `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
||||
@@ -840,9 +844,7 @@ Here is the full list of Serena's tools with a short description (output of `uv
|
||||
* `get_active_project`: Gets the name of the currently active project (if any) and lists existing projects
|
||||
* `get_current_config`: Prints the current configuration of the agent, including the active modes, tools, and context.
|
||||
* `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file or directory.
|
||||
* `initial_instructions`: Gets the initial instructions for the current project.
|
||||
Should only be used in settings where the system prompt cannot be set,
|
||||
e.g. in clients you have no control over, like Claude Desktop.
|
||||
* `initial_instructions`: Gets the initial instructions for the current project. Disabled by default, has to be enabled explicitly by using `included_optional_tools`
|
||||
* `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.
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
|
||||
# Connecting Serena MCP Server to ChatGPT via MCPO & Cloudflare Tunnel
|
||||
|
||||
This guide explains how to expose a **locally running Serena MCP server** (powered by MCPO) to the internet using **Cloudflare Tunnel**, and how to connect it to **ChatGPT as a Custom GPT with tool access**.
|
||||
|
||||
Once configured, ChatGPT becomes a powerful **coding agent** with direct access to your codebase, shell, and file system — so **read the security notes carefully**.
|
||||
|
||||
---
|
||||
## Prerequisites
|
||||
|
||||
Make sure you have [uv](https://docs.astral.sh/uv/getting-started/installation/)
|
||||
and [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) installed.
|
||||
|
||||
## 1. Start the Serena MCP Server via MCPO
|
||||
|
||||
Run the following command to launch Serena as http server (assuming port 8000):
|
||||
|
||||
```bash
|
||||
uvx mcpo --port 8000 --api-key <YOUR_SECRET_KEY> -- \
|
||||
uvx --from git+https://github.com/oraios/serena \
|
||||
serena start-mcp-server --context chatgpt --project $(pwd)
|
||||
```
|
||||
|
||||
- `--api-key` is required to secure the server.
|
||||
- `--project` should point to the root of your codebase.
|
||||
|
||||
You can also use other options, and you don't have to pass `--project` if you want to work on multiple projects
|
||||
or want to activate it later. See
|
||||
|
||||
```shell
|
||||
uvx --from git+https://github.com/oraios/serena serena start-mcp-server --help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Expose the Server Using Cloudflare Tunnel
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cloudflared tunnel --url http://localhost:8000
|
||||
```
|
||||
|
||||
This will give you a **public HTTPS URL** like:
|
||||
|
||||
```
|
||||
https://serena-agent-tunnel.trycloudflare.com
|
||||
```
|
||||
|
||||
Your server is now securely exposed to the internet.
|
||||
|
||||
---
|
||||
|
||||
## 3. Connect It to ChatGPT (Custom GPT)
|
||||
|
||||
### Steps:
|
||||
|
||||
1. Go to [ChatGPT → Explore GPTs → Create](https://chat.openai.com/gpts/editor)
|
||||
2. During setup, click **“Add APIs”**
|
||||
3. Set up **API Key authentication** with the auth type as **Bearer** and enter the api key you used to start the MCPO server.
|
||||
4. In the **Schema** section, click on **import from URL** and paste `<cloudflared_url>/openapi.json` with the URL you got from the previous step.
|
||||
5. Add the following line to the top of the imported JSON schema:
|
||||
```
|
||||
"servers": ["url": "<cloudflared_url>"],
|
||||
```
|
||||
**Important**: don't include a trailing slash at the end of the URL!
|
||||
|
||||
ChatGPT will read the schema and create functions automatically.
|
||||
|
||||
---
|
||||
|
||||
## Security Warning — Read Carefully
|
||||
|
||||
Depending on your configuration and enabled tools, Serena's MCP server may:
|
||||
- Execute **arbitrary shell commands**
|
||||
- Read, write, and modify **files in your codebase**
|
||||
|
||||
This gives ChatGPT the same powers as a remote developer on your machine.
|
||||
|
||||
### ⚠️ Key Rules:
|
||||
- **NEVER expose your API key**
|
||||
- **Only expose this server when needed**, and monitor its use.
|
||||
|
||||
In your project’s `.serena/project.yml` or global config, you can disable tools like:
|
||||
|
||||
```yaml
|
||||
excluded_tools:
|
||||
- execute_shell_command
|
||||
- ...
|
||||
read_only: true
|
||||
```
|
||||
|
||||
This is strongly recommended if you want a read-only or safer agent.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Final Thoughts
|
||||
|
||||
With this setup, ChatGPT becomes a coding assistant **running on your local code** — able to index, search, edit, and even run shell commands depending on your configuration.
|
||||
|
||||
Use responsibly, and keep security in mind.
|
||||
+2
-2
@@ -18,9 +18,9 @@ dependencies = [
|
||||
"pyright>=1.1.396,<2",
|
||||
"overrides>=7.7.0,<8",
|
||||
"python-dotenv>=1.0.0, <2",
|
||||
"mcp>=1.5.0",
|
||||
"mcp==1.12.3",
|
||||
"flask>=3.0.0",
|
||||
"sensai-utils>=1.4.0",
|
||||
"sensai-utils>=1.5.0",
|
||||
"pydantic>=2.10.6",
|
||||
"types-pyyaml>=6.0.12.20241230",
|
||||
"pyyaml>=6.0.2",
|
||||
|
||||
@@ -9,7 +9,7 @@ from pprint import pprint
|
||||
from serena.agent import SerenaAgent
|
||||
from serena.config.serena_config import SerenaConfig
|
||||
from serena.constants import REPO_ROOT
|
||||
from serena.tools import FindFileTool, FindReferencingSymbolsTool, SearchForPatternTool
|
||||
from serena.tools import FindFileTool, FindReferencingSymbolsTool, GetSymbolsOverviewTool, SearchForPatternTool
|
||||
|
||||
if __name__ == "__main__":
|
||||
agent = SerenaAgent(project=REPO_ROOT, serena_config=SerenaConfig(gui_log_window_enabled=False, web_dashboard=False))
|
||||
@@ -18,13 +18,9 @@ if __name__ == "__main__":
|
||||
find_refs_tool = agent.get_tool(FindReferencingSymbolsTool)
|
||||
find_file_tool = agent.get_tool(FindFileTool)
|
||||
search_pattern_tool = agent.get_tool(SearchForPatternTool)
|
||||
overview_tool = agent.get_tool(GetSymbolsOverviewTool)
|
||||
|
||||
result = agent.execute_task(
|
||||
lambda: search_pattern_tool.apply(
|
||||
r"def request_full_.*?\).*?\)",
|
||||
restrict_search_to_code_files=False,
|
||||
relative_path="src/solidlsp",
|
||||
paths_include_glob="**/ls.py",
|
||||
)
|
||||
lambda: overview_tool.apply("src/solidlsp/ls.py"),
|
||||
)
|
||||
pprint(json.loads(result))
|
||||
@@ -7,14 +7,14 @@ from pathlib import Path
|
||||
from sensai.util import logging
|
||||
|
||||
from interprompt import autogenerate_prompt_factory_module
|
||||
from serena.constants import PROMPT_TEMPLATES_DIR, REPO_ROOT
|
||||
from serena.constants import PROMPT_TEMPLATES_DIR_INTERNAL, REPO_ROOT
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def main():
|
||||
autogenerate_prompt_factory_module(
|
||||
prompts_dir=PROMPT_TEMPLATES_DIR,
|
||||
prompts_dir=PROMPT_TEMPLATES_DIR_INTERNAL,
|
||||
target_module_path=str(Path(REPO_ROOT) / "src" / "serena" / "generated" / "generated_prompt_factory.py"),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import logging
|
||||
import os
|
||||
from enum import Enum
|
||||
from typing import Any, Generic, TypeVar
|
||||
from typing import Any, Generic, Literal, TypeVar
|
||||
|
||||
import yaml
|
||||
from sensai.util.string import ToStringMixin
|
||||
@@ -92,6 +92,9 @@ class _MultiLangContainer(Generic[T], ToStringMixin):
|
||||
raise KeyError(f"Item for language '{lang_code}' already registered for name '{self.name}'")
|
||||
self._lang2item[lang_code] = item
|
||||
|
||||
def has_item(self, lang_code: str = DEFAULT_LANG_CODE) -> bool:
|
||||
return lang_code in self._lang2item
|
||||
|
||||
def get_item(self, lang: str = DEFAULT_LANG_CODE, fallback_mode: LanguageFallbackMode = LanguageFallbackMode.EXCEPTION) -> T:
|
||||
"""
|
||||
Gets the item for the given language.
|
||||
@@ -183,6 +186,9 @@ class MultiLangPromptTemplate(ParameterizedTemplateInterface):
|
||||
prompt_template = self.get_prompt_template(lang_code, fallback_mode)
|
||||
return prompt_template.render(**params)
|
||||
|
||||
def has_item(self, lang_code: str = DEFAULT_LANG_CODE) -> bool:
|
||||
return self._prompts_container.has_item(lang_code)
|
||||
|
||||
|
||||
class MultiLangPromptList(_MultiLangContainer[PromptList]):
|
||||
pass
|
||||
@@ -212,44 +218,96 @@ class MultiLangPromptCollection:
|
||||
The prompt names must be unique (for the same language) within the collection.
|
||||
"""
|
||||
|
||||
def __init__(self, prompts_dir: str, fallback_mode: LanguageFallbackMode = LanguageFallbackMode.EXCEPTION) -> None:
|
||||
def __init__(self, prompts_dir: str | list[str], fallback_mode: LanguageFallbackMode = LanguageFallbackMode.EXCEPTION) -> None:
|
||||
"""
|
||||
:param prompts_dir: the directory containing the prompt templates and prompt lists
|
||||
:param prompts_dir: the directory containing the prompt templates and prompt lists.
|
||||
If a list is provided, will look for prompt templates in the dirs from left to right
|
||||
(first one containing the desired template wins).
|
||||
:param fallback_mode: the fallback mode to use when a prompt template or prompt list is not found for the requested language.
|
||||
May be reset after initialization.
|
||||
"""
|
||||
self._multi_lang_prompt_templates: dict[str, MultiLangPromptTemplate] = {}
|
||||
self._multi_lang_prompt_lists: dict[str, MultiLangPromptList] = {}
|
||||
self._load_from_disc(prompts_dir)
|
||||
if isinstance(prompts_dir, str):
|
||||
prompts_dir = [prompts_dir]
|
||||
|
||||
# Add prompts from multiple directories, prioritizing names from the left.
|
||||
# If name collisions appear in the first directory, an error is raised (so the first directory should have no
|
||||
# internal collisions, this helps in avoiding errors)
|
||||
# For all following directories, on a collision the new value will be ignored.
|
||||
# This also means that for the following directories, there is no error check on collisions internal to them.
|
||||
# We assume that they are correct (i.e., they have no internal collisions).
|
||||
first_prompts_dir, fallback_prompt_dirs = prompts_dir[0], prompts_dir[1:]
|
||||
self._load_from_disc(first_prompts_dir, on_name_collision="raise")
|
||||
for fallback_prompt_dir in fallback_prompt_dirs:
|
||||
# already loaded prompts have priority
|
||||
self._load_from_disc(fallback_prompt_dir, on_name_collision="skip")
|
||||
|
||||
self.fallback_mode = fallback_mode
|
||||
|
||||
def _add_prompt_template(self, name: str, template_str: str, lang_code: str = DEFAULT_LANG_CODE) -> None:
|
||||
def _add_prompt_template(
|
||||
self,
|
||||
name: str,
|
||||
template_str: str,
|
||||
lang_code: str = DEFAULT_LANG_CODE,
|
||||
on_name_collision: Literal["skip", "overwrite", "raise"] = "raise",
|
||||
) -> None:
|
||||
"""
|
||||
:param name: name of the prompt template
|
||||
:param template_str: the Jinja template string
|
||||
:param lang_code: the language code for which to add the prompt template.
|
||||
:param on_name_collision: how to deal with name/lang_code collisions
|
||||
"""
|
||||
allow_overwrite = False
|
||||
prompt_template = PromptTemplate(name, template_str)
|
||||
mlpt = self._multi_lang_prompt_templates.get(name)
|
||||
if mlpt is None:
|
||||
mlpt = MultiLangPromptTemplate(name)
|
||||
self._multi_lang_prompt_templates[name] = mlpt
|
||||
mlpt.add_prompt_template(prompt_template, lang_code=lang_code)
|
||||
if mlpt.has_item(lang_code):
|
||||
if on_name_collision == "raise":
|
||||
raise KeyError(f"Prompt '{name}' for {lang_code} already exists!")
|
||||
if on_name_collision == "skip":
|
||||
log.debug(f"Skipping prompt '{name}' since it already exists.")
|
||||
return
|
||||
elif on_name_collision == "overwrite":
|
||||
allow_overwrite = True
|
||||
mlpt.add_prompt_template(prompt_template, lang_code=lang_code, allow_overwrite=allow_overwrite)
|
||||
|
||||
def _add_prompt_list(self, name: str, prompt_list: list[str], lang_code: str = DEFAULT_LANG_CODE) -> None:
|
||||
def _add_prompt_list(
|
||||
self,
|
||||
name: str,
|
||||
prompt_list: list[str],
|
||||
lang_code: str = DEFAULT_LANG_CODE,
|
||||
on_name_collision: Literal["skip", "overwrite", "raise"] = "raise",
|
||||
) -> None:
|
||||
"""
|
||||
:param name: name of the prompt list
|
||||
:param prompt_list: a list of prompts
|
||||
:param lang_code: the language code for which to add the prompt list.
|
||||
:param on_name_collision: how to deal with name/lang_code collisions
|
||||
"""
|
||||
allow_overwrite = False
|
||||
multilang_prompt_list = self._multi_lang_prompt_lists.get(name)
|
||||
if multilang_prompt_list is None:
|
||||
multilang_prompt_list = MultiLangPromptList(name)
|
||||
self._multi_lang_prompt_lists[name] = multilang_prompt_list
|
||||
multilang_prompt_list.add_item(PromptList(prompt_list), lang_code=lang_code)
|
||||
if multilang_prompt_list.has_item(lang_code):
|
||||
if on_name_collision == "raise":
|
||||
raise KeyError(f"Prompt '{name}' for {lang_code} already exists!")
|
||||
if on_name_collision == "skip":
|
||||
log.debug(f"Skipping prompt '{name}' since it already exists.")
|
||||
return
|
||||
elif on_name_collision == "overwrite":
|
||||
allow_overwrite = True
|
||||
multilang_prompt_list.add_item(PromptList(prompt_list), lang_code=lang_code, allow_overwrite=allow_overwrite)
|
||||
|
||||
def _load_from_disc(self, prompts_dir: str) -> None:
|
||||
"""Loads all prompt templates and prompt lists from yaml files in the given directory."""
|
||||
def _load_from_disc(self, prompts_dir: str, on_name_collision: Literal["skip", "overwrite", "raise"] = "raise") -> None:
|
||||
"""Loads all prompt templates and prompt lists from yaml files in the given directory.
|
||||
|
||||
:param prompts_dir:
|
||||
:param on_name_collision: how to deal with name/lang_code collisions
|
||||
"""
|
||||
for fn in os.listdir(prompts_dir):
|
||||
if not fn.endswith((".yml", ".yaml")):
|
||||
log.debug(f"Skipping non-YAML file: {fn}")
|
||||
@@ -266,9 +324,11 @@ class MultiLangPromptCollection:
|
||||
# add the data to the collection
|
||||
for prompt_name, prompt_template_or_list in prompts_data.items():
|
||||
if isinstance(prompt_template_or_list, list):
|
||||
self._add_prompt_list(prompt_name, prompt_template_or_list, lang_code=lang_code)
|
||||
self._add_prompt_list(prompt_name, prompt_template_or_list, lang_code=lang_code, on_name_collision=on_name_collision)
|
||||
elif isinstance(prompt_template_or_list, str):
|
||||
self._add_prompt_template(prompt_name, prompt_template_or_list, lang_code=lang_code)
|
||||
self._add_prompt_template(
|
||||
prompt_name, prompt_template_or_list, lang_code=lang_code, on_name_collision=on_name_collision
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid prompt type for {prompt_name} in file {path} (should be str or list): {prompt_template_or_list}"
|
||||
@@ -306,6 +366,18 @@ class MultiLangPromptCollection:
|
||||
"""The PromptList object for the given prompt name and language code."""
|
||||
return self.get_multilang_prompt_list(prompt_name).get_item(lang_code)
|
||||
|
||||
def _has_prompt_list(self, prompt_name: str, lang_code: str = DEFAULT_LANG_CODE) -> bool:
|
||||
multi_lang_prompt_list = self._multi_lang_prompt_lists.get(prompt_name)
|
||||
if multi_lang_prompt_list is None:
|
||||
return False
|
||||
return multi_lang_prompt_list.has_item(lang_code)
|
||||
|
||||
def _has_prompt_template(self, prompt_name: str, lang_code: str = DEFAULT_LANG_CODE) -> bool:
|
||||
multi_lang_prompt_template = self._multi_lang_prompt_templates.get(prompt_name)
|
||||
if multi_lang_prompt_template is None:
|
||||
return False
|
||||
return multi_lang_prompt_template.has_item(lang_code)
|
||||
|
||||
def render_prompt_template(
|
||||
self,
|
||||
prompt_name: str,
|
||||
|
||||
@@ -10,9 +10,11 @@ log = logging.getLogger(__name__)
|
||||
class PromptFactoryBase:
|
||||
"""Base class for auto-generated prompt factory classes."""
|
||||
|
||||
def __init__(self, prompts_dir: str, lang_code: str = DEFAULT_LANG_CODE, fallback_mode=LanguageFallbackMode.EXCEPTION):
|
||||
def __init__(self, prompts_dir: str | list[str], lang_code: str = DEFAULT_LANG_CODE, fallback_mode=LanguageFallbackMode.EXCEPTION):
|
||||
"""
|
||||
:param prompts_dir: the directory containing the prompt templates and prompt lists
|
||||
:param prompts_dir: the directory containing the prompt templates and prompt lists.
|
||||
If a list is provided, will look for prompt templates in the dirs from left to right
|
||||
(first one containing the desired template wins).
|
||||
:param lang_code: the language code to use for retrieving the prompt templates and prompt lists.
|
||||
Leave as `default` for single-language use cases.
|
||||
:param fallback_mode: the fallback mode to use when a prompt template or prompt list is not found for the requested language.
|
||||
|
||||
+57
-6
@@ -18,6 +18,7 @@ from typing import TYPE_CHECKING, Any, Optional, TypeVar
|
||||
from sensai.util import logging
|
||||
from sensai.util.logging import LogTime
|
||||
|
||||
from interprompt.jinja_template import JinjaTemplate
|
||||
from serena import serena_version
|
||||
from serena.analytics import RegisteredTokenCountEstimator, ToolUsageStats
|
||||
from serena.config.context_mode import RegisteredContext, SerenaAgentContext, SerenaAgentMode
|
||||
@@ -25,7 +26,8 @@ from serena.config.serena_config import SerenaConfig, ToolInclusionDefinition, T
|
||||
from serena.dashboard import SerenaDashboardAPI
|
||||
from serena.project import Project
|
||||
from serena.prompt_factory import SerenaPromptFactory
|
||||
from serena.tools import ActivateProjectTool, Tool, ToolRegistry
|
||||
from serena.tools import ActivateProjectTool, Tool, ToolMarker, ToolRegistry
|
||||
from serena.util.inspection import iter_subclasses
|
||||
from serena.util.logging import MemoryLogHandler
|
||||
from solidlsp import SolidLanguageServer
|
||||
|
||||
@@ -91,6 +93,23 @@ class MemoriesManager:
|
||||
return f"Memory {name} deleted."
|
||||
|
||||
|
||||
class AvailableTools:
|
||||
def __init__(self, tools: list[Tool]):
|
||||
"""
|
||||
:param tools: the list of available tools
|
||||
"""
|
||||
self.tools = tools
|
||||
self.tool_names = [tool.get_name_from_cls() for tool in tools]
|
||||
self.tool_marker_names = set()
|
||||
for marker_class in iter_subclasses(ToolMarker):
|
||||
for tool in tools:
|
||||
if isinstance(tool, marker_class):
|
||||
self.tool_marker_names.add(marker_class.__name__)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.tools)
|
||||
|
||||
|
||||
class SerenaAgent:
|
||||
def __init__(
|
||||
self,
|
||||
@@ -181,6 +200,8 @@ class SerenaAgent:
|
||||
log.info("Available projects: {}".format(", ".join(self.serena_config.project_names)))
|
||||
log.info(f"Loaded tools ({len(self._all_tools)}): {', '.join([tool.get_name_from_cls() for tool in self._all_tools.values()])}")
|
||||
|
||||
self._check_shell_settings()
|
||||
|
||||
# determine the base toolset defining the set of exposed tools (which e.g. the MCP shall see),
|
||||
# limited by the Serena config, the context (which is fixed for the session) and JetBrains mode
|
||||
tool_inclusion_definitions: list[ToolInclusionDefinition] = [self.serena_config, self._context]
|
||||
@@ -190,7 +211,7 @@ class SerenaAgent:
|
||||
tool_inclusion_definitions.append(SerenaAgentMode.from_name_internal("jetbrains"))
|
||||
|
||||
self._base_tool_set = ToolSet.default().apply(*tool_inclusion_definitions)
|
||||
self._exposed_tools = {tc: t for tc, t in self._all_tools.items() if self._base_tool_set.includes_name(t.get_name())}
|
||||
self._exposed_tools = AvailableTools([t for t in self._all_tools.values() if self._base_tool_set.includes_name(t.get_name())])
|
||||
log.info(f"Number of exposed tools: {len(self._exposed_tools)}")
|
||||
|
||||
# create executor for starting the language server and running tools in another thread
|
||||
@@ -225,6 +246,22 @@ class SerenaAgent:
|
||||
except Exception as e:
|
||||
log.error(f"Error activating project '{project}' at startup: {e}", exc_info=e)
|
||||
|
||||
def get_context(self) -> SerenaAgentContext:
|
||||
return self._context
|
||||
|
||||
def get_tool_description_override(self, tool_name: str) -> str | None:
|
||||
return self._context.tool_description_overrides.get(tool_name, None)
|
||||
|
||||
def _check_shell_settings(self) -> None:
|
||||
# On Windows, Claude Code sets COMSPEC to Git-Bash (often even with a path containing spaces),
|
||||
# which causes all sorts of trouble, preventing language servers from being launched correctly.
|
||||
# So we make sure that COMSPEC is unset if it has been set to bash specifically.
|
||||
if platform.system() == "Windows":
|
||||
comspec = os.environ.get("COMSPEC", "")
|
||||
if "bash" in comspec:
|
||||
os.environ["COMSPEC"] = "" # force use of default shell
|
||||
log.info("Adjusting COMSPEC environment variable to use the default shell instead of '%s'", comspec)
|
||||
|
||||
def _ide_context_tool_inclusion_definitions(self, project_root_or_name: str | None) -> list[ToolInclusionDefinition]:
|
||||
"""
|
||||
In the IDE assistant context, the agent is assumed to work on a single project, and we thus
|
||||
@@ -289,7 +326,7 @@ class SerenaAgent:
|
||||
If a client should attempt to use a tool that is dynamically disabled
|
||||
(e.g. because a project is activated that disables it), it will receive an error.
|
||||
"""
|
||||
return list(self._exposed_tools.values())
|
||||
return list(self._exposed_tools.tools)
|
||||
|
||||
def get_active_project(self) -> Project | None:
|
||||
"""
|
||||
@@ -323,11 +360,21 @@ class SerenaAgent:
|
||||
"""
|
||||
return list(self._modes)
|
||||
|
||||
def _format_prompt(self, prompt_template: str) -> str:
|
||||
template = JinjaTemplate(prompt_template)
|
||||
return template.render(available_tools=self._exposed_tools.tool_names, available_markers=self._exposed_tools.tool_marker_names)
|
||||
|
||||
def create_system_prompt(self) -> str:
|
||||
return self.prompt_factory.create_system_prompt(
|
||||
context_system_prompt=self._context.prompt,
|
||||
mode_system_prompts=[mode.prompt for mode in self._modes],
|
||||
available_markers = self._exposed_tools.tool_marker_names
|
||||
log.info("Generating system prompt with available_tools=(see exposed tools), available_markers=%s", available_markers)
|
||||
system_prompt = self.prompt_factory.create_system_prompt(
|
||||
context_system_prompt=self._format_prompt(self._context.prompt),
|
||||
mode_system_prompts=[self._format_prompt(mode.prompt) for mode in self._modes],
|
||||
available_tools=self._exposed_tools.tool_names,
|
||||
available_markers=available_markers,
|
||||
)
|
||||
log.info("System prompt:\n%s", system_prompt)
|
||||
return system_prompt
|
||||
|
||||
def _update_active_tools(self) -> None:
|
||||
"""
|
||||
@@ -571,3 +618,7 @@ class SerenaAgent:
|
||||
if self._gui_log_viewer:
|
||||
log.info("Stopping the GUI log window ...")
|
||||
self._gui_log_viewer.stop()
|
||||
|
||||
def get_tool_by_name(self, tool_name: str) -> Tool:
|
||||
tool_class = ToolRegistry().get_tool_class_by_name(tool_name)
|
||||
return self.get_tool(tool_class)
|
||||
+114
-3
@@ -1,3 +1,4 @@
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
@@ -16,6 +17,8 @@ from serena.config.serena_config import ProjectConfig, SerenaConfig, SerenaPaths
|
||||
from serena.constants import (
|
||||
DEFAULT_CONTEXT,
|
||||
DEFAULT_MODES,
|
||||
PROMPT_TEMPLATES_DIR_IN_USER_HOME,
|
||||
PROMPT_TEMPLATES_DIR_INTERNAL,
|
||||
SERENA_LOG_FORMAT,
|
||||
SERENA_MANAGED_DIR_IN_HOME,
|
||||
SERENAS_OWN_CONTEXT_YAMLS_DIR,
|
||||
@@ -23,7 +26,7 @@ from serena.constants import (
|
||||
USER_CONTEXT_YAMLS_DIR,
|
||||
USER_MODE_YAMLS_DIR,
|
||||
)
|
||||
from serena.mcp import SerenaMCPFactorySingleProcess
|
||||
from serena.mcp import SerenaMCPFactory, SerenaMCPFactorySingleProcess
|
||||
from serena.project import Project
|
||||
from serena.tools import ToolRegistry
|
||||
from serena.util.logging import MemoryLogHandler
|
||||
@@ -435,7 +438,7 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
@click.argument("project", type=click.Path(exists=True), default=os.getcwd(), required=False)
|
||||
@click.option("--log-level", type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]), default="WARNING")
|
||||
def index_deprecated(project: str, log_level: str = "WARNING") -> None:
|
||||
click.echo("Deprecated! Use `project index` instead.")
|
||||
click.echo("Deprecated! Use `serena project index` instead.")
|
||||
ProjectCommands._index_project(project, log_level)
|
||||
|
||||
@staticmethod
|
||||
@@ -475,6 +478,113 @@ class ToolCommands(AutoRegisteringGroup):
|
||||
else:
|
||||
ToolRegistry().print_tool_overview()
|
||||
|
||||
@staticmethod
|
||||
@click.command(
|
||||
"description",
|
||||
help="Print the description of a tool, optionally with a specific context (the latter may modify the default description).",
|
||||
)
|
||||
@click.argument("tool_name", type=str)
|
||||
@click.option("--context", type=str, default=None, help="Context name or path to context file.")
|
||||
def description(tool_name: str, context: str | None = None) -> None:
|
||||
# Load the context
|
||||
serena_context = None
|
||||
if context:
|
||||
serena_context = SerenaAgentContext.load(context)
|
||||
|
||||
agent = SerenaAgent(
|
||||
project=None,
|
||||
serena_config=SerenaConfig(web_dashboard=False, log_level=logging.INFO),
|
||||
context=serena_context,
|
||||
)
|
||||
tool = agent.get_tool_by_name(tool_name)
|
||||
mcp_tool = SerenaMCPFactory.make_mcp_tool(tool)
|
||||
click.echo(mcp_tool.description)
|
||||
|
||||
|
||||
class PromptCommands(AutoRegisteringGroup):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(name="prompts", help="Commands related to Serena's prompts that are outside of contexts and modes.")
|
||||
|
||||
@staticmethod
|
||||
def _get_user_prompt_yaml_path(prompt_yaml_name: str) -> str:
|
||||
os.makedirs(PROMPT_TEMPLATES_DIR_IN_USER_HOME, exist_ok=True)
|
||||
return os.path.join(PROMPT_TEMPLATES_DIR_IN_USER_HOME, prompt_yaml_name)
|
||||
|
||||
@staticmethod
|
||||
@click.command("list", help="Lists yamls that are used for defining prompts.")
|
||||
def list() -> None:
|
||||
serena_prompt_yaml_names = [os.path.basename(f) for f in glob.glob(PROMPT_TEMPLATES_DIR_INTERNAL + "/*.yml")]
|
||||
for prompt_yaml_name in serena_prompt_yaml_names:
|
||||
user_prompt_yaml_path = PromptCommands._get_user_prompt_yaml_path(prompt_yaml_name)
|
||||
if os.path.exists(user_prompt_yaml_path):
|
||||
click.echo(f"{user_prompt_yaml_path} merged with default prompts in {prompt_yaml_name}")
|
||||
else:
|
||||
click.echo(prompt_yaml_name)
|
||||
|
||||
@staticmethod
|
||||
@click.command("create-override", help="Create an override of an internal prompts yaml for customizing Serena's prompts")
|
||||
@click.argument("prompt_yaml_name")
|
||||
def create_override(prompt_yaml_name: str) -> None:
|
||||
"""
|
||||
:param prompt_yaml_name: The yaml name of the prompt you want to override. Call the `list` command for discovering valid prompt yaml names.
|
||||
:return:
|
||||
"""
|
||||
# for convenience, we can pass names without .yml
|
||||
if not prompt_yaml_name.endswith(".yml"):
|
||||
prompt_yaml_name = prompt_yaml_name + ".yml"
|
||||
user_prompt_yaml_path = PromptCommands._get_user_prompt_yaml_path(prompt_yaml_name)
|
||||
if os.path.exists(user_prompt_yaml_path):
|
||||
raise FileExistsError(f"{user_prompt_yaml_path} already exists.")
|
||||
serena_prompt_yaml_path = os.path.join(PROMPT_TEMPLATES_DIR_INTERNAL, prompt_yaml_name)
|
||||
shutil.copyfile(serena_prompt_yaml_path, user_prompt_yaml_path)
|
||||
_open_in_editor(user_prompt_yaml_path)
|
||||
|
||||
@staticmethod
|
||||
@click.command("edit-override", help="Edit an existing prompt override file")
|
||||
@click.argument("prompt_yaml_name")
|
||||
def edit_override(prompt_yaml_name: str) -> None:
|
||||
"""
|
||||
:param prompt_yaml_name: The yaml name of the prompt override to edit.
|
||||
:return:
|
||||
"""
|
||||
# for convenience, we can pass names without .yml
|
||||
if not prompt_yaml_name.endswith(".yml"):
|
||||
prompt_yaml_name = prompt_yaml_name + ".yml"
|
||||
user_prompt_yaml_path = PromptCommands._get_user_prompt_yaml_path(prompt_yaml_name)
|
||||
if not os.path.exists(user_prompt_yaml_path):
|
||||
click.echo(f"Override file '{prompt_yaml_name}' not found. Create it with: prompts create-override {prompt_yaml_name}")
|
||||
return
|
||||
_open_in_editor(user_prompt_yaml_path)
|
||||
|
||||
@staticmethod
|
||||
@click.command("list-overrides", help="List existing prompt override files")
|
||||
def list_overrides() -> None:
|
||||
os.makedirs(PROMPT_TEMPLATES_DIR_IN_USER_HOME, exist_ok=True)
|
||||
serena_prompt_yaml_names = [os.path.basename(f) for f in glob.glob(PROMPT_TEMPLATES_DIR_INTERNAL + "/*.yml")]
|
||||
override_files = glob.glob(os.path.join(PROMPT_TEMPLATES_DIR_IN_USER_HOME, "*.yml"))
|
||||
for file_path in override_files:
|
||||
if os.path.basename(file_path) in serena_prompt_yaml_names:
|
||||
click.echo(file_path)
|
||||
|
||||
@staticmethod
|
||||
@click.command("delete-override", help="Delete a prompt override file")
|
||||
@click.argument("prompt_yaml_name")
|
||||
def delete_override(prompt_yaml_name: str) -> None:
|
||||
"""
|
||||
|
||||
:param prompt_yaml_name: The yaml name of the prompt override to delete."
|
||||
:return:
|
||||
"""
|
||||
# for convenience, we can pass names without .yml
|
||||
if not prompt_yaml_name.endswith(".yml"):
|
||||
prompt_yaml_name = prompt_yaml_name + ".yml"
|
||||
user_prompt_yaml_path = PromptCommands._get_user_prompt_yaml_path(prompt_yaml_name)
|
||||
if not os.path.exists(user_prompt_yaml_path):
|
||||
click.echo(f"Override file '{prompt_yaml_name}' not found.")
|
||||
return
|
||||
os.remove(user_prompt_yaml_path)
|
||||
click.echo(f"Deleted override file '{prompt_yaml_name}'.")
|
||||
|
||||
|
||||
# Expose groups so we can reference them in pyproject.toml
|
||||
mode = ModeCommands()
|
||||
@@ -482,6 +592,7 @@ context = ContextCommands()
|
||||
project = ProjectCommands()
|
||||
config = SerenaConfigCommands()
|
||||
tools = ToolCommands()
|
||||
prompts = PromptCommands()
|
||||
|
||||
# Expose toplevel commands for the same reason
|
||||
top_level = TopLevelCommands()
|
||||
@@ -489,7 +600,7 @@ start_mcp_server = top_level.start_mcp_server
|
||||
index_project = project.index_deprecated
|
||||
|
||||
# needed for the help script to work - register all subcommands to the top-level group
|
||||
for subgroup in (mode, context, project, config, tools):
|
||||
for subgroup in (mode, context, project, config, tools, prompts):
|
||||
top_level.add_command(subgroup)
|
||||
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@ Context and Mode configuration loader
|
||||
"""
|
||||
|
||||
import os
|
||||
from copy import copy
|
||||
from dataclasses import asdict, dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Self
|
||||
@@ -39,22 +38,15 @@ class SerenaAgentMode(ToolInclusionDefinition, ToStringMixin):
|
||||
|
||||
name: str
|
||||
prompt: str
|
||||
"""
|
||||
a Jinja2 template for the generation of the system prompt.
|
||||
It is formatted by the agent (see SerenaAgent._format_prompt()).
|
||||
"""
|
||||
description: str = ""
|
||||
|
||||
def _tostring_includes(self) -> list[str]:
|
||||
return ["name"]
|
||||
|
||||
def to_json_dict(self) -> dict[str, str | list[str]]:
|
||||
result = asdict(self)
|
||||
result["excluded_tools"] = list(result["excluded_tools"])
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def from_json_dict(cls, data: dict) -> Self:
|
||||
data = copy(data)
|
||||
data["excluded_tools"] = set(data["excluded_tools"])
|
||||
return cls(**data)
|
||||
|
||||
def print_overview(self) -> None:
|
||||
"""Print an overview of the mode."""
|
||||
print(f"{self.name}:\n {self.description}")
|
||||
@@ -133,28 +125,26 @@ class SerenaAgentContext(ToolInclusionDefinition, ToStringMixin):
|
||||
|
||||
name: str
|
||||
prompt: str
|
||||
"""
|
||||
a Jinja2 template for the generation of the system prompt.
|
||||
It is formatted by the agent (see SerenaAgent._format_prompt()).
|
||||
"""
|
||||
description: str = ""
|
||||
tool_description_overrides: dict[str, str] = field(default_factory=dict)
|
||||
"""Maps tool names to custom descriptions, default descriptions are extracted from the tool docstrings."""
|
||||
|
||||
def _tostring_includes(self) -> list[str]:
|
||||
return ["name"]
|
||||
|
||||
def to_json_dict(self) -> dict[str, str | list[str]]:
|
||||
result = asdict(self)
|
||||
result["excluded_tools"] = list(result["excluded_tools"])
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def from_json_dict(cls, data: dict) -> Self:
|
||||
data = copy(data)
|
||||
data["excluded_tools"] = set(data["excluded_tools"])
|
||||
return cls(**data)
|
||||
|
||||
@classmethod
|
||||
def from_yaml(cls, yaml_path: str | Path) -> Self:
|
||||
"""Load a context from a YAML file."""
|
||||
with open(yaml_path, encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f)
|
||||
name = data.pop("name", Path(yaml_path).stem)
|
||||
# Ensure backwards compatibility for tool_description_overrides
|
||||
if "tool_description_overrides" not in data:
|
||||
data["tool_description_overrides"] = {}
|
||||
return cls(name=name, **data)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -47,13 +47,13 @@ class SerenaPaths:
|
||||
Provides paths to various Serena-related directories and files.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self.user_config_dir: str = SERENA_MANAGED_DIR_IN_HOME
|
||||
"""
|
||||
the path to the user's Serena configuration directory, which is typically ~/.serena
|
||||
"""
|
||||
|
||||
def get_next_log_file_path(self, prefix: str):
|
||||
def get_next_log_file_path(self, prefix: str) -> str:
|
||||
"""
|
||||
:param prefix: the filename prefix indicating the type of the log file
|
||||
:return: the full path to the log file to use
|
||||
|
||||
@@ -10,7 +10,8 @@ SERENA_MANAGED_DIR_IN_HOME = str(_serena_in_home_managed_dir)
|
||||
|
||||
# TODO: Path-related constants should be moved to SerenaPaths; don't add further constants here.
|
||||
REPO_ROOT = str(_repo_root_path)
|
||||
PROMPT_TEMPLATES_DIR = str(_serena_pkg_path / "resources" / "config" / "prompt_templates")
|
||||
PROMPT_TEMPLATES_DIR_INTERNAL = str(_serena_pkg_path / "resources" / "config" / "prompt_templates")
|
||||
PROMPT_TEMPLATES_DIR_IN_USER_HOME = str(_serena_in_home_managed_dir / "prompt_templates")
|
||||
SERENAS_OWN_CONTEXT_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "contexts")
|
||||
"""The contexts that are shipped with the Serena package, i.e. the default contexts."""
|
||||
USER_CONTEXT_YAMLS_DIR = str(_serena_in_home_managed_dir / "contexts")
|
||||
|
||||
@@ -32,5 +32,7 @@ class PromptFactory(PromptFactoryBase):
|
||||
def create_prepare_for_new_conversation(self) -> str:
|
||||
return self._render_prompt("prepare_for_new_conversation", locals())
|
||||
|
||||
def create_system_prompt(self, *, context_system_prompt: Any, mode_system_prompts: Any) -> str:
|
||||
def create_system_prompt(
|
||||
self, *, available_markers: Any, available_tools: Any, context_system_prompt: Any, mode_system_prompts: Any
|
||||
) -> str:
|
||||
return self._render_prompt("system_prompt", locals())
|
||||
+21
-5
@@ -68,10 +68,17 @@ class SerenaMCPFactory:
|
||||
|
||||
# Mount the tool description as a combination of the docstring description and
|
||||
# the return value description, if it exists.
|
||||
if docstring.description:
|
||||
func_doc = f"{docstring.description.strip().strip('.')}."
|
||||
overridden_description = tool.agent.get_context().tool_description_overrides.get(func_name, None)
|
||||
|
||||
if overridden_description is not None:
|
||||
func_doc = overridden_description
|
||||
elif docstring.description:
|
||||
func_doc = docstring.description
|
||||
else:
|
||||
func_doc = ""
|
||||
func_doc = func_doc.strip().strip(".")
|
||||
if func_doc:
|
||||
func_doc += "."
|
||||
if docstring.returns and (docstring_returns_descr := docstring.returns.description):
|
||||
# Only add a space before "Returns" if func_doc is not empty
|
||||
prefix = " " if func_doc else ""
|
||||
@@ -113,6 +120,7 @@ class SerenaMCPFactory:
|
||||
for tool in self._iter_tools():
|
||||
mcp_tool = self.make_mcp_tool(tool)
|
||||
mcp._tool_manager._tools[tool.get_name()] = mcp_tool
|
||||
log.info(f"Starting MCP server with {len(mcp._tool_manager._tools)} tools: {list(mcp._tool_manager._tools.keys())}")
|
||||
|
||||
@abstractmethod
|
||||
def _instantiate_agent(self, serena_config: SerenaConfig, modes: list[SerenaAgentMode]) -> None:
|
||||
@@ -170,9 +178,8 @@ class SerenaMCPFactory:
|
||||
# `.env` files (e.g. containing LOG_LEVEL) that are not supposed to override the MCP settings;
|
||||
# retain only FASTMCP_ prefix for already set environment variables.
|
||||
Settings.model_config = SettingsConfigDict(env_prefix="FASTMCP_")
|
||||
|
||||
mcp_settings: Settings = Settings(lifespan=self.server_lifespan, host=host, port=port)
|
||||
mcp = FastMCP(**mcp_settings.model_dump())
|
||||
instructions = self._get_initial_instructions()
|
||||
mcp = FastMCP(lifespan=self.server_lifespan, host=host, port=port, instructions=instructions)
|
||||
return mcp
|
||||
|
||||
@asynccontextmanager
|
||||
@@ -181,6 +188,10 @@ class SerenaMCPFactory:
|
||||
"""Manage server startup and shutdown lifecycle."""
|
||||
yield None # ensures MyPy understands we yield None
|
||||
|
||||
@abstractmethod
|
||||
def _get_initial_instructions(self) -> str:
|
||||
pass
|
||||
|
||||
|
||||
class SerenaMCPFactorySingleProcess(SerenaMCPFactory):
|
||||
"""
|
||||
@@ -207,6 +218,11 @@ class SerenaMCPFactorySingleProcess(SerenaMCPFactory):
|
||||
assert self.agent is not None
|
||||
yield from self.agent.get_exposed_tool_instances()
|
||||
|
||||
def _get_initial_instructions(self) -> str:
|
||||
assert self.agent is not None
|
||||
# we don't use the tool (which at the time of writing calls this method), since the tool may be disabled by the config
|
||||
return self.agent.create_system_prompt()
|
||||
|
||||
@asynccontextmanager
|
||||
async def server_lifespan(self, mcp_server: FastMCP) -> AsyncIterator[None]:
|
||||
self._set_mcp_tools(mcp_server)
|
||||
|
||||
@@ -5,11 +5,13 @@ from pathlib import Path
|
||||
import pathspec
|
||||
|
||||
from serena.config.serena_config import DEFAULT_TOOL_TIMEOUT, ProjectConfig
|
||||
from serena.constants import SERENA_MANAGED_DIR_IN_HOME
|
||||
from serena.text_utils import MatchedConsecutiveLines, search_files
|
||||
from serena.util.file_system import GitignoreParser, match_path
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -271,4 +273,5 @@ class Project:
|
||||
ls_logger,
|
||||
self.project_root,
|
||||
timeout=ls_timeout,
|
||||
solidlsp_settings=SolidLSPSettings(solidlsp_dir=SERENA_MANAGED_DIR_IN_HOME),
|
||||
)
|
||||
@@ -1,4 +1,6 @@
|
||||
from serena.constants import PROMPT_TEMPLATES_DIR
|
||||
import os
|
||||
|
||||
from serena.constants import PROMPT_TEMPLATES_DIR_IN_USER_HOME, PROMPT_TEMPLATES_DIR_INTERNAL
|
||||
from serena.generated.generated_prompt_factory import PromptFactory
|
||||
|
||||
|
||||
@@ -8,4 +10,5 @@ class SerenaPromptFactory(PromptFactory):
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(PROMPT_TEMPLATES_DIR)
|
||||
os.makedirs(PROMPT_TEMPLATES_DIR_IN_USER_HOME, exist_ok=True)
|
||||
super().__init__(prompts_dir=[PROMPT_TEMPLATES_DIR_IN_USER_HOME, PROMPT_TEMPLATES_DIR_INTERNAL])
|
||||
@@ -3,4 +3,6 @@ prompt: |
|
||||
You are running in agent context where the system prompt is provided externally. You should use symbolic
|
||||
tools when possible for code understanding and modification.
|
||||
excluded_tools:
|
||||
- initial_instructions
|
||||
- initial_instructions
|
||||
|
||||
tool_description_overrides: {}
|
||||
@@ -0,0 +1,28 @@
|
||||
description: A configuration specific for chatgpt, which has a limit of 30 tools and requires short descriptions.
|
||||
prompt: |
|
||||
You are running in desktop app context where the tools give you access to the code base as well as some
|
||||
access to the file system, if configured. You interact with the user through a chat interface that is separated
|
||||
from the code base. As a consequence, if you are in interactive mode, your communication with the user should
|
||||
involve high-level thinking and planning as well as some summarization of any code edits that you make.
|
||||
For viewing the code edits the user will view them in a separate code editor window, and the back-and-forth
|
||||
between the chat and the code editor should be minimized as well as facilitated by you.
|
||||
If complex changes have been made, advise the user on how to review them in the code editor.
|
||||
If complex relationships that the user asked for should be visualized or explained, consider creating
|
||||
a diagram in addition to your text-based communication. Note that in the chat interface you have various rendering
|
||||
options for text, html, and mermaid diagrams, as has been explained to you in your initial instructions.
|
||||
excluded_tools: []
|
||||
included_optional_tools:
|
||||
- switch_modes
|
||||
|
||||
tool_description_overrides:
|
||||
find_symbol: |
|
||||
Retrieves symbols matching `name_path` in a file.
|
||||
Use `depth > 0` to include children. `name_path` can be: "foo": any symbol named "foo"; "foo/bar": "bar" within "foo"; "/foo/bar": only top-level "foo/bar"
|
||||
replace_regex: |
|
||||
Replaces text using regular expressions. Preferred for smaller edits where symbol-level tools aren't appropriate.
|
||||
Use wildcards (.*?) to match large sections efficiently: "beginning.*?end" instead of specifying exact content.
|
||||
Essential for multi-line replacements.
|
||||
search_for_pattern: |
|
||||
Flexible pattern search across codebase. Prefer symbolic operations when possible.
|
||||
Uses DOTALL matching. Use non-greedy quantifiers (.*?) to avoid over-matching.
|
||||
Supports file filtering via globs and code-only restriction.
|
||||
@@ -1,4 +1,11 @@
|
||||
# See Serena's documentation for more details on concept of contexts.
|
||||
description: Description of the context, not used in the code.
|
||||
prompt: Prompt that will form part of the system prompt/initial instructions for agents started in this context.
|
||||
excluded_tools: []
|
||||
excluded_tools: []
|
||||
|
||||
# several tools are excluded by default and have to be explicitly included by the user
|
||||
included_optional_tools: []
|
||||
|
||||
# mapping of tool names to an override of their descriptions (the default description is the docstring of the Tool's apply method).
|
||||
# Sometimes, tool descriptions are too long (e.g., for ChatGPT), or users may want to override them for another reason.
|
||||
tool_description_overrides: {}
|
||||
@@ -10,4 +10,8 @@ prompt: |
|
||||
If complex relationships that the user asked for should be visualized or explained, consider creating
|
||||
a diagram in addition to your text-based communication. Note that in the chat interface you have various rendering
|
||||
options for text, html, and mermaid diagrams, as has been explained to you in your initial instructions.
|
||||
excluded_tools: []
|
||||
excluded_tools: []
|
||||
included_optional_tools:
|
||||
- switch_modes
|
||||
|
||||
tool_description_overrides: {}
|
||||
@@ -19,7 +19,8 @@ prompt: |
|
||||
excluded_tools:
|
||||
- create_text_file
|
||||
- read_file
|
||||
- delete_lines
|
||||
- replace_lines
|
||||
- insert_at_line
|
||||
- execute_shell_command
|
||||
- execute_shell_command
|
||||
- prepare_for_new_conversation
|
||||
|
||||
tool_description_overrides: {}
|
||||
|
||||
@@ -14,7 +14,7 @@ prompt: |
|
||||
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
|
||||
Symbols are identified by their name path and relative file path, see the description of the `find_symbol` 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
|
||||
@@ -34,7 +34,8 @@ prompt: |
|
||||
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.
|
||||
{% if 'replace_regex' in available_tools %}
|
||||
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
|
||||
@@ -104,7 +105,8 @@ prompt: |
|
||||
Generally, I remind you that you rely on the regex tool with providing you the correct feedback, no need for more verification!
|
||||
|
||||
IMPORTANT: REMEMBER TO USE WILDCARDS WHEN APPROPRIATE! I WILL BE VERY UNHAPPY IF YOU WRITE LONG REGEXES WITHOUT USING WILDCARDS INSTEAD!
|
||||
excluded_tools:
|
||||
{% endif %}
|
||||
excluded_tools:
|
||||
- replace_lines
|
||||
- insert_at_line
|
||||
- delete_lines
|
||||
@@ -2,3 +2,6 @@
|
||||
description: Description of the mode, not used in the code.
|
||||
prompt: Prompt that will form part of the message sent to the model when activating this mode.
|
||||
excluded_tools: []
|
||||
|
||||
# several tools are excluded by default and have to be explicitly included by the user
|
||||
included_optional_tools: []
|
||||
@@ -5,32 +5,38 @@ prompts:
|
||||
system_prompt: |
|
||||
You are a professional coding agent concerned with one particular codebase. You have
|
||||
access to semantic coding tools on which you rely heavily for all your work, as well as collection of memory
|
||||
files containing general information about the codebase. You operate in a frugal and intelligent manner, always
|
||||
files containing general information about the codebase. You operate in a resource-efficient and intelligent manner, always
|
||||
keeping in mind to not read or generate content that is not needed for the task at hand.
|
||||
|
||||
When reading code in order to answer a user question or task, you should try reading only the necessary code.
|
||||
Some tasks may require you to understand the architecture of large parts of the codebase, while for others,
|
||||
it may be enough to read a small set of symbols or a single file.
|
||||
Generally, you should avoid reading entire files unless it is absolutely necessary, instead relying on
|
||||
intelligent step-by-step acquisition of information. However, if you already read a file, it does not make
|
||||
intelligent step-by-step acquisition of information. {% if 'ToolMarkerSymbolicRead' in available_markers %}However, if you already read a file, it does not make
|
||||
sense to further analyse it with the symbolic tools (except for the `find_referencing_symbols` tool),
|
||||
as you already have the information.
|
||||
as you already have the information.{% endif %}
|
||||
|
||||
I WILL BE SERIOUSLY UPSET IF YOU READ ENTIRE FILES WITHOUT NEED! CONSIDER INSTEAD USING THE OVERVIEW TOOL AND
|
||||
SYMBOLIC TOOLS TO READ ONLY THE NECESSARY CODE FIRST!
|
||||
I WILL BE SERIOUSLY UPSET IF YOU READ ENTIRE FILES WITHOUT NEED!
|
||||
{% if 'ToolMarkerSymbolicRead' in available_markers %}
|
||||
CONSIDER INSTEAD USING THE OVERVIEW TOOL AND SYMBOLIC TOOLS TO READ ONLY THE NECESSARY CODE FIRST!
|
||||
I WILL BE EVEN MORE UPSET IF AFTER HAVING READ AN ENTIRE FILE YOU KEEP READING THE SAME CONTENT WITH THE SYMBOLIC TOOLS!
|
||||
THE PURPOSE OF THE SYMBOLIC TOOLS IS TO HAVE TO READ LESS CODE, NOT READ THE SAME CONTENT MULTIPLE TIMES!
|
||||
{% endif %}
|
||||
|
||||
You can achieve the intelligent reading of code by using the symbolic tools for getting an overview of symbols and
|
||||
the relations between them, and then only reading the bodies of symbols that are necessary to answer the question
|
||||
or complete the task. You can also use the standard tools like list_dir, find_file and search_for_pattern if you need to.
|
||||
or complete the task.
|
||||
You can use the standard tools like list_dir, find_file and search_for_pattern if you need to.
|
||||
When tools allow it, you pass the `relative_path` parameter to restrict the search to a specific file or directory.
|
||||
For some tools, `relative_path` can only be a file path, so make sure to properly read the tool descriptions.
|
||||
If you are unsure about a symbol's name or location (to the extent that substring_matching for the symbol name is not enough), you can use the `search_for_pattern` tool, which allows fast
|
||||
and flexible search for patterns in the codebase. This way you can first find candidates for symbols or files,
|
||||
and then proceed with the symbolic tools.
|
||||
{% if 'search_for_pattern' in available_tools %}
|
||||
If you are unsure about a symbol's name or location{% if 'find_symbol' in available_tools %} (to the extent that substring_matching for the symbol name is not enough){% endif %}, you can use the `search_for_pattern` tool, which allows fast
|
||||
and flexible search for patterns in the codebase.{% if 'ToolMarkerSymbolicRead' in available_markers %}This way you can first find candidates for symbols or files,
|
||||
and then proceed with the symbolic tools.{% endif %}
|
||||
{% endif %}
|
||||
|
||||
Symbols are identified by their `name_path and `relative_path`, see the description of the `find_symbols` tool for more details
|
||||
{% if 'ToolMarkerSymbolicRead' in available_markers %}
|
||||
Symbols are identified by their `name_path and `relative_path`, see the description of the `find_symbol` 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
|
||||
@@ -40,10 +46,13 @@ prompts:
|
||||
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`
|
||||
You can understand relationships between symbols by using the `find_referencing_symbols` tool.
|
||||
{% endif %}
|
||||
|
||||
{% if 'read_memory' in available_tools %}
|
||||
You generally have access to memories and it may be useful for you to read them, but also only if they help you
|
||||
to answer the question or complete the task. You can infer which memories are relevant to the current task by reading
|
||||
the memory names and descriptions.
|
||||
{% endif %}
|
||||
|
||||
The context and modes of operation are described below. From them you can infer how to interact with your user
|
||||
and which tasks and kinds of interactions are expected of you.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class LogMessage {
|
||||
constructor(message, toolNames) {
|
||||
message = this.escapeHtml(message);
|
||||
const logLevel = this.determineLogLevel(message);
|
||||
const highlightedMessage = this.highlightToolNames(message, toolNames);
|
||||
this.$elem = $('<div>').addClass('log-' + logLevel).html(highlightedMessage + '\n');
|
||||
@@ -27,6 +28,21 @@ class LogMessage {
|
||||
});
|
||||
return highlightedMessage;
|
||||
}
|
||||
|
||||
escapeHtml (convertString) {
|
||||
if (typeof convertString !== 'string') return convertString;
|
||||
|
||||
const patterns = {
|
||||
'<' : '<',
|
||||
'>' : '>',
|
||||
'&' : '&',
|
||||
'"' : '"',
|
||||
'\'' : ''',
|
||||
'`' : '`'
|
||||
};
|
||||
|
||||
return convertString.replace(/[<>&"'`]/g, match => patterns[match]);
|
||||
};
|
||||
}
|
||||
|
||||
class Dashboard {
|
||||
|
||||
@@ -19,15 +19,8 @@ class ExecuteShellCommandTool(Tool, ToolMarkerCanEdit):
|
||||
max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH,
|
||||
) -> str:
|
||||
"""
|
||||
Execute a shell command and return its output.
|
||||
|
||||
IMPORTANT: you should always consider the memory about suggested shell commands before using this tool.
|
||||
If this memory was not loaded in the current conversation, you should load it using the `read_memory` tool
|
||||
before using this tool.
|
||||
|
||||
You should have at least once looked at the suggested shell commands from the corresponding memory
|
||||
created during the onboarding process before using this tool.
|
||||
Never execute unsafe shell commands like `rm -rf /` or similar! Generally be very careful with deletions.
|
||||
Execute a shell command and return its output. If there is a memory about suggested commands, read that first.
|
||||
Never execute unsafe shell commands like `rm -rf /` or similar!
|
||||
|
||||
:param command: the shell command to execute
|
||||
:param cwd: the working directory to execute the command in. If None, the project root will be used.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import json
|
||||
|
||||
from serena.config.context_mode import SerenaAgentMode
|
||||
from serena.tools import Tool, ToolMarkerDoesNotRequireActiveProject
|
||||
from serena.tools import Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional
|
||||
|
||||
|
||||
class ActivateProjectTool(Tool, ToolMarkerDoesNotRequireActiveProject):
|
||||
@@ -35,7 +35,7 @@ class ActivateProjectTool(Tool, ToolMarkerDoesNotRequireActiveProject):
|
||||
return result_str
|
||||
|
||||
|
||||
class RemoveProjectTool(Tool, ToolMarkerDoesNotRequireActiveProject):
|
||||
class RemoveProjectTool(Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional):
|
||||
"""
|
||||
Removes a project from the Serena configuration.
|
||||
"""
|
||||
@@ -50,7 +50,7 @@ class RemoveProjectTool(Tool, ToolMarkerDoesNotRequireActiveProject):
|
||||
return f"Successfully removed project '{project_name}' from configuration."
|
||||
|
||||
|
||||
class SwitchModesTool(Tool):
|
||||
class SwitchModesTool(Tool, ToolMarkerOptional):
|
||||
"""
|
||||
Activates modes by providing a list of their names
|
||||
"""
|
||||
@@ -71,7 +71,7 @@ class SwitchModesTool(Tool):
|
||||
return result_str
|
||||
|
||||
|
||||
class GetCurrentConfigTool(Tool):
|
||||
class GetCurrentConfigTool(Tool, ToolMarkerOptional):
|
||||
"""
|
||||
Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
||||
"""
|
||||
|
||||
@@ -14,7 +14,7 @@ from fnmatch import fnmatch
|
||||
from pathlib import Path
|
||||
|
||||
from serena.text_utils import search_files
|
||||
from serena.tools import SUCCESS_RESULT, TOOL_DEFAULT_MAX_ANSWER_LENGTH, EditedFileContext, Tool, ToolMarkerCanEdit
|
||||
from serena.tools import SUCCESS_RESULT, TOOL_DEFAULT_MAX_ANSWER_LENGTH, EditedFileContext, Tool, ToolMarkerCanEdit, ToolMarkerOptional
|
||||
from serena.util.file_system import scan_directory
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ class ReadFileTool(Tool):
|
||||
"""
|
||||
Reads the given file or a chunk of it. Generally, symbolic operations
|
||||
like find_symbol or find_referencing_symbols should be preferred if you know which symbols you are looking for.
|
||||
Reading the entire file is only recommended if there is no other way to get the content required for the task.
|
||||
|
||||
:param relative_path: the relative path to the file to read
|
||||
:param start_line: the 0-based index of the first line to be retrieved.
|
||||
@@ -60,14 +59,7 @@ class CreateTextFileTool(Tool, ToolMarkerCanEdit):
|
||||
|
||||
def apply(self, relative_path: str, content: str) -> str:
|
||||
"""
|
||||
Write a new file (or overwrite an existing file). For existing files, it is strongly recommended
|
||||
to use symbolic operations like replace_symbol_body or insert_after_symbol/insert_before_symbol, if possible.
|
||||
You can also use insert_at_line to insert content at a specific line for existing files if the symbolic operations
|
||||
are not the right choice for what you want to do.
|
||||
|
||||
If ever used on an existing file, the content has to be the complete content of that file (so it
|
||||
may never end with something like "The remaining content of the file is left unchanged.").
|
||||
For operations that just replace a part of a file, use the replace_lines or the symbolic editing tools instead.
|
||||
Write a new file or overwrite an existing file.
|
||||
|
||||
:param relative_path: the relative path to the file to create
|
||||
:param content: the (utf-8-encoded) content to write to the file
|
||||
@@ -200,7 +192,7 @@ class ReplaceRegexTool(Tool, ToolMarkerCanEdit):
|
||||
return SUCCESS_RESULT
|
||||
|
||||
|
||||
class DeleteLinesTool(Tool, ToolMarkerCanEdit):
|
||||
class DeleteLinesTool(Tool, ToolMarkerCanEdit, ToolMarkerOptional):
|
||||
"""
|
||||
Deletes a range of lines within a file.
|
||||
"""
|
||||
@@ -228,7 +220,7 @@ class DeleteLinesTool(Tool, ToolMarkerCanEdit):
|
||||
return SUCCESS_RESULT
|
||||
|
||||
|
||||
class ReplaceLinesTool(Tool, ToolMarkerCanEdit):
|
||||
class ReplaceLinesTool(Tool, ToolMarkerCanEdit, ToolMarkerOptional):
|
||||
"""
|
||||
Replaces a range of lines within a file with new content.
|
||||
"""
|
||||
@@ -259,7 +251,7 @@ class ReplaceLinesTool(Tool, ToolMarkerCanEdit):
|
||||
return SUCCESS_RESULT
|
||||
|
||||
|
||||
class InsertAtLineTool(Tool, ToolMarkerCanEdit):
|
||||
class InsertAtLineTool(Tool, ToolMarkerCanEdit, ToolMarkerOptional):
|
||||
"""
|
||||
Inserts content at a given line in a file.
|
||||
"""
|
||||
@@ -351,7 +343,7 @@ class SearchForPatternTool(Tool):
|
||||
For example, for finding classes or methods from a name pattern.
|
||||
Setting to False is a better choice if you also want to search in non-code files, like in html or yaml files,
|
||||
which is why it is the default.
|
||||
:return: A JSON object mapping file paths to lists of matched consecutive lines (with context, if requested).
|
||||
:return: A mapping of file paths to lists of matched consecutive lines.
|
||||
"""
|
||||
abs_path = os.path.join(self.get_project_root(), relative_path)
|
||||
if not os.path.exists(abs_path):
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import json
|
||||
|
||||
from serena.tools import TOOL_DEFAULT_MAX_ANSWER_LENGTH, Tool, ToolMarkerOptional
|
||||
from serena.tools import TOOL_DEFAULT_MAX_ANSWER_LENGTH, Tool, ToolMarkerOptional, ToolMarkerSymbolicRead
|
||||
from serena.tools.jetbrains_plugin_client import JetBrainsPluginClient
|
||||
|
||||
|
||||
class JetBrainsFindSymbolTool(Tool, ToolMarkerOptional):
|
||||
class JetBrainsFindSymbolTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional):
|
||||
"""
|
||||
Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
|
||||
"""
|
||||
@@ -70,7 +70,7 @@ class JetBrainsFindSymbolTool(Tool, ToolMarkerOptional):
|
||||
return self._limit_length(result, max_answer_chars)
|
||||
|
||||
|
||||
class JetBrainsFindReferencingSymbolsTool(Tool, ToolMarkerOptional):
|
||||
class JetBrainsFindReferencingSymbolsTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional):
|
||||
"""
|
||||
Finds symbols that reference the given symbol
|
||||
"""
|
||||
@@ -100,7 +100,7 @@ class JetBrainsFindReferencingSymbolsTool(Tool, ToolMarkerOptional):
|
||||
return self._limit_length(result, max_answer_chars)
|
||||
|
||||
|
||||
class JetBrainsGetSymbolsOverviewTool(Tool, ToolMarkerOptional):
|
||||
class JetBrainsGetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional):
|
||||
"""
|
||||
Retrieves an overview of the top-level symbols within a specified file
|
||||
"""
|
||||
|
||||
@@ -10,15 +10,8 @@ class WriteMemoryTool(Tool):
|
||||
|
||||
def apply(self, memory_name: str, content: str, max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH) -> str:
|
||||
"""
|
||||
Write some information about this project that can be useful for future tasks to a memory.
|
||||
Use markdown formatting for the content.
|
||||
The information should be short and to the point.
|
||||
The memory name should be meaningful, such that from the name you can infer what the information is about.
|
||||
It is better to have multiple small memories than to have a single large one because
|
||||
memories will be read one by one and we only ever want to read relevant memories.
|
||||
|
||||
This tool is either called during the onboarding process or when you have identified
|
||||
something worth remembering about the project from the past conversation.
|
||||
Write some information about this project that can be useful for future tasks to a memory in md format.
|
||||
The memory name should be meaningful.
|
||||
"""
|
||||
if len(content) > max_answer_chars:
|
||||
raise ValueError(
|
||||
|
||||
@@ -4,11 +4,18 @@ Language server-related tools
|
||||
|
||||
import dataclasses
|
||||
import json
|
||||
import os
|
||||
from collections.abc import Sequence
|
||||
from copy import copy
|
||||
from typing import Any
|
||||
|
||||
from serena.tools import SUCCESS_RESULT, TOOL_DEFAULT_MAX_ANSWER_LENGTH, Tool, ToolMarkerCanEdit
|
||||
from serena.tools import (
|
||||
SUCCESS_RESULT,
|
||||
TOOL_DEFAULT_MAX_ANSWER_LENGTH,
|
||||
Tool,
|
||||
ToolMarkerSymbolicEdit,
|
||||
ToolMarkerSymbolicRead,
|
||||
)
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
|
||||
|
||||
@@ -33,44 +40,45 @@ class RestartLanguageServerTool(Tool):
|
||||
|
||||
def apply(self) -> str:
|
||||
"""Use this tool only on explicit user request or after confirmation.
|
||||
It may be necessary to restart the language server if the user performs edits
|
||||
not through Serena, so the language server state becomes outdated and further editing attempts lead to errors.
|
||||
|
||||
If such editing errors happen, you should suggest using this tool.
|
||||
It may be necessary to restart the language server if it hangs.
|
||||
"""
|
||||
self.agent.reset_language_server()
|
||||
return SUCCESS_RESULT
|
||||
|
||||
|
||||
class GetSymbolsOverviewTool(Tool):
|
||||
class GetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead):
|
||||
"""
|
||||
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 = TOOL_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_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.
|
||||
Before requesting a symbol overview, it is usually a good idea to narrow down the scope of the overview
|
||||
by first understanding the basic directory structure of the repository that you can get from memories
|
||||
or by using the `list_dir` and `find_file` tools (or similar).
|
||||
Use this tool to get a high-level understanding of the code symbols in a file.
|
||||
This should be the first tool to call when you want to understand a new file, unless you already know
|
||||
what you are looking for.
|
||||
|
||||
: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_path, kind).
|
||||
:return: a JSON object containing info about top-level symbols in the file
|
||||
"""
|
||||
symbol_retriever = self.create_language_server_symbol_retriever()
|
||||
result = symbol_retriever.get_symbol_overview(relative_path)
|
||||
result_json_str = json.dumps({k: [dataclasses.asdict(i) for i in l] for k, l in result.items()})
|
||||
file_path = os.path.join(self.project.project_root, relative_path)
|
||||
|
||||
# The symbol overview is capable of working with both files and directories,
|
||||
# but we want to ensure that the user provides a file path.
|
||||
if not os.path.exists(file_path):
|
||||
raise FileNotFoundError(f"File or directory {relative_path} does not exist in the project.")
|
||||
if os.path.isdir(file_path):
|
||||
raise ValueError(f"Expected a file path, but got a directory path: {relative_path}. ")
|
||||
result = symbol_retriever.get_symbol_overview(relative_path)[relative_path]
|
||||
result_json_str = json.dumps([dataclasses.asdict(i) for i in result])
|
||||
return self._limit_length(result_json_str, max_answer_chars)
|
||||
|
||||
|
||||
class FindSymbolTool(Tool):
|
||||
class FindSymbolTool(Tool, ToolMarkerSymbolicRead):
|
||||
"""
|
||||
Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
|
||||
"""
|
||||
@@ -132,7 +140,7 @@ class FindSymbolTool(Tool):
|
||||
:param exclude_kinds: Optional. List of LSP symbol kind integers to exclude. Takes precedence over `include_kinds`.
|
||||
:param substring_matching: If True, use substring matching for the last segment of `name`.
|
||||
:param max_answer_chars: Max characters for the JSON result. If exceeded, no content is returned.
|
||||
:return: JSON string: a list of symbols (with locations) matching the name.
|
||||
:return: a list of symbols (with locations) matching the name.
|
||||
"""
|
||||
parsed_include_kinds: Sequence[SymbolKind] | None = [SymbolKind(k) for k in include_kinds] if include_kinds else None
|
||||
parsed_exclude_kinds: Sequence[SymbolKind] | None = [SymbolKind(k) for k in exclude_kinds] if exclude_kinds else None
|
||||
@@ -150,7 +158,7 @@ class FindSymbolTool(Tool):
|
||||
return self._limit_length(result, max_answer_chars)
|
||||
|
||||
|
||||
class FindReferencingSymbolsTool(Tool):
|
||||
class FindReferencingSymbolsTool(Tool, ToolMarkerSymbolicRead):
|
||||
"""
|
||||
Finds symbols that reference the symbol at the given location (optionally filtered by type).
|
||||
"""
|
||||
@@ -164,10 +172,8 @@ class FindReferencingSymbolsTool(Tool):
|
||||
max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH,
|
||||
) -> str:
|
||||
"""
|
||||
Finds symbols that reference the symbol at the given `name_path`. The result will contain metadata about the referencing symbols
|
||||
as well as a short code snippet around the reference (unless `include_body` is True, then the short snippet will be omitted).
|
||||
Note that among other kinds of references, this function can be used to find (direct) subclasses of a class,
|
||||
as subclasses are referencing symbols that have the kind class.
|
||||
Finds references to the symbol at the given `name_path`. The result will contain metadata about the referencing symbols
|
||||
as well as a short code snippet around the reference.
|
||||
|
||||
:param name_path: for finding the symbol to find references for, same logic as in the `find_symbol` tool.
|
||||
:param relative_path: the relative path to the file containing the symbol for which to find references.
|
||||
@@ -204,7 +210,7 @@ class FindReferencingSymbolsTool(Tool):
|
||||
return self._limit_length(result, max_answer_chars)
|
||||
|
||||
|
||||
class ReplaceSymbolBodyTool(Tool, ToolMarkerCanEdit):
|
||||
class ReplaceSymbolBodyTool(Tool, ToolMarkerSymbolicEdit):
|
||||
"""
|
||||
Replaces the full definition of a symbol.
|
||||
"""
|
||||
@@ -232,7 +238,7 @@ class ReplaceSymbolBodyTool(Tool, ToolMarkerCanEdit):
|
||||
return SUCCESS_RESULT
|
||||
|
||||
|
||||
class InsertAfterSymbolTool(Tool, ToolMarkerCanEdit):
|
||||
class InsertAfterSymbolTool(Tool, ToolMarkerSymbolicEdit):
|
||||
"""
|
||||
Inserts content after the end of the definition of a given symbol.
|
||||
"""
|
||||
@@ -257,7 +263,7 @@ class InsertAfterSymbolTool(Tool, ToolMarkerCanEdit):
|
||||
return SUCCESS_RESULT
|
||||
|
||||
|
||||
class InsertBeforeSymbolTool(Tool, ToolMarkerCanEdit):
|
||||
class InsertBeforeSymbolTool(Tool, ToolMarkerSymbolicEdit):
|
||||
"""
|
||||
Inserts content before the beginning of the definition of a given symbol.
|
||||
"""
|
||||
@@ -269,9 +275,9 @@ class InsertBeforeSymbolTool(Tool, ToolMarkerCanEdit):
|
||||
body: str,
|
||||
) -> str:
|
||||
"""
|
||||
Inserts the given body/content before the beginning of the definition of the given symbol (via the symbol's location).
|
||||
A typical use case is to insert a new class, function, method, field or variable assignment.
|
||||
It also can be used to insert a new import statement before the first symbol in the file.
|
||||
Inserts the given content before the beginning of the definition of the given symbol (via the symbol's location).
|
||||
A typical use case is to insert a new class, function, method, field or variable assignment; or
|
||||
a new import statement before the first symbol in the file.
|
||||
|
||||
:param name_path: name path of the symbol before which to insert content (definitions in the `find_symbol` tool apply)
|
||||
:param relative_path: the relative path to the file containing the symbol
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import inspect
|
||||
import os
|
||||
import traceback
|
||||
from abc import ABC
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import dataclass
|
||||
@@ -16,6 +15,7 @@ from serena.prompt_factory import PromptFactory
|
||||
from serena.symbol import LanguageServerSymbolRetriever
|
||||
from serena.util.class_decorators import singleton
|
||||
from serena.util.inspection import iter_subclasses
|
||||
from solidlsp.ls_exceptions import SolidLSPException
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from serena.agent import LinesRead, MemoriesManager, SerenaAgent
|
||||
@@ -73,22 +73,40 @@ class Component(ABC):
|
||||
TOOL_DEFAULT_MAX_ANSWER_LENGTH = int(2e5)
|
||||
|
||||
|
||||
class ToolMarkerCanEdit:
|
||||
class ToolMarker:
|
||||
"""
|
||||
Base class for tool markers.
|
||||
"""
|
||||
|
||||
|
||||
class ToolMarkerCanEdit(ToolMarker):
|
||||
"""
|
||||
Marker class for all tools that can perform editing operations on files.
|
||||
"""
|
||||
|
||||
|
||||
class ToolMarkerDoesNotRequireActiveProject:
|
||||
class ToolMarkerDoesNotRequireActiveProject(ToolMarker):
|
||||
pass
|
||||
|
||||
|
||||
class ToolMarkerOptional:
|
||||
class ToolMarkerOptional(ToolMarker):
|
||||
"""
|
||||
Marker class for optional tools that are disabled by default.
|
||||
"""
|
||||
|
||||
|
||||
class ToolMarkerSymbolicRead(ToolMarker):
|
||||
"""
|
||||
Marker class for tools that perform symbol read operations.
|
||||
"""
|
||||
|
||||
|
||||
class ToolMarkerSymbolicEdit(ToolMarkerCanEdit):
|
||||
"""
|
||||
Marker class for tools that perform symbolic edit operations.
|
||||
"""
|
||||
|
||||
|
||||
class ApplyMethodProtocol(Protocol):
|
||||
"""Callable protocol for the apply method of a tool."""
|
||||
|
||||
@@ -237,18 +255,24 @@ class Tool(Component):
|
||||
self.agent.reset_language_server()
|
||||
|
||||
# apply the actual tool
|
||||
result = apply_fn(**kwargs)
|
||||
try:
|
||||
result = apply_fn(**kwargs)
|
||||
except SolidLSPException as e:
|
||||
if e.is_language_server_terminated():
|
||||
log.error(f"Language server terminated while executing tool ({e}). Restarting the language server and retrying ...")
|
||||
self.agent.reset_language_server()
|
||||
result = apply_fn(**kwargs)
|
||||
else:
|
||||
raise
|
||||
|
||||
# record tool usage
|
||||
self.agent.record_tool_usage_if_enabled(kwargs, result, self)
|
||||
|
||||
except Exception as e:
|
||||
if not catch_exceptions:
|
||||
raise
|
||||
msg = f"Error executing tool: {e}\n{traceback.format_exc()}"
|
||||
log.error(
|
||||
f"Error executing tool: {e}. "
|
||||
f"Consider restarting the language server to solve this (especially, if it's a timeout of a symbolic operation)",
|
||||
exc_info=e,
|
||||
)
|
||||
msg = f"Error executing tool: {e}"
|
||||
log.error(f"Error executing tool: {e}", exc_info=e)
|
||||
result = msg
|
||||
|
||||
if log_call:
|
||||
|
||||
@@ -5,7 +5,7 @@ Tools supporting the general workflow of the agent
|
||||
import json
|
||||
import platform
|
||||
|
||||
from serena.tools import Tool, ToolMarkerDoesNotRequireActiveProject
|
||||
from serena.tools import Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional
|
||||
|
||||
|
||||
class CheckOnboardingPerformedTool(Tool):
|
||||
@@ -96,7 +96,7 @@ class ThinkAboutWhetherYouAreDoneTool(Tool):
|
||||
return self.prompt_factory.create_think_about_whether_you_are_done()
|
||||
|
||||
|
||||
class SummarizeChangesTool(Tool):
|
||||
class SummarizeChangesTool(Tool, ToolMarkerOptional):
|
||||
"""
|
||||
Provides instructions for summarizing the changes made to the codebase.
|
||||
"""
|
||||
@@ -122,7 +122,7 @@ class PrepareForNewConversationTool(Tool):
|
||||
return self.prompt_factory.create_prepare_for_new_conversation()
|
||||
|
||||
|
||||
class InitialInstructionsTool(Tool, ToolMarkerDoesNotRequireActiveProject):
|
||||
class InitialInstructionsTool(Tool, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional):
|
||||
"""
|
||||
Gets the initial instructions for the current project.
|
||||
Should only be used in settings where the system prompt cannot be set,
|
||||
|
||||
@@ -5,7 +5,6 @@ Provides C/C++ specific instantiation of the LanguageServer class. Contains vari
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import stat
|
||||
import threading
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
@@ -13,6 +12,7 @@ from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
@@ -24,25 +24,30 @@ class ClangdLanguageServer(SolidLanguageServer):
|
||||
Also make sure compile_commands.json is created at root of the source directory. Check clangd test case for example.
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
def __init__(
|
||||
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
):
|
||||
"""
|
||||
Creates a ClangdLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
clangd_executable_path = self._setup_runtime_dependencies(logger, config)
|
||||
clangd_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=clangd_executable_path, cwd=repository_root_path),
|
||||
"cpp",
|
||||
solidlsp_settings,
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
self.service_ready_event = threading.Event()
|
||||
self.initialize_searcher_command_available = threading.Event()
|
||||
self.resolve_main_method_available = threading.Event()
|
||||
|
||||
@staticmethod
|
||||
def _setup_runtime_dependencies(logger: LanguageServerLogger, config: LanguageServerConfig) -> str:
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(
|
||||
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
|
||||
) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for ClangdLanguageServer and return the command to start the server.
|
||||
"""
|
||||
@@ -75,7 +80,7 @@ class ClangdLanguageServer(SolidLanguageServer):
|
||||
]
|
||||
)
|
||||
|
||||
clangd_ls_dir = os.path.join(os.path.dirname(__file__), "static", "clangd")
|
||||
clangd_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "clangd")
|
||||
dep = deps.single_for_current_platform()
|
||||
clangd_executable_path = deps.binary_path(clangd_ls_dir)
|
||||
if not os.path.exists(clangd_executable_path):
|
||||
@@ -89,7 +94,7 @@ class ClangdLanguageServer(SolidLanguageServer):
|
||||
f"Clangd executable not found at {clangd_executable_path}.\n"
|
||||
"Make sure you have installed clangd. See https://clangd.llvm.org/installation"
|
||||
)
|
||||
os.chmod(clangd_executable_path, stat.S_IEXEC)
|
||||
os.chmod(clangd_executable_path, 0o755)
|
||||
|
||||
return clangd_executable_path
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import logging
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
@@ -15,6 +14,7 @@ from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
@@ -78,34 +78,38 @@ class ClojureLSP(SolidLanguageServer):
|
||||
]
|
||||
)
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
def __init__(
|
||||
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
):
|
||||
"""
|
||||
Creates a ClojureLSP instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
clojure_lsp_executable_path = self._setup_runtime_dependencies(logger, config)
|
||||
clojure_lsp_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=clojure_lsp_executable_path, cwd=repository_root_path),
|
||||
"clojure",
|
||||
solidlsp_settings,
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
self.initialize_searcher_command_available = threading.Event()
|
||||
self.resolve_main_method_available = threading.Event()
|
||||
self.service_ready_event = threading.Event()
|
||||
|
||||
@staticmethod
|
||||
def _setup_runtime_dependencies(logger: LanguageServerLogger, config: LanguageServerConfig) -> str:
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(
|
||||
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
|
||||
) -> str:
|
||||
"""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()
|
||||
|
||||
clojurelsp_ls_dir = os.path.join(os.path.dirname(__file__), "static", "clojure-lsp")
|
||||
clojurelsp_ls_dir = cls.ls_resources_dir(solidlsp_settings)
|
||||
clojurelsp_executable_path = deps.binary_path(clojurelsp_ls_dir)
|
||||
if not os.path.exists(clojurelsp_executable_path):
|
||||
os.makedirs(clojurelsp_ls_dir, exist_ok=True)
|
||||
logger.log(
|
||||
f"Downloading and extracting clojure-lsp from {dependency.url} to {clojurelsp_ls_dir}",
|
||||
logging.INFO,
|
||||
@@ -113,7 +117,7 @@ class ClojureLSP(SolidLanguageServer):
|
||||
deps.install(logger, clojurelsp_ls_dir)
|
||||
if not os.path.exists(clojurelsp_executable_path):
|
||||
raise FileNotFoundError(f"Download failed? Could not find clojure-lsp executable at {clojurelsp_executable_path}")
|
||||
os.chmod(clojurelsp_executable_path, stat.S_IEXEC)
|
||||
os.chmod(clojurelsp_executable_path, 0o755)
|
||||
return clojurelsp_executable_path
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
@@ -21,7 +22,7 @@ class RuntimeDependency:
|
||||
url: str | None = None
|
||||
archive_type: str | None = None
|
||||
binary_name: str | None = None
|
||||
command: str | None = None
|
||||
command: str | list[str] | None = None
|
||||
package_name: str | None = None
|
||||
package_version: str | None = None
|
||||
extract_path: str | None = None
|
||||
@@ -71,7 +72,7 @@ class RuntimeDependencyCollection:
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def _run_command(command: str, cwd: str) -> None:
|
||||
def _run_command(command: str | list[str], cwd: str) -> None:
|
||||
kwargs = {}
|
||||
if PlatformUtils.get_platform_id().is_windows():
|
||||
kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW # type: ignore
|
||||
@@ -79,8 +80,16 @@ class RuntimeDependencyCollection:
|
||||
import pwd
|
||||
|
||||
kwargs["user"] = pwd.getpwuid(os.getuid()).pw_name
|
||||
log.info("Running command '%s' in '%s'", command, cwd)
|
||||
completed_proces = subprocess.run(
|
||||
|
||||
is_windows = platform.system() == "Windows"
|
||||
if not isinstance(command, str) and not is_windows:
|
||||
# Since we are using the shell, we need to convert the command list to a single string
|
||||
# on Linux/macOS
|
||||
command = " ".join(command)
|
||||
|
||||
log.info("Running command %s in '%s'", f"'{command}'" if isinstance(command, str) else command, cwd)
|
||||
|
||||
completed_process = subprocess.run(
|
||||
command,
|
||||
shell=True,
|
||||
check=True,
|
||||
@@ -89,11 +98,13 @@ class RuntimeDependencyCollection:
|
||||
stderr=subprocess.STDOUT,
|
||||
**kwargs,
|
||||
)
|
||||
log.log(
|
||||
logging.WARNING if completed_proces.returncode else logging.INFO,
|
||||
"Command completed with return code %d",
|
||||
completed_proces.returncode,
|
||||
)
|
||||
if completed_process.returncode != 0:
|
||||
log.warning("Command '%s' failed with return code %d", command, completed_process.returncode)
|
||||
log.warning("Command output:\n%s", completed_process.stdout)
|
||||
else:
|
||||
log.info(
|
||||
"Command completed successfully",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _install_from_url(dep: RuntimeDependency, logger: LanguageServerLogger, target_dir: str) -> None:
|
||||
|
||||
@@ -7,7 +7,6 @@ import logging
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import tarfile
|
||||
import threading
|
||||
@@ -20,11 +19,12 @@ from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_exceptions import LanguageServerException
|
||||
from solidlsp.ls_exceptions import SolidLSPException
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import PathUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from .common import RuntimeDependency
|
||||
|
||||
@@ -175,18 +175,20 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
This is the official Roslyn-based language server from Microsoft.
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
def __init__(
|
||||
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
):
|
||||
"""
|
||||
Creates a CSharpLanguageServer instance. This class is not meant to be instantiated directly.
|
||||
Use LanguageServer.create() instead.
|
||||
"""
|
||||
dotnet_path, language_server_path = self._ensure_server_installed(logger, config)
|
||||
dotnet_path, language_server_path = self._ensure_server_installed(logger, config, solidlsp_settings)
|
||||
|
||||
# Find solution or project file
|
||||
solution_or_project = find_solution_or_project_file(repository_root_path)
|
||||
|
||||
# Create log directory
|
||||
log_dir = Path(self.ls_resources_dir()) / "logs"
|
||||
log_dir = Path(self.ls_resources_dir(solidlsp_settings)) / "logs"
|
||||
log_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Build command using dotnet directly
|
||||
@@ -206,6 +208,7 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path),
|
||||
"csharp",
|
||||
solidlsp_settings,
|
||||
)
|
||||
|
||||
self.initialization_complete = threading.Event()
|
||||
@@ -215,15 +218,17 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["bin", "obj", "packages", ".vs"]
|
||||
|
||||
@classmethod
|
||||
def _ensure_server_installed(cls, logger: LanguageServerLogger, config: LanguageServerConfig) -> tuple[str, str]:
|
||||
def _ensure_server_installed(
|
||||
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
|
||||
) -> tuple[str, str]:
|
||||
"""
|
||||
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)
|
||||
dotnet_path = CSharpLanguageServer._ensure_dotnet_runtime(logger, dotnet_runtime_dep)
|
||||
server_dll_path = CSharpLanguageServer._ensure_language_server(logger, lang_server_dep)
|
||||
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
|
||||
|
||||
@@ -240,7 +245,7 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
elif system == "linux":
|
||||
return "linux-x64" if machine in ["x86_64", "amd64"] else "linux-arm64"
|
||||
else:
|
||||
raise LanguageServerException(f"Unsupported platform: {system} {machine}")
|
||||
raise SolidLSPException(f"Unsupported platform: {system} {machine}")
|
||||
|
||||
@staticmethod
|
||||
def _get_runtime_dependencies(runtime_id: str) -> tuple[RuntimeDependency, RuntimeDependency]:
|
||||
@@ -255,14 +260,16 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
dotnet_runtime_dep = dep
|
||||
|
||||
if not lang_server_dep:
|
||||
raise LanguageServerException(f"No C# language server dependency found for platform {runtime_id}")
|
||||
raise SolidLSPException(f"No C# language server dependency found for platform {runtime_id}")
|
||||
if not dotnet_runtime_dep:
|
||||
raise LanguageServerException(f"No .NET runtime dependency found for platform {runtime_id}")
|
||||
raise SolidLSPException(f"No .NET runtime dependency found for platform {runtime_id}")
|
||||
|
||||
return lang_server_dep, dotnet_runtime_dep
|
||||
|
||||
@staticmethod
|
||||
def _ensure_dotnet_runtime(logger: LanguageServerLogger, runtime_dep: RuntimeDependency) -> str:
|
||||
@classmethod
|
||||
def _ensure_dotnet_runtime(
|
||||
cls, logger: LanguageServerLogger, runtime_dep: RuntimeDependency, solidlsp_settings: SolidLSPSettings
|
||||
) -> str:
|
||||
"""Ensure .NET runtime is available and return the dotnet executable path."""
|
||||
# Check if dotnet is already available on the system
|
||||
system_dotnet = shutil.which("dotnet")
|
||||
@@ -277,15 +284,17 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
pass
|
||||
|
||||
# Download .NET 9 runtime using config
|
||||
return CSharpLanguageServer._ensure_dotnet_runtime_from_config(logger, runtime_dep)
|
||||
return cls._ensure_dotnet_runtime_from_config(logger, runtime_dep, solidlsp_settings)
|
||||
|
||||
@classmethod
|
||||
def _ensure_language_server(cls, logger: LanguageServerLogger, lang_server_dep: RuntimeDependency) -> str:
|
||||
def _ensure_language_server(
|
||||
cls, logger: LanguageServerLogger, lang_server_dep: RuntimeDependency, solidlsp_settings: SolidLSPSettings
|
||||
) -> str:
|
||||
"""Ensure language server is available and return the DLL path."""
|
||||
package_name = lang_server_dep.package_name
|
||||
package_version = lang_server_dep.package_version
|
||||
|
||||
server_dir = Path(cls.ls_resources_dir()) / f"{package_name}.{package_version}"
|
||||
server_dir = Path(cls.ls_resources_dir(solidlsp_settings)) / f"{package_name}.{package_version}"
|
||||
server_dll = server_dir / lang_server_dep.binary_name
|
||||
|
||||
if server_dll.exists():
|
||||
@@ -294,17 +303,17 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
|
||||
# Download and install the language server
|
||||
logger.log(f"Downloading {package_name} version {package_version}...", logging.INFO)
|
||||
package_path = CSharpLanguageServer._download_nuget_package_direct(logger, package_name, package_version)
|
||||
package_path = cls._download_nuget_package_direct(logger, package_name, package_version, solidlsp_settings)
|
||||
|
||||
# Extract and install
|
||||
CSharpLanguageServer._extract_language_server(lang_server_dep, package_path, server_dir)
|
||||
cls._extract_language_server(lang_server_dep, package_path, server_dir)
|
||||
|
||||
if not server_dll.exists():
|
||||
raise LanguageServerException("Microsoft.CodeAnalysis.LanguageServer.dll not found after extraction")
|
||||
raise SolidLSPException("Microsoft.CodeAnalysis.LanguageServer.dll not found after extraction")
|
||||
|
||||
# Make executable on Unix systems
|
||||
if platform.system().lower() != "windows":
|
||||
server_dll.chmod(server_dll.stat().st_mode | stat.S_IEXEC)
|
||||
server_dll.chmod(0o755)
|
||||
|
||||
logger.log(f"Successfully installed Microsoft.CodeAnalysis.LanguageServer to {server_dll}", logging.INFO)
|
||||
return str(server_dll)
|
||||
@@ -326,14 +335,16 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
source_dir = possible_dir
|
||||
break
|
||||
else:
|
||||
raise LanguageServerException(f"Could not find language server files in package. Searched in {package_path}")
|
||||
raise SolidLSPException(f"Could not find language server files in package. Searched in {package_path}")
|
||||
|
||||
# Copy files to cache directory
|
||||
server_dir.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copytree(source_dir, server_dir, dirs_exist_ok=True)
|
||||
|
||||
@classmethod
|
||||
def _download_nuget_package_direct(cls, logger: LanguageServerLogger, package_name: str, package_version: str) -> Path:
|
||||
def _download_nuget_package_direct(
|
||||
cls, logger: LanguageServerLogger, package_name: str, package_version: str, solidlsp_settings: SolidLSPSettings
|
||||
) -> Path:
|
||||
"""
|
||||
Download a NuGet package directly from the Azure NuGet feed.
|
||||
Returns the path to the extracted package directory.
|
||||
@@ -341,7 +352,7 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
azure_feed_url = "https://pkgs.dev.azure.com/azure-public/vside/_packaging/vs-impl/nuget/v3/index.json"
|
||||
|
||||
# Create temporary directory for package download
|
||||
temp_dir = Path(cls.ls_resources_dir()) / "temp_downloads"
|
||||
temp_dir = Path(cls.ls_resources_dir(solidlsp_settings)) / "temp_downloads"
|
||||
temp_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
try:
|
||||
@@ -358,7 +369,7 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
break
|
||||
|
||||
if not package_base_address:
|
||||
raise LanguageServerException("Could not find package base address in Azure NuGet feed")
|
||||
raise SolidLSPException("Could not find package base address in Azure NuGet feed")
|
||||
|
||||
# Construct the download URL for the specific package
|
||||
package_id_lower = package_name.lower()
|
||||
@@ -385,12 +396,14 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
return package_extract_dir
|
||||
|
||||
except Exception as e:
|
||||
raise LanguageServerException(
|
||||
raise SolidLSPException(
|
||||
f"Failed to download package {package_name} version {package_version} from Azure NuGet feed: {e}"
|
||||
) from e
|
||||
|
||||
@classmethod
|
||||
def _ensure_dotnet_runtime_from_config(cls, logger: LanguageServerLogger, runtime_dep: RuntimeDependency) -> str:
|
||||
def _ensure_dotnet_runtime_from_config(
|
||||
cls, logger: LanguageServerLogger, runtime_dep: RuntimeDependency, solidlsp_settings: SolidLSPSettings
|
||||
) -> str:
|
||||
"""
|
||||
Ensure .NET 9 runtime is available using runtime dependency configuration.
|
||||
Returns the path to the dotnet executable.
|
||||
@@ -408,7 +421,7 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
pass
|
||||
|
||||
# Download .NET 9 runtime using config
|
||||
dotnet_dir = Path(cls.ls_resources_dir()) / "dotnet-runtime-9.0"
|
||||
dotnet_dir = Path(cls.ls_resources_dir(solidlsp_settings)) / "dotnet-runtime-9.0"
|
||||
dotnet_exe = dotnet_dir / runtime_dep.binary_name
|
||||
|
||||
if dotnet_exe.exists():
|
||||
@@ -442,13 +455,13 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
|
||||
# Make dotnet executable on Unix
|
||||
if platform.system().lower() != "windows":
|
||||
dotnet_exe.chmod(dotnet_exe.stat().st_mode | stat.S_IEXEC)
|
||||
dotnet_exe.chmod(0o755)
|
||||
|
||||
logger.log(f"Successfully installed .NET 9 runtime to {dotnet_exe}", logging.INFO)
|
||||
return str(dotnet_exe)
|
||||
|
||||
except Exception as e:
|
||||
raise LanguageServerException(f"Failed to download .NET 9 runtime from {url}: {e}") from e
|
||||
raise SolidLSPException(f"Failed to download .NET 9 runtime from {url}: {e}") from e
|
||||
|
||||
def _get_initialize_params(self) -> InitializeParams:
|
||||
"""
|
||||
@@ -623,7 +636,7 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
self.server.start()
|
||||
except Exception as e:
|
||||
self.logger.log(f"Failed to start language server process: {e}", logging.ERROR)
|
||||
raise LanguageServerException(f"Failed to start C# language server: {e}")
|
||||
raise SolidLSPException(f"Failed to start C# language server: {e}")
|
||||
|
||||
# Send initialization
|
||||
initialize_params = self._get_initialize_params()
|
||||
@@ -633,7 +646,7 @@ class CSharpLanguageServer(SolidLanguageServer):
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
self.logger.log(f"Received initialize response: {init_response}", logging.DEBUG)
|
||||
except Exception as e:
|
||||
raise LanguageServerException(f"Failed to initialize C# language server for {self.repository_root_path}: {e}") from e
|
||||
raise SolidLSPException(f"Failed to initialize C# language server for {self.repository_root_path}: {e}") from e
|
||||
|
||||
# Apply diagnostic capabilities
|
||||
self._force_pull_diagnostics(init_response)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import stat
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
@@ -15,21 +15,22 @@ class DartLanguageServer(SolidLanguageServer):
|
||||
Provides Dart specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Dart.
|
||||
"""
|
||||
|
||||
def __init__(self, config, logger, repository_root_path):
|
||||
def __init__(self, config, logger, repository_root_path, solidlsp_settings: SolidLSPSettings):
|
||||
"""
|
||||
Creates a DartServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
executable_path = self._setup_runtime_dependencies(logger)
|
||||
executable_path = self._setup_runtime_dependencies(logger, solidlsp_settings)
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=executable_path, cwd=repository_root_path),
|
||||
"dart",
|
||||
solidlsp_settings,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(cls, logger: "LanguageServerLogger") -> str:
|
||||
def _setup_runtime_dependencies(cls, logger: "LanguageServerLogger", solidlsp_settings: SolidLSPSettings) -> str:
|
||||
deps = RuntimeDependencyCollection(
|
||||
[
|
||||
RuntimeDependency(
|
||||
@@ -75,14 +76,14 @@ class DartLanguageServer(SolidLanguageServer):
|
||||
]
|
||||
)
|
||||
|
||||
dart_ls_dir = cls.ls_resources_dir()
|
||||
dart_ls_dir = cls.ls_resources_dir(solidlsp_settings)
|
||||
dart_executable_path = deps.binary_path(dart_ls_dir)
|
||||
|
||||
if not os.path.exists(dart_executable_path):
|
||||
deps.install(logger, dart_ls_dir)
|
||||
|
||||
assert os.path.exists(dart_executable_path)
|
||||
os.chmod(dart_executable_path, stat.S_IEXEC)
|
||||
os.chmod(dart_executable_path, 0o755)
|
||||
|
||||
return f"{dart_executable_path} language-server --client-id multilspy.dart --client-version 1.2"
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import logging
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
import stat
|
||||
import threading
|
||||
import uuid
|
||||
from pathlib import PurePath
|
||||
@@ -44,18 +43,20 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
The EclipseJDTLS class provides a Java specific implementation of the LanguageServer class
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
def __init__(
|
||||
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
):
|
||||
"""
|
||||
Creates a new EclipseJDTLS instance initializing the language server settings appropriately.
|
||||
This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
runtime_dependency_paths = self._setupRuntimeDependencies(logger, config)
|
||||
runtime_dependency_paths = self._setupRuntimeDependencies(logger, config, solidlsp_settings)
|
||||
self.runtime_dependency_paths = runtime_dependency_paths
|
||||
|
||||
# ws_dir is the workspace directory for the EclipseJDTLS server
|
||||
ws_dir = str(
|
||||
PurePath(
|
||||
SolidLSPSettings.get_language_server_directory(),
|
||||
solidlsp_settings.ls_resources_dir,
|
||||
"EclipseJDTLS",
|
||||
"workspaces",
|
||||
uuid.uuid4().hex,
|
||||
@@ -63,15 +64,15 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
)
|
||||
|
||||
# shared_cache_location is the global cache used by Eclipse JDTLS across all workspaces
|
||||
shared_cache_location = str(PurePath(SolidLSPSettings.get_global_cache_directory(), "lsp", "EclipseJDTLS", "sharedIndex"))
|
||||
shared_cache_location = str(PurePath(solidlsp_settings.ls_resources_dir, "lsp", "EclipseJDTLS", "sharedIndex"))
|
||||
os.makedirs(shared_cache_location, exist_ok=True)
|
||||
os.makedirs(ws_dir, exist_ok=True)
|
||||
|
||||
jre_path = self.runtime_dependency_paths.jre_path
|
||||
lombok_jar_path = self.runtime_dependency_paths.lombok_jar_path
|
||||
|
||||
jdtls_launcher_jar = self.runtime_dependency_paths.jdtls_launcher_jar_path
|
||||
|
||||
os.makedirs(ws_dir, exist_ok=True)
|
||||
|
||||
data_dir = str(PurePath(ws_dir, "data_dir"))
|
||||
jdtls_config_path = str(PurePath(ws_dir, "config_path"))
|
||||
|
||||
@@ -132,7 +133,9 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
self.intellicode_enable_command_available = threading.Event()
|
||||
self.initialize_searcher_command_available = threading.Event()
|
||||
|
||||
super().__init__(config, logger, repository_root_path, ProcessLaunchInfo(cmd, proc_env, proc_cwd), "java")
|
||||
super().__init__(
|
||||
config, logger, repository_root_path, ProcessLaunchInfo(cmd, proc_env, proc_cwd), "java", solidlsp_settings=solidlsp_settings
|
||||
)
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
@@ -153,7 +156,9 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _setupRuntimeDependencies(cls, logger: LanguageServerLogger, config: LanguageServerConfig) -> RuntimeDependencyPaths:
|
||||
def _setupRuntimeDependencies(
|
||||
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
|
||||
) -> RuntimeDependencyPaths:
|
||||
"""
|
||||
Setup runtime dependencies for EclipseJDTLS and return the paths.
|
||||
"""
|
||||
@@ -238,7 +243,7 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
|
||||
gradle_path = str(
|
||||
PurePath(
|
||||
cls.ls_resources_dir(),
|
||||
cls.ls_resources_dir(solidlsp_settings),
|
||||
"gradle-7.3.3",
|
||||
)
|
||||
)
|
||||
@@ -254,7 +259,7 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
assert os.path.exists(gradle_path)
|
||||
|
||||
dependency = runtime_dependencies["vscode-java"][platformId.value]
|
||||
vscode_java_path = str(PurePath(cls.ls_resources_dir(), dependency["relative_extraction_path"]))
|
||||
vscode_java_path = str(PurePath(cls.ls_resources_dir(solidlsp_settings), dependency["relative_extraction_path"]))
|
||||
os.makedirs(vscode_java_path, exist_ok=True)
|
||||
jre_home_path = str(PurePath(vscode_java_path, dependency["jre_home_path"]))
|
||||
jre_path = str(PurePath(vscode_java_path, dependency["jre_path"]))
|
||||
@@ -273,7 +278,7 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
):
|
||||
FileUtils.download_and_extract_archive(logger, dependency["url"], vscode_java_path, dependency["archiveType"])
|
||||
|
||||
os.chmod(jre_path, stat.S_IEXEC)
|
||||
os.chmod(jre_path, 0o755)
|
||||
|
||||
assert os.path.exists(vscode_java_path)
|
||||
assert os.path.exists(jre_home_path)
|
||||
@@ -283,7 +288,7 @@ class EclipseJDTLS(SolidLanguageServer):
|
||||
assert os.path.exists(jdtls_readonly_config_path)
|
||||
|
||||
dependency = runtime_dependencies["intellicode"]["platform-agnostic"]
|
||||
intellicode_directory_path = str(PurePath(cls.ls_resources_dir(), dependency["relative_extraction_path"]))
|
||||
intellicode_directory_path = str(PurePath(cls.ls_resources_dir(solidlsp_settings), dependency["relative_extraction_path"]))
|
||||
os.makedirs(intellicode_directory_path, exist_ok=True)
|
||||
intellicode_jar_path = str(PurePath(intellicode_directory_path, dependency["intellicode_jar_path"]))
|
||||
intellisense_members_path = str(PurePath(intellicode_directory_path, dependency["intellisense_members_path"]))
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
@@ -6,16 +5,18 @@ import stat
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
from pathlib import PurePath
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import FileUtils, PlatformUtils
|
||||
from solidlsp.ls_utils import FileUtils, PlatformId, PlatformUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from ..common import RuntimeDependency
|
||||
|
||||
|
||||
class ElixirTools(SolidLanguageServer):
|
||||
@@ -68,8 +69,8 @@ class ElixirTools(SolidLanguageServer):
|
||||
|
||||
return filtered_response
|
||||
|
||||
@staticmethod
|
||||
def _get_elixir_version():
|
||||
@classmethod
|
||||
def _get_elixir_version(cls):
|
||||
"""Get the installed Elixir version or None if not found."""
|
||||
try:
|
||||
result = subprocess.run(["elixir", "--version"], capture_output=True, text=True, check=False)
|
||||
@@ -79,13 +80,16 @@ class ElixirTools(SolidLanguageServer):
|
||||
return None
|
||||
return None
|
||||
|
||||
def setupRuntimeDependencies(self, logger: LanguageServerLogger, config: LanguageServerConfig) -> str:
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(
|
||||
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
|
||||
) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for Next LS.
|
||||
Downloads the Next LS binary for the current platform and returns the path to the executable.
|
||||
"""
|
||||
# Check if Elixir is available first
|
||||
elixir_version = self._get_elixir_version()
|
||||
elixir_version = cls._get_elixir_version()
|
||||
if not elixir_version:
|
||||
raise RuntimeError(
|
||||
"Elixir is not installed. Please install Elixir from https://elixir-lang.org/install.html and make sure it is added to your PATH."
|
||||
@@ -93,70 +97,78 @@ class ElixirTools(SolidLanguageServer):
|
||||
|
||||
logger.log(f"Found Elixir: {elixir_version}", logging.INFO)
|
||||
|
||||
platformId = PlatformUtils.get_platform_id()
|
||||
|
||||
with open(str(PurePath(os.path.dirname(__file__), "runtime_dependencies.json")), encoding="utf-8") as f:
|
||||
runtimeDependencies = json.load(f)
|
||||
del runtimeDependencies["_description"]
|
||||
|
||||
os.makedirs(str(PurePath(os.path.abspath(os.path.dirname(__file__)), "static")), exist_ok=True)
|
||||
|
||||
# Map platform IDs to runtime dependency keys
|
||||
platform_mapping = {
|
||||
"linux-x64": "linux-x64",
|
||||
"osx-x64": "darwin-x64",
|
||||
"osx-arm64": "darwin-arm64",
|
||||
"darwin-x64": "darwin-x64",
|
||||
"darwin-arm64": "darwin-arm64",
|
||||
"win-x64": "win-x64",
|
||||
}
|
||||
|
||||
platform_key = platform_mapping.get(platformId.value)
|
||||
if not platform_key:
|
||||
raise RuntimeError(f"Unsupported platform for Next LS: {platformId.value}")
|
||||
platform_id = PlatformUtils.get_platform_id()
|
||||
|
||||
# Check for Windows and provide a helpful error message
|
||||
if platformId.value.startswith("win"):
|
||||
if platform_id.value.startswith("win"):
|
||||
raise RuntimeError(
|
||||
"Windows is not supported by Next LS. The Next LS project does not provide Windows binaries. "
|
||||
"Consider using Windows Subsystem for Linux (WSL) or a virtual machine with Linux/macOS."
|
||||
)
|
||||
|
||||
dependency = runtimeDependencies["next_ls"][platform_key]
|
||||
next_ls_dir = str(PurePath(os.path.abspath(os.path.dirname(__file__)), "static", dependency["relative_extraction_path"]))
|
||||
os.makedirs(next_ls_dir, exist_ok=True)
|
||||
valid_platforms = [
|
||||
PlatformId.LINUX_x64,
|
||||
PlatformId.OSX_x64,
|
||||
PlatformId.OSX_arm64,
|
||||
]
|
||||
assert platform_id in valid_platforms, f"Platform {platform_id} is not supported for Next LS at the moment"
|
||||
|
||||
executable_path = str(PurePath(next_ls_dir, dependency["executable_name"]))
|
||||
binary_path = str(PurePath(next_ls_dir, dependency["binary_name"]))
|
||||
next_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "next-ls")
|
||||
|
||||
# Define runtime dependencies inline
|
||||
runtime_deps = {
|
||||
PlatformId.LINUX_x64: RuntimeDependency(
|
||||
id="next_ls_linux_amd64",
|
||||
platform_id="linux-x64",
|
||||
url="https://github.com/elixir-tools/next-ls/releases/download/v0.23.3/next_ls_linux_amd64",
|
||||
archive_type="binary",
|
||||
binary_name="next_ls_linux_amd64",
|
||||
extract_path="next_ls",
|
||||
),
|
||||
PlatformId.OSX_x64: RuntimeDependency(
|
||||
id="next_ls_darwin_amd64",
|
||||
platform_id="osx-x64",
|
||||
url="https://github.com/elixir-tools/next-ls/releases/download/v0.23.3/next_ls_darwin_amd64",
|
||||
archive_type="binary",
|
||||
binary_name="next_ls_darwin_amd64",
|
||||
extract_path="next_ls",
|
||||
),
|
||||
PlatformId.OSX_arm64: RuntimeDependency(
|
||||
id="next_ls_darwin_arm64",
|
||||
platform_id="osx-arm64",
|
||||
url="https://github.com/elixir-tools/next-ls/releases/download/v0.23.3/next_ls_darwin_arm64",
|
||||
archive_type="binary",
|
||||
binary_name="next_ls_darwin_arm64",
|
||||
extract_path="next_ls",
|
||||
),
|
||||
}
|
||||
|
||||
dependency = runtime_deps[platform_id]
|
||||
executable_path = os.path.join(next_ls_dir, "nextls")
|
||||
binary_path = os.path.join(next_ls_dir, dependency.binary_name)
|
||||
|
||||
if not os.path.exists(executable_path):
|
||||
logger.log(f"Downloading Next LS binary from {dependency['url']}", logging.INFO)
|
||||
FileUtils.download_file(logger, dependency["url"], binary_path)
|
||||
logger.log(f"Downloading Next LS binary from {dependency.url}", logging.INFO)
|
||||
FileUtils.download_file(logger, dependency.url, binary_path)
|
||||
|
||||
# Make the binary executable on Unix-like systems
|
||||
if not platformId.value.startswith("win"):
|
||||
os.chmod(binary_path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
|
||||
os.chmod(binary_path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
|
||||
|
||||
# Create a symlink or copy with the expected name
|
||||
# Create a symlink with the expected name
|
||||
if binary_path != executable_path:
|
||||
if os.path.exists(executable_path):
|
||||
os.remove(executable_path)
|
||||
if platformId.value.startswith("win"):
|
||||
# On Windows, copy the file
|
||||
import shutil
|
||||
|
||||
shutil.copy2(binary_path, executable_path)
|
||||
else:
|
||||
# On Unix-like systems, create a symlink
|
||||
os.symlink(os.path.basename(binary_path), executable_path)
|
||||
os.symlink(os.path.basename(binary_path), executable_path)
|
||||
|
||||
assert os.path.exists(executable_path), f"Next LS executable not found at {executable_path}"
|
||||
|
||||
logger.log(f"Next LS binary ready at: {executable_path}", logging.INFO)
|
||||
return executable_path
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
nextls_executable_path = self.setupRuntimeDependencies(logger, config)
|
||||
def __init__(
|
||||
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
):
|
||||
nextls_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
|
||||
|
||||
super().__init__(
|
||||
config,
|
||||
@@ -164,6 +176,7 @@ class ElixirTools(SolidLanguageServer):
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=f'"{nextls_executable_path}" --stdio', cwd=repository_root_path),
|
||||
"elixir",
|
||||
solidlsp_settings,
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
self.request_id = 0
|
||||
@@ -171,29 +184,66 @@ class ElixirTools(SolidLanguageServer):
|
||||
# Set generous timeout for Next LS which can be slow to initialize and respond
|
||||
self.set_request_timeout(180.0) # 60 seconds for all environments
|
||||
|
||||
def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
Returns the initialize params for the Next LS Language Server.
|
||||
"""
|
||||
with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
root_uri = pathlib.Path(repository_absolute_path).as_uri()
|
||||
initialize_params = {
|
||||
"processId": os.getpid(),
|
||||
"locale": "en",
|
||||
"rootPath": repository_absolute_path,
|
||||
"rootUri": root_uri,
|
||||
"initializationOptions": {
|
||||
"mix_env": "dev",
|
||||
"mix_target": "host",
|
||||
"experimental": {"completions": {"enable": False}},
|
||||
"extensions": {"credo": {"enable": True, "cli_options": []}},
|
||||
},
|
||||
"capabilities": {
|
||||
"textDocument": {
|
||||
"synchronization": {"didSave": True, "dynamicRegistration": True},
|
||||
"completion": {
|
||||
"dynamicRegistration": True,
|
||||
"completionItem": {"snippetSupport": True, "documentationFormat": ["markdown", "plaintext"]},
|
||||
},
|
||||
"definition": {"dynamicRegistration": True},
|
||||
"references": {"dynamicRegistration": True},
|
||||
"documentSymbol": {
|
||||
"dynamicRegistration": True,
|
||||
"hierarchicalDocumentSymbolSupport": True,
|
||||
"symbolKind": {"valueSet": list(range(1, 27))},
|
||||
},
|
||||
"hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
|
||||
"formatting": {"dynamicRegistration": True},
|
||||
"codeAction": {
|
||||
"dynamicRegistration": True,
|
||||
"codeActionLiteralSupport": {
|
||||
"codeActionKind": {
|
||||
"valueSet": [
|
||||
"quickfix",
|
||||
"refactor",
|
||||
"refactor.extract",
|
||||
"refactor.inline",
|
||||
"refactor.rewrite",
|
||||
"source",
|
||||
"source.organizeImports",
|
||||
]
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"workspace": {
|
||||
"workspaceFolders": True,
|
||||
"didChangeConfiguration": {"dynamicRegistration": True},
|
||||
"executeCommand": {"dynamicRegistration": True},
|
||||
},
|
||||
},
|
||||
"workspaceFolders": [{"uri": root_uri, "name": os.path.basename(repository_absolute_path)}],
|
||||
}
|
||||
|
||||
del d["_description"]
|
||||
|
||||
d["processId"] = os.getpid()
|
||||
assert d["rootPath"] == "$rootPath"
|
||||
d["rootPath"] = repository_absolute_path
|
||||
|
||||
assert d["rootUri"] == "$rootUri"
|
||||
d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["uri"] == "$uri"
|
||||
d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["name"] == "$name"
|
||||
d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path)
|
||||
|
||||
return d
|
||||
return initialize_params
|
||||
|
||||
def _start_server(self):
|
||||
"""Start Next LS server process"""
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
{
|
||||
"_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize",
|
||||
"processId": "os.getpid()",
|
||||
"locale": "en",
|
||||
"rootPath": "$rootPath",
|
||||
"rootUri": "$rootUri",
|
||||
"initializationOptions": {
|
||||
"mix_env": "dev",
|
||||
"mix_target": "host",
|
||||
"experimental": {
|
||||
"completions": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
"extensions": {
|
||||
"credo": {
|
||||
"enable": true,
|
||||
"cli_options": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"capabilities": {
|
||||
"textDocument": {
|
||||
"synchronization": {
|
||||
"didSave": true,
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"completion": {
|
||||
"dynamicRegistration": true,
|
||||
"completionItem": {
|
||||
"snippetSupport": true,
|
||||
"documentationFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
]
|
||||
}
|
||||
},
|
||||
"definition": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"references": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"documentSymbol": {
|
||||
"dynamicRegistration": true,
|
||||
"hierarchicalDocumentSymbolSupport": true,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
|
||||
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26
|
||||
]
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"dynamicRegistration": true,
|
||||
"contentFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
]
|
||||
},
|
||||
"formatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"codeAction": {
|
||||
"dynamicRegistration": true,
|
||||
"codeActionLiteralSupport": {
|
||||
"codeActionKind": {
|
||||
"valueSet": [
|
||||
"quickfix",
|
||||
"refactor",
|
||||
"refactor.extract",
|
||||
"refactor.inline",
|
||||
"refactor.rewrite",
|
||||
"source",
|
||||
"source.organizeImports"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"workspaceFolders": true,
|
||||
"didChangeConfiguration": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"executeCommand": {
|
||||
"dynamicRegistration": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspaceFolders": [
|
||||
{
|
||||
"uri": "$uri",
|
||||
"name": "$name"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"_description": "This file lists the runtime dependencies for the Elixir Language Server (Next LS)",
|
||||
"next_ls": {
|
||||
"linux-x64": {
|
||||
"url": "https://github.com/elixir-tools/next-ls/releases/download/v0.23.3/next_ls_linux_amd64",
|
||||
"archiveType": "binary",
|
||||
"relative_extraction_path": "next_ls",
|
||||
"binary_name": "next_ls_linux_amd64",
|
||||
"executable_name": "nextls"
|
||||
},
|
||||
"darwin-x64": {
|
||||
"url": "https://github.com/elixir-tools/next-ls/releases/download/v0.23.3/next_ls_darwin_amd64",
|
||||
"archiveType": "binary",
|
||||
"relative_extraction_path": "next_ls",
|
||||
"binary_name": "next_ls_darwin_amd64",
|
||||
"executable_name": "nextls"
|
||||
},
|
||||
"darwin-arm64": {
|
||||
"url": "https://github.com/elixir-tools/next-ls/releases/download/v0.23.3/next_ls_darwin_arm64",
|
||||
"archiveType": "binary",
|
||||
"relative_extraction_path": "next_ls",
|
||||
"binary_name": "next_ls_darwin_arm64",
|
||||
"executable_name": "nextls"
|
||||
},
|
||||
"win-x64": {
|
||||
"url": "https://github.com/elixir-tools/next-ls/releases/download/v0.23.3/next_ls_windows_amd64.exe",
|
||||
"archiveType": "binary",
|
||||
"relative_extraction_path": "next_ls",
|
||||
"binary_name": "next_ls_windows_amd64.exe",
|
||||
"executable_name": "nextls.exe"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
|
||||
class Gopls(SolidLanguageServer):
|
||||
@@ -70,7 +71,9 @@ class Gopls(SolidLanguageServer):
|
||||
|
||||
return True
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
def __init__(
|
||||
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
):
|
||||
self._setup_runtime_dependency()
|
||||
|
||||
super().__init__(
|
||||
@@ -79,6 +82,7 @@ class Gopls(SolidLanguageServer):
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd="gopls", cwd=repository_root_path),
|
||||
"go",
|
||||
solidlsp_settings,
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
self.request_id = 0
|
||||
|
||||
@@ -16,6 +16,7 @@ from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import PlatformId, PlatformUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import DefinitionParams, InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
@@ -34,7 +35,9 @@ class Intelephense(SolidLanguageServer):
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["node_modules", "vendor", "cache"]
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(cls, logger: LanguageServerLogger, config: LanguageServerConfig) -> str:
|
||||
def _setup_runtime_dependencies(
|
||||
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
|
||||
) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for Intelephense and return the command to start the server.
|
||||
"""
|
||||
@@ -58,7 +61,7 @@ class Intelephense(SolidLanguageServer):
|
||||
assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
|
||||
|
||||
# Install intelephense if not already installed
|
||||
intelephense_ls_dir = os.path.join(cls.ls_resources_dir(), "php-lsp")
|
||||
intelephense_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "php-lsp")
|
||||
os.makedirs(intelephense_ls_dir, exist_ok=True)
|
||||
intelephense_executable_path = os.path.join(intelephense_ls_dir, "node_modules", ".bin", "intelephense")
|
||||
if not os.path.exists(intelephense_executable_path):
|
||||
@@ -79,11 +82,20 @@ class Intelephense(SolidLanguageServer):
|
||||
|
||||
return f"{intelephense_executable_path} --stdio"
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
def __init__(
|
||||
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
):
|
||||
# Setup runtime dependencies before initializing
|
||||
intelephense_cmd = self._setup_runtime_dependencies(logger, config)
|
||||
intelephense_cmd = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
|
||||
|
||||
super().__init__(config, logger, repository_root_path, ProcessLaunchInfo(cmd=intelephense_cmd, cwd=repository_root_path), "php")
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=intelephense_cmd, cwd=repository_root_path),
|
||||
"php",
|
||||
solidlsp_settings,
|
||||
)
|
||||
self.request_id = 0
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -13,6 +13,7 @@ from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
|
||||
class JediServer(SolidLanguageServer):
|
||||
@@ -20,7 +21,9 @@ class JediServer(SolidLanguageServer):
|
||||
Provides Python specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Python.
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
def __init__(
|
||||
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
):
|
||||
"""
|
||||
Creates a JediServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
@@ -30,6 +33,7 @@ class JediServer(SolidLanguageServer):
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd="jedi-language-server", cwd=repository_root_path),
|
||||
"python",
|
||||
solidlsp_settings,
|
||||
)
|
||||
|
||||
@override
|
||||
|
||||
@@ -14,6 +14,7 @@ from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import FileUtils, PlatformUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
@@ -32,11 +33,13 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
Provides Kotlin specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Kotlin.
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
def __init__(
|
||||
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
):
|
||||
"""
|
||||
Creates a Kotlin Language Server instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
runtime_dependency_paths = self._setup_runtime_dependencies(logger, config)
|
||||
runtime_dependency_paths = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
|
||||
self.runtime_dependency_paths = runtime_dependency_paths
|
||||
|
||||
# Create command to execute the Kotlin Language Server script
|
||||
@@ -51,10 +54,13 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=cmd, env=proc_env, cwd=repository_root_path),
|
||||
"kotlin",
|
||||
solidlsp_settings,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(cls, logger: LanguageServerLogger, config: LanguageServerConfig) -> KotlinRuntimeDependencyPaths:
|
||||
def _setup_runtime_dependencies(
|
||||
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
|
||||
) -> KotlinRuntimeDependencyPaths:
|
||||
"""
|
||||
Setup runtime dependencies for Kotlin Language Server and return the paths.
|
||||
"""
|
||||
@@ -111,7 +117,7 @@ class KotlinLanguageServer(SolidLanguageServer):
|
||||
java_dependency = runtime_dependencies["java"][platform_id.value]
|
||||
|
||||
# Setup paths for dependencies
|
||||
static_dir = os.path.join(cls.ls_resources_dir(), "kotlin_language_server")
|
||||
static_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "kotlin_language_server")
|
||||
os.makedirs(static_dir, exist_ok=True)
|
||||
|
||||
# Setup Java paths
|
||||
|
||||
@@ -6,7 +6,6 @@ import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import stat
|
||||
import threading
|
||||
from collections.abc import Iterable
|
||||
|
||||
@@ -14,11 +13,12 @@ from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_exceptions import LanguageServerException
|
||||
from solidlsp.ls_exceptions import SolidLSPException
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import DotnetVersion, FileUtils, PlatformId, PlatformUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
|
||||
def breadth_first_file_scan(root) -> Iterable[str]:
|
||||
@@ -59,16 +59,18 @@ class OmniSharp(SolidLanguageServer):
|
||||
Provides C# specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C#.
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
def __init__(
|
||||
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
):
|
||||
"""
|
||||
Creates an OmniSharp instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
omnisharp_executable_path, dll_path = self._setup_runtime_dependencies(logger, config)
|
||||
omnisharp_executable_path, dll_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
|
||||
|
||||
slnfilename = find_least_depth_sln_file(repository_root_path)
|
||||
if slnfilename is None:
|
||||
logger.log("No *.sln file found in repository", logging.ERROR)
|
||||
raise LanguageServerException("No SLN file found in repository")
|
||||
raise SolidLSPException("No SLN file found in repository")
|
||||
|
||||
cmd = " ".join(
|
||||
[
|
||||
@@ -102,7 +104,9 @@ class OmniSharp(SolidLanguageServer):
|
||||
"formattingOptions:indentationSize=4",
|
||||
]
|
||||
)
|
||||
super().__init__(config, logger, repository_root_path, ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path), "csharp")
|
||||
super().__init__(
|
||||
config, logger, repository_root_path, ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path), "csharp", solidlsp_settings
|
||||
)
|
||||
|
||||
self.server_ready = threading.Event()
|
||||
self.definition_available = threading.Event()
|
||||
@@ -138,7 +142,9 @@ class OmniSharp(SolidLanguageServer):
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(cls, logger: LanguageServerLogger, config: LanguageServerConfig) -> tuple[str, str]:
|
||||
def _setup_runtime_dependencies(
|
||||
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
|
||||
) -> tuple[str, str]:
|
||||
"""
|
||||
Setup runtime dependencies for OmniSharp.
|
||||
"""
|
||||
@@ -181,15 +187,15 @@ class OmniSharp(SolidLanguageServer):
|
||||
assert "OmniSharp" in runtime_dependencies
|
||||
assert "RazorOmnisharp" in runtime_dependencies
|
||||
|
||||
omnisharp_ls_dir = os.path.join(cls.ls_resources_dir(), "OmniSharp")
|
||||
omnisharp_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "OmniSharp")
|
||||
if not os.path.exists(omnisharp_ls_dir):
|
||||
os.makedirs(omnisharp_ls_dir)
|
||||
FileUtils.download_and_extract_archive(logger, runtime_dependencies["OmniSharp"]["url"], omnisharp_ls_dir, "zip")
|
||||
omnisharp_executable_path = os.path.join(omnisharp_ls_dir, runtime_dependencies["OmniSharp"]["binaryName"])
|
||||
assert os.path.exists(omnisharp_executable_path)
|
||||
os.chmod(omnisharp_executable_path, stat.S_IEXEC)
|
||||
os.chmod(omnisharp_executable_path, 0o755)
|
||||
|
||||
razor_omnisharp_ls_dir = os.path.join(cls.ls_resources_dir(), "RazorOmnisharp")
|
||||
razor_omnisharp_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "RazorOmnisharp")
|
||||
if not os.path.exists(razor_omnisharp_ls_dir):
|
||||
os.makedirs(razor_omnisharp_ls_dir)
|
||||
FileUtils.download_and_extract_archive(logger, runtime_dependencies["RazorOmnisharp"]["url"], razor_omnisharp_ls_dir, "zip")
|
||||
|
||||
@@ -15,6 +15,7 @@ from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
|
||||
class PyrightServer(SolidLanguageServer):
|
||||
@@ -23,7 +24,9 @@ class PyrightServer(SolidLanguageServer):
|
||||
Contains various configurations and settings specific to Python.
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
def __init__(
|
||||
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
):
|
||||
"""
|
||||
Creates a PyrightServer instance. This class is not meant to be instantiated directly.
|
||||
Use LanguageServer.create() instead.
|
||||
@@ -36,6 +39,7 @@ class PyrightServer(SolidLanguageServer):
|
||||
# Note 2: we can also use `bpyright-langserver --stdio` if we ever are unhappy with pyright
|
||||
ProcessLaunchInfo(cmd="python -m pyright.langserver --stdio", cwd=repository_root_path),
|
||||
"python",
|
||||
solidlsp_settings,
|
||||
)
|
||||
|
||||
# Event to signal when initial workspace analysis is complete
|
||||
|
||||
@@ -5,7 +5,6 @@ Provides Rust specific instantiation of the LanguageServer class. Contains vario
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import stat
|
||||
import threading
|
||||
|
||||
from overrides import override
|
||||
@@ -15,6 +14,7 @@ from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
@@ -24,17 +24,20 @@ class RustAnalyzer(SolidLanguageServer):
|
||||
Provides Rust specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Rust.
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
def __init__(
|
||||
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
):
|
||||
"""
|
||||
Creates a RustAnalyzer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
rustanalyzer_executable_path = self._setup_runtime_dependencies(logger, config)
|
||||
rustanalyzer_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=rustanalyzer_executable_path, cwd=repository_root_path),
|
||||
"rust",
|
||||
solidlsp_settings,
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
self.service_ready_event = threading.Event()
|
||||
@@ -46,7 +49,9 @@ class RustAnalyzer(SolidLanguageServer):
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["target"]
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(cls, logger: LanguageServerLogger, config: LanguageServerConfig) -> str:
|
||||
def _setup_runtime_dependencies(
|
||||
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
|
||||
) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for rust_analyzer and return the command to start the server.
|
||||
"""
|
||||
@@ -100,13 +105,13 @@ class RustAnalyzer(SolidLanguageServer):
|
||||
# "win-x64",
|
||||
# ], "Only linux-x64 and win-x64 platform is supported for in multilspy at the moment"
|
||||
|
||||
rustanalyzer_ls_dir = os.path.join(cls.ls_resources_dir(), "RustAnalyzer")
|
||||
rustanalyzer_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "RustAnalyzer")
|
||||
rustanalyzer_executable_path = deps.binary_path(rustanalyzer_ls_dir)
|
||||
if not os.path.exists(rustanalyzer_ls_dir):
|
||||
os.makedirs(rustanalyzer_ls_dir)
|
||||
deps.install(logger, rustanalyzer_ls_dir)
|
||||
assert os.path.exists(rustanalyzer_executable_path)
|
||||
os.chmod(rustanalyzer_executable_path, stat.S_IEXEC)
|
||||
os.chmod(rustanalyzer_executable_path, 0o755)
|
||||
|
||||
return rustanalyzer_executable_path
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import stat
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
@@ -18,6 +17,7 @@ from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
|
||||
class Solargraph(SolidLanguageServer):
|
||||
@@ -26,7 +26,9 @@ class Solargraph(SolidLanguageServer):
|
||||
Contains various configurations and settings specific to Ruby.
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
def __init__(
|
||||
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
):
|
||||
"""
|
||||
Creates a Solargraph instance. This class is not meant to be instantiated directly.
|
||||
Use LanguageServer.create() instead.
|
||||
@@ -38,6 +40,7 @@ class Solargraph(SolidLanguageServer):
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=f"{solargraph_executable_path} stdio", cwd=repository_root_path),
|
||||
"ruby",
|
||||
solidlsp_settings,
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
self.service_ready_event = threading.Event()
|
||||
@@ -83,19 +86,7 @@ class Solargraph(SolidLanguageServer):
|
||||
logger.log("Installing Solargraph...", logging.INFO)
|
||||
subprocess.run(dependency["installCommand"].split(), check=True, capture_output=True, cwd=repository_root_path)
|
||||
|
||||
# Get the gem executable path directly
|
||||
result = subprocess.run(["gem", "which", "solargraph"], check=True, capture_output=True, text=True, cwd=repository_root_path)
|
||||
gem_path = result.stdout.strip()
|
||||
bin_dir = os.path.join(os.path.dirname(os.path.dirname(gem_path)), "bin")
|
||||
executable_path = os.path.join(bin_dir, "solargraph")
|
||||
|
||||
if not os.path.exists(executable_path):
|
||||
raise RuntimeError(f"Solargraph executable not found at {executable_path}")
|
||||
|
||||
# Ensure the executable has the right permissions
|
||||
os.chmod(executable_path, os.stat(executable_path).st_mode | stat.S_IEXEC)
|
||||
|
||||
return executable_path
|
||||
return "gem exec solargraph"
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError(f"Failed to check or install Solargraph. {e.stderr}") from e
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import threading
|
||||
|
||||
from overrides import override
|
||||
@@ -12,6 +11,7 @@ from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import PathUtils, PlatformUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
@@ -57,7 +57,7 @@ class TerraformLS(SolidLanguageServer):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(cls, logger: LanguageServerLogger) -> str:
|
||||
def _setup_runtime_dependencies(cls, logger: LanguageServerLogger, solidlsp_settings: SolidLSPSettings) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for terraform-ls.
|
||||
Downloads and installs terraform-ls if not already present.
|
||||
@@ -102,24 +102,26 @@ class TerraformLS(SolidLanguageServer):
|
||||
)
|
||||
dependency = deps.single_for_current_platform()
|
||||
|
||||
terraform_ls_executable_path = deps.binary_path(cls.ls_resources_dir())
|
||||
terraform_ls_executable_path = deps.binary_path(cls.ls_resources_dir(solidlsp_settings))
|
||||
if not os.path.exists(terraform_ls_executable_path):
|
||||
logger.log(f"Downloading terraform-ls from {dependency.url}", logging.INFO)
|
||||
deps.install(logger, cls.ls_resources_dir())
|
||||
deps.install(logger, cls.ls_resources_dir(solidlsp_settings))
|
||||
|
||||
assert os.path.exists(terraform_ls_executable_path), f"terraform-ls executable not found at {terraform_ls_executable_path}"
|
||||
|
||||
# Make the executable file executable on Unix-like systems
|
||||
if platform_id.value != "win-x64":
|
||||
os.chmod(terraform_ls_executable_path, stat.S_IEXEC | stat.S_IREAD)
|
||||
os.chmod(terraform_ls_executable_path, 0o755)
|
||||
|
||||
return terraform_ls_executable_path
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
def __init__(
|
||||
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
):
|
||||
"""
|
||||
Creates a TerraformLS instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
terraform_ls_executable_path = self._setup_runtime_dependencies(logger)
|
||||
terraform_ls_executable_path = self._setup_runtime_dependencies(logger, solidlsp_settings)
|
||||
|
||||
super().__init__(
|
||||
config,
|
||||
@@ -127,6 +129,7 @@ class TerraformLS(SolidLanguageServer):
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=f"{terraform_ls_executable_path} serve", cwd=repository_root_path),
|
||||
"terraform",
|
||||
solidlsp_settings,
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
self.request_id = 0
|
||||
|
||||
@@ -18,6 +18,7 @@ from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import PlatformId, PlatformUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
@@ -42,17 +43,20 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
||||
Provides TypeScript specific instantiation of the LanguageServer class. Contains various configurations and settings specific to TypeScript.
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
def __init__(
|
||||
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
):
|
||||
"""
|
||||
Creates a TypeScriptLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
ts_lsp_executable_path = self._setup_runtime_dependencies(logger, config)
|
||||
ts_lsp_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=ts_lsp_executable_path, cwd=repository_root_path),
|
||||
"typescript",
|
||||
solidlsp_settings,
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
self.initialize_searcher_command_available = threading.Event()
|
||||
@@ -67,7 +71,9 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(cls, logger: LanguageServerLogger, config: LanguageServerConfig) -> str:
|
||||
def _setup_runtime_dependencies(
|
||||
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
|
||||
) -> list[str]:
|
||||
"""
|
||||
Setup runtime dependencies for TypeScript Language Server and return the command to start the server.
|
||||
"""
|
||||
@@ -89,13 +95,13 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
||||
RuntimeDependency(
|
||||
id="typescript",
|
||||
description="typescript package",
|
||||
command="npm install --prefix ./ typescript@5.5.4",
|
||||
command=["npm", "install", "--prefix", "./", "typescript@5.5.4"],
|
||||
platform_id="any",
|
||||
),
|
||||
RuntimeDependency(
|
||||
id="typescript-language-server",
|
||||
description="typescript-language-server package",
|
||||
command="npm install --prefix ./ typescript-language-server@4.3.3",
|
||||
command=["npm", "install", "--prefix", "./", "typescript-language-server@4.3.3"],
|
||||
platform_id="any",
|
||||
),
|
||||
]
|
||||
@@ -114,7 +120,7 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
||||
assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
|
||||
|
||||
# Install typescript and typescript-language-server if not already installed
|
||||
tsserver_ls_dir = os.path.join(cls.ls_resources_dir(), "ts-lsp")
|
||||
tsserver_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "ts-lsp")
|
||||
tsserver_executable_path = os.path.join(tsserver_ls_dir, "node_modules", ".bin", "typescript-language-server")
|
||||
if not os.path.exists(tsserver_executable_path):
|
||||
logger.log(f"Typescript Language Server executable not found at {tsserver_executable_path}. Installing...", logging.INFO)
|
||||
@@ -125,7 +131,7 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
||||
raise FileNotFoundError(
|
||||
f"typescript-language-server executable not found at {tsserver_executable_path}, something went wrong with the installation."
|
||||
)
|
||||
return f"{tsserver_executable_path} --stdio"
|
||||
return [tsserver_executable_path, "--stdio"]
|
||||
|
||||
@staticmethod
|
||||
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
|
||||
|
||||
@@ -19,6 +19,7 @@ from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import PlatformId, PlatformUtils
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
from .common import RuntimeDependency, RuntimeDependencyCollection
|
||||
|
||||
@@ -29,17 +30,20 @@ class VtsLanguageServer(SolidLanguageServer):
|
||||
Contains various configurations and settings specific to TypeScript via vtsls wrapper.
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str):
|
||||
def __init__(
|
||||
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
|
||||
):
|
||||
"""
|
||||
Creates a VtsLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
vts_lsp_executable_path = self._setup_runtime_dependencies(logger, config)
|
||||
vts_lsp_executable_path = self._setup_runtime_dependencies(logger, config, solidlsp_settings)
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=vts_lsp_executable_path, cwd=repository_root_path),
|
||||
"typescript",
|
||||
solidlsp_settings,
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
self.initialize_searcher_command_available = threading.Event()
|
||||
@@ -54,7 +58,9 @@ class VtsLanguageServer(SolidLanguageServer):
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _setup_runtime_dependencies(cls, logger: LanguageServerLogger, config: LanguageServerConfig) -> str:
|
||||
def _setup_runtime_dependencies(
|
||||
cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings
|
||||
) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for VTS Language Server and return the command to start the server.
|
||||
"""
|
||||
@@ -81,7 +87,7 @@ class VtsLanguageServer(SolidLanguageServer):
|
||||
),
|
||||
]
|
||||
)
|
||||
vts_ls_dir = os.path.join(cls.ls_resources_dir(), "vts-lsp")
|
||||
vts_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "vts-lsp")
|
||||
vts_executable_path = os.path.join(vts_ls_dir, "vtsls")
|
||||
|
||||
# Verify both node and npm are installed
|
||||
|
||||
+54
-33
@@ -5,6 +5,7 @@ import logging
|
||||
import os
|
||||
import pathlib
|
||||
import pickle
|
||||
import shutil
|
||||
import subprocess
|
||||
import threading
|
||||
from abc import ABC, abstractmethod
|
||||
@@ -21,7 +22,7 @@ from serena.text_utils import MatchedConsecutiveLines
|
||||
from serena.util.file_system import match_path
|
||||
from solidlsp import ls_types
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_exceptions import LanguageServerException
|
||||
from solidlsp.ls_exceptions import SolidLSPException
|
||||
from solidlsp.ls_handler import SolidLanguageServerHandler
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_utils import FileUtils, PathUtils, TextUtils
|
||||
@@ -30,10 +31,11 @@ from solidlsp.lsp_protocol_handler import lsp_types as LSPTypes
|
||||
from solidlsp.lsp_protocol_handler.lsp_constants import LSPConstants
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import Definition, DefinitionParams, LocationLink, SymbolKind
|
||||
from solidlsp.lsp_protocol_handler.server import (
|
||||
Error,
|
||||
LSPError,
|
||||
ProcessLaunchInfo,
|
||||
StringDict,
|
||||
)
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
GenericDocumentSymbol = Union[LSPTypes.DocumentSymbol, LSPTypes.SymbolInformation, ls_types.UnifiedSymbolInformation]
|
||||
|
||||
@@ -89,19 +91,34 @@ class SolidLanguageServer(ABC):
|
||||
return dirname.startswith(".")
|
||||
|
||||
@classmethod
|
||||
def ls_resources_dir(cls, mkdir: bool = True) -> str:
|
||||
def ls_resources_dir(cls, solidlsp_settings: SolidLSPSettings, mkdir: bool = True) -> str:
|
||||
"""
|
||||
Returns the directory where the language server resources are downloaded.
|
||||
This is used to store language server binaries, configuration files, etc.
|
||||
"""
|
||||
result = os.path.join(os.path.dirname(__file__), "language_servers", "static", cls.__name__)
|
||||
result = os.path.join(solidlsp_settings.ls_resources_dir, cls.__name__)
|
||||
|
||||
# Migration of previously downloaded LS resources that were downloaded to a subdir of solidlsp instead of to the user's home
|
||||
pre_migration_ls_resources_dir = os.path.join(os.path.dirname(__file__), "language_servers", "static", cls.__name__)
|
||||
if os.path.exists(pre_migration_ls_resources_dir):
|
||||
if os.path.exists(result):
|
||||
# if the directory already exists, we just remove the old resources
|
||||
shutil.rmtree(result, ignore_errors=True)
|
||||
else:
|
||||
# move old resources to the new location
|
||||
shutil.move(pre_migration_ls_resources_dir, result)
|
||||
if mkdir:
|
||||
os.makedirs(result, exist_ok=True)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, timeout: float | None = None
|
||||
cls,
|
||||
config: LanguageServerConfig,
|
||||
logger: LanguageServerLogger,
|
||||
repository_root_path: str,
|
||||
timeout: float | None = None,
|
||||
solidlsp_settings: SolidLSPSettings | None = None,
|
||||
) -> "SolidLanguageServer":
|
||||
"""
|
||||
Creates a language specific LanguageServer instance based on the given configuration, and appropriate settings for the programming language.
|
||||
@@ -116,100 +133,102 @@ class SolidLanguageServer(ABC):
|
||||
:return LanguageServer: A language specific LanguageServer instance.
|
||||
"""
|
||||
ls: SolidLanguageServer
|
||||
if solidlsp_settings is None:
|
||||
solidlsp_settings = SolidLSPSettings()
|
||||
|
||||
if config.code_language == Language.PYTHON:
|
||||
from solidlsp.language_servers.pyright_server import (
|
||||
PyrightServer,
|
||||
)
|
||||
|
||||
ls = PyrightServer(config, logger, repository_root_path)
|
||||
ls = PyrightServer(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
|
||||
elif config.code_language == Language.PYTHON_JEDI:
|
||||
from solidlsp.language_servers.jedi_server import JediServer
|
||||
|
||||
ls = JediServer(config, logger, repository_root_path)
|
||||
ls = JediServer(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
|
||||
elif config.code_language == Language.JAVA:
|
||||
from solidlsp.language_servers.eclipse_jdtls import (
|
||||
EclipseJDTLS,
|
||||
)
|
||||
|
||||
ls = EclipseJDTLS(config, logger, repository_root_path)
|
||||
ls = EclipseJDTLS(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
|
||||
|
||||
elif config.code_language == Language.KOTLIN:
|
||||
from solidlsp.language_servers.kotlin_language_server import (
|
||||
KotlinLanguageServer,
|
||||
)
|
||||
|
||||
ls = KotlinLanguageServer(config, logger, repository_root_path)
|
||||
ls = KotlinLanguageServer(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
|
||||
|
||||
elif config.code_language == Language.RUST:
|
||||
from solidlsp.language_servers.rust_analyzer import (
|
||||
RustAnalyzer,
|
||||
)
|
||||
|
||||
ls = RustAnalyzer(config, logger, repository_root_path)
|
||||
ls = RustAnalyzer(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
|
||||
|
||||
elif config.code_language == Language.CSHARP:
|
||||
from solidlsp.language_servers.csharp_language_server import CSharpLanguageServer
|
||||
|
||||
ls = CSharpLanguageServer(config, logger, repository_root_path)
|
||||
ls = CSharpLanguageServer(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
|
||||
elif config.code_language == Language.CSHARP_OMNISHARP:
|
||||
from solidlsp.language_servers.omnisharp import OmniSharp
|
||||
|
||||
ls = OmniSharp(config, logger, repository_root_path)
|
||||
ls = OmniSharp(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
|
||||
elif config.code_language == Language.TYPESCRIPT:
|
||||
from solidlsp.language_servers.typescript_language_server import (
|
||||
TypeScriptLanguageServer,
|
||||
)
|
||||
|
||||
ls = TypeScriptLanguageServer(config, logger, repository_root_path)
|
||||
ls = TypeScriptLanguageServer(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
|
||||
elif config.code_language == Language.TYPESCRIPT_VTS:
|
||||
# VTS based Language Server implementation, need to experiment to see if it improves performance
|
||||
from solidlsp.language_servers.vts_language_server import VtsLanguageServer
|
||||
|
||||
ls = VtsLanguageServer(config, logger, repository_root_path)
|
||||
ls = VtsLanguageServer(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
|
||||
elif config.code_language == Language.GO:
|
||||
from solidlsp.language_servers.gopls import Gopls
|
||||
|
||||
ls = Gopls(config, logger, repository_root_path)
|
||||
ls = Gopls(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
|
||||
|
||||
elif config.code_language == Language.RUBY:
|
||||
from solidlsp.language_servers.solargraph import Solargraph
|
||||
|
||||
ls = Solargraph(config, logger, repository_root_path)
|
||||
ls = Solargraph(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
|
||||
|
||||
elif config.code_language == Language.DART:
|
||||
from solidlsp.language_servers.dart_language_server import DartLanguageServer
|
||||
|
||||
ls = DartLanguageServer(config, logger, repository_root_path)
|
||||
ls = DartLanguageServer(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
|
||||
|
||||
elif config.code_language == Language.CPP:
|
||||
from solidlsp.language_servers.clangd_language_server import ClangdLanguageServer
|
||||
|
||||
ls = ClangdLanguageServer(config, logger, repository_root_path)
|
||||
ls = ClangdLanguageServer(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
|
||||
|
||||
elif config.code_language == Language.PHP:
|
||||
from solidlsp.language_servers.intelephense import Intelephense
|
||||
|
||||
ls = Intelephense(config, logger, repository_root_path)
|
||||
ls = Intelephense(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
|
||||
|
||||
elif config.code_language == Language.CLOJURE:
|
||||
from solidlsp.language_servers.clojure_lsp import ClojureLSP
|
||||
|
||||
ls = ClojureLSP(config, logger, repository_root_path)
|
||||
ls = ClojureLSP(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
|
||||
|
||||
elif config.code_language == Language.ELIXIR:
|
||||
from solidlsp.language_servers.elixir_tools.elixir_tools import ElixirTools
|
||||
|
||||
ls = ElixirTools(config, logger, repository_root_path)
|
||||
ls = ElixirTools(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
|
||||
|
||||
elif config.code_language == Language.TERRAFORM:
|
||||
from solidlsp.language_servers.terraform_ls import TerraformLS
|
||||
|
||||
ls = TerraformLS(config, logger, repository_root_path)
|
||||
ls = TerraformLS(config, logger, repository_root_path, solidlsp_settings=solidlsp_settings)
|
||||
|
||||
else:
|
||||
logger.log(f"Language {config.code_language} is not supported", logging.ERROR)
|
||||
raise LanguageServerException(f"Language {config.code_language} is not supported")
|
||||
raise SolidLSPException(f"Language {config.code_language} is not supported")
|
||||
|
||||
ls.set_request_timeout(timeout)
|
||||
return ls
|
||||
@@ -221,6 +240,7 @@ class SolidLanguageServer(ABC):
|
||||
repository_root_path: str,
|
||||
process_launch_info: ProcessLaunchInfo,
|
||||
language_id: str,
|
||||
solidlsp_settings: SolidLSPSettings,
|
||||
):
|
||||
"""
|
||||
Initializes a LanguageServer instance.
|
||||
@@ -235,6 +255,7 @@ class SolidLanguageServer(ABC):
|
||||
The command must pass appropriate flags to the binary, so that it runs in the stdio mode,
|
||||
as opposed to HTTP, TCP modes supported by some language servers.
|
||||
"""
|
||||
self._solidlsp_settings = solidlsp_settings
|
||||
self.logger = logger
|
||||
self.repository_root_path: str = repository_root_path
|
||||
self.logger.log(
|
||||
@@ -260,7 +281,7 @@ class SolidLanguageServer(ABC):
|
||||
if config.trace_lsp_communication:
|
||||
|
||||
def logging_fn(source: str, target: str, msg: StringDict | str):
|
||||
self.logger.log(f"LSP: {source} -> {target}: {str(msg)[:90]}...", self.logger.logger.level)
|
||||
self.logger.log(f"LSP: {source} -> {target}: {msg!s}", self.logger.logger.level)
|
||||
|
||||
else:
|
||||
logging_fn = None
|
||||
@@ -427,7 +448,7 @@ class SolidLanguageServer(ABC):
|
||||
"open_file called before Language Server started",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise LanguageServerException("Language Server not started")
|
||||
raise SolidLSPException("Language Server not started")
|
||||
|
||||
absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path))
|
||||
uri = pathlib.Path(absolute_file_path).as_uri()
|
||||
@@ -483,7 +504,7 @@ class SolidLanguageServer(ABC):
|
||||
"insert_text_at_position called before Language Server started",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise LanguageServerException("Language Server not started")
|
||||
raise SolidLSPException("Language Server not started")
|
||||
|
||||
absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path))
|
||||
uri = pathlib.Path(absolute_file_path).as_uri()
|
||||
@@ -529,7 +550,7 @@ class SolidLanguageServer(ABC):
|
||||
"insert_text_at_position called before Language Server started",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise LanguageServerException("Language Server not started")
|
||||
raise SolidLSPException("Language Server not started")
|
||||
|
||||
absolute_file_path = str(PurePath(self.repository_root_path, relative_file_path))
|
||||
uri = pathlib.Path(absolute_file_path).as_uri()
|
||||
@@ -573,7 +594,7 @@ class SolidLanguageServer(ABC):
|
||||
"request_definition called before Language Server started",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise LanguageServerException("Language Server not started")
|
||||
raise SolidLSPException("Language Server not started")
|
||||
|
||||
with self.open_file(relative_file_path):
|
||||
# sending request to the language server and waiting for response
|
||||
@@ -665,14 +686,14 @@ class SolidLanguageServer(ABC):
|
||||
"request_references called before Language Server started",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise LanguageServerException("Language Server not started")
|
||||
raise SolidLSPException("Language Server not started")
|
||||
|
||||
with self.open_file(relative_file_path):
|
||||
try:
|
||||
response = self._send_references_request(relative_file_path, line=line, column=column)
|
||||
except Exception as e:
|
||||
# Catch LSP internal error (-32603) and raise a more informative exception
|
||||
if isinstance(e, Error) and getattr(e, "code", None) == -32603:
|
||||
if isinstance(e, LSPError) and getattr(e, "code", None) == -32603:
|
||||
raise RuntimeError(
|
||||
f"LSP internal error (-32603) when requesting references for {relative_file_path}:{line}:{column}. "
|
||||
"This often occurs when requesting references for a symbol not referenced in the expected way. "
|
||||
@@ -724,7 +745,7 @@ class SolidLanguageServer(ABC):
|
||||
"request_text_document_diagnostics called before Language Server started",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise LanguageServerException("Language Server not started")
|
||||
raise SolidLSPException("Language Server not started")
|
||||
|
||||
with self.open_file(relative_file_path):
|
||||
response = self.server.send.text_document_diagnostic(
|
||||
@@ -1286,7 +1307,7 @@ class SolidLanguageServer(ABC):
|
||||
"request_referencing_symbols called before Language Server started",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise LanguageServerException("Language Server not started")
|
||||
raise SolidLSPException("Language Server not started")
|
||||
|
||||
# First, get all references to the symbol
|
||||
references = self.request_references(relative_file_path, line, column)
|
||||
@@ -1558,7 +1579,7 @@ class SolidLanguageServer(ABC):
|
||||
"request_defining_symbol called before Language Server started",
|
||||
logging.ERROR,
|
||||
)
|
||||
raise LanguageServerException("Language Server not started")
|
||||
raise SolidLSPException("Language Server not started")
|
||||
|
||||
# Get the definition location(s)
|
||||
definitions = self.request_definition(relative_file_path, line, column)
|
||||
|
||||
@@ -1,15 +1,40 @@
|
||||
"""
|
||||
This module contains the exceptions raised by the Multilspy framework.
|
||||
This module contains the exceptions raised by the framework.
|
||||
"""
|
||||
|
||||
|
||||
class LanguageServerException(Exception):
|
||||
"""
|
||||
Exceptions raised by the Multilspy framework.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str):
|
||||
class SolidLSPException(Exception):
|
||||
def __init__(self, message: str, cause: Exception | None = None):
|
||||
"""
|
||||
Initializes the exception with the given message.
|
||||
|
||||
:param message: the message describing the exception
|
||||
:param cause: the original exception that caused this exception, if any.
|
||||
For exceptions raised during request handling, this is typically
|
||||
* an LSPError for errors returned by the LSP server
|
||||
* LanguageServerTerminatedException for errors due to the language server having terminated.
|
||||
"""
|
||||
self.cause = cause
|
||||
super().__init__(message)
|
||||
|
||||
def is_language_server_terminated(self):
|
||||
"""
|
||||
:return: True if the exception is caused by the language server having terminated as indicated
|
||||
by the causing exception being an instance of LanguageServerTerminatedException.
|
||||
"""
|
||||
from .ls_handler import LanguageServerTerminatedException
|
||||
|
||||
return isinstance(self.cause, LanguageServerTerminatedException)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
Returns a string representation of the exception.
|
||||
"""
|
||||
s = super().__str__()
|
||||
if self.cause:
|
||||
if "\n" in s:
|
||||
s += "\n"
|
||||
else:
|
||||
s += " "
|
||||
s += f"(caused by {self.cause})"
|
||||
return s
|
||||
+101
-71
@@ -12,14 +12,15 @@ from queue import Queue
|
||||
from typing import Any
|
||||
|
||||
import psutil
|
||||
from sensai.util.string import ToStringMixin
|
||||
|
||||
from solidlsp.ls_exceptions import LanguageServerException
|
||||
from solidlsp.ls_exceptions import SolidLSPException
|
||||
from solidlsp.ls_request import LanguageServerRequest
|
||||
from solidlsp.lsp_protocol_handler.lsp_requests import LspNotification
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import ErrorCodes
|
||||
from solidlsp.lsp_protocol_handler.server import (
|
||||
ENCODING,
|
||||
Error,
|
||||
LSPError,
|
||||
MessageType,
|
||||
PayloadLike,
|
||||
ProcessLaunchInfo,
|
||||
@@ -35,23 +36,50 @@ from solidlsp.lsp_protocol_handler.server import (
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Request:
|
||||
class LanguageServerTerminatedException(Exception):
|
||||
"""
|
||||
Exception raised when the language server process has terminated unexpectedly.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, cause: Exception | None = None) -> None:
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
self.cause = cause
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"LanguageServerTerminatedException: {self.message}" + (f"; Cause: {self.cause}" if self.cause else "")
|
||||
|
||||
|
||||
class Request(ToStringMixin):
|
||||
|
||||
@dataclass
|
||||
class Result:
|
||||
payload: PayloadLike | None = None
|
||||
error: Error | None = None
|
||||
error: Exception | None = None
|
||||
|
||||
def is_error(self) -> bool:
|
||||
return self.error is not None
|
||||
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, request_id: int, method: str) -> None:
|
||||
self._request_id = request_id
|
||||
self._method = method
|
||||
self._status = "pending"
|
||||
self._result_queue = Queue()
|
||||
|
||||
def _tostring_includes(self) -> list[str]:
|
||||
return ["_request_id", "_status", "_method"]
|
||||
|
||||
def on_result(self, params: PayloadLike) -> None:
|
||||
self._status = "completed"
|
||||
self._result_queue.put(Request.Result(payload=params))
|
||||
|
||||
def on_error(self, err: Error) -> None:
|
||||
def on_error(self, err: Exception) -> None:
|
||||
"""
|
||||
:param err: the error that occurred while processing the request (typically an LSPError
|
||||
for errors returned by the LS or LanguageServerTerminatedException if the error
|
||||
is due to the language server process terminating unexpectedly).
|
||||
"""
|
||||
self._status = "error"
|
||||
self._result_queue.put(Request.Result(error=err))
|
||||
|
||||
def get_result(self, timeout: float | None = None) -> Result:
|
||||
@@ -75,10 +103,8 @@ class SolidLanguageServerHandler:
|
||||
notify: A LspNotification object that can be used to send notifications to the server.
|
||||
cmd: A string that represents the command to launch the language server process.
|
||||
process: A subprocess.Popen object that represents the language server process.
|
||||
_received_shutdown: A boolean flag that indicates whether the client has received
|
||||
a shutdown request from the server.
|
||||
request_id: An integer that represents the next available request id for the client.
|
||||
_response_handlers: A dictionary that maps request ids to Request objects that
|
||||
_pending_requests: A dictionary that maps request ids to Request objects that
|
||||
store the results or errors of the requests.
|
||||
on_request_handlers: A dictionary that maps method names to callback functions
|
||||
that handle requests from the server.
|
||||
@@ -104,15 +130,15 @@ class SolidLanguageServerHandler:
|
||||
start_independent_lsp_process=True,
|
||||
request_timeout: float | None = None,
|
||||
) -> None:
|
||||
self.send = LanguageServerRequest(self.send_request)
|
||||
self.send = LanguageServerRequest(self)
|
||||
self.notify = LspNotification(self.send_notification)
|
||||
|
||||
self.process_launch_info = process_launch_info
|
||||
self.process = None
|
||||
self._received_shutdown = False
|
||||
self.process: subprocess.Popen | None = None
|
||||
self._is_shutting_down = False
|
||||
|
||||
self.request_id = 1
|
||||
self._response_handlers: dict[Any, Request] = {}
|
||||
self._pending_requests: dict[Any, Request] = {}
|
||||
self.on_request_handlers = {}
|
||||
self.on_notification_handlers = {}
|
||||
self.logger = logger
|
||||
@@ -176,12 +202,12 @@ class SolidLanguageServerHandler:
|
||||
|
||||
# start threads to read stdout and stderr of the process
|
||||
threading.Thread(
|
||||
target=self.run_forever,
|
||||
target=self._read_ls_process_stdout,
|
||||
name="LSP-stdout-reader",
|
||||
daemon=True,
|
||||
).start()
|
||||
threading.Thread(
|
||||
target=self.run_forever_stderr,
|
||||
target=self._read_ls_process_stderr,
|
||||
name="LSP-stderr-reader",
|
||||
daemon=True,
|
||||
).start()
|
||||
@@ -225,21 +251,6 @@ class SolidLanguageServerHandler:
|
||||
# First try to terminate the process tree gracefully
|
||||
self._signal_process_tree(process, terminate=True)
|
||||
|
||||
# TODO
|
||||
"""
|
||||
# Wait for the process to exit (with timeout)
|
||||
try:
|
||||
asyncio.wait_for(process.wait(), timeout=10)
|
||||
except (asyncio.TimeoutError, Exception):
|
||||
# If termination failed, forcefully kill the process tree
|
||||
self._signal_process_tree(process, terminate=False)
|
||||
try:
|
||||
# Give it one more chance to exit
|
||||
await asyncio.wait_for(process.wait(), timeout=2)
|
||||
except Exception:
|
||||
pass
|
||||
"""
|
||||
|
||||
def _signal_process_tree(self, process, terminate=True):
|
||||
"""Send signal (terminate or kill) to the process and all its children."""
|
||||
signal_method = "terminate" if terminate else "kill"
|
||||
@@ -276,21 +287,13 @@ class SolidLanguageServerHandler:
|
||||
"""
|
||||
Perform the shutdown sequence for the client, including sending the shutdown request to the server and notifying it of exit
|
||||
"""
|
||||
self._is_shutting_down = True
|
||||
self._log("Sending shutdown request to server")
|
||||
self.send.shutdown()
|
||||
self._log("Received shutdown response from server")
|
||||
self._received_shutdown = True
|
||||
self._log("Sending exit notification to server")
|
||||
self.notify.exit()
|
||||
self._log("Sent exit notification to server")
|
||||
# TODO
|
||||
"""
|
||||
if self.process and self.process.stdout:
|
||||
self.process.stdout.set_exception(StopLoopException())
|
||||
# This yields the control to the event loop to allow the exception to be handled
|
||||
# in the run_forever and run_forever_stderr methods
|
||||
await asyncio.sleep(0)
|
||||
"""
|
||||
|
||||
def _log(self, message: str | StringDict) -> None:
|
||||
"""
|
||||
@@ -302,29 +305,30 @@ class SolidLanguageServerHandler:
|
||||
@staticmethod
|
||||
def _read_bytes_from_process(process, stream, num_bytes):
|
||||
"""Read exactly num_bytes from process stdout"""
|
||||
if process.poll() is not None:
|
||||
# Process has terminated, check if we can still read
|
||||
pass
|
||||
|
||||
data = b""
|
||||
while len(data) < num_bytes:
|
||||
chunk = stream.read(num_bytes - len(data))
|
||||
if not chunk:
|
||||
if process.poll() is not None:
|
||||
raise EOFError(f"Process terminated. Expected {num_bytes} bytes, got {len(data)}")
|
||||
# Process still running but no data available yet
|
||||
time.sleep(0.01) # Small delay
|
||||
raise LanguageServerTerminatedException(
|
||||
f"Process terminated while trying to read response (read {num_bytes} of {len(data)} bytes before termination)"
|
||||
)
|
||||
# Process still running but no data available yet, retry after a short delay
|
||||
time.sleep(0.01)
|
||||
continue
|
||||
data += chunk
|
||||
return data
|
||||
|
||||
def run_forever(self) -> bool:
|
||||
def _read_ls_process_stdout(self) -> None:
|
||||
"""
|
||||
Continuously read from the language server process stdout and handle the messages
|
||||
invoking the registered response and notification handlers
|
||||
"""
|
||||
exception: Exception | None = None
|
||||
try:
|
||||
while self.process and self.process.stdout and self.process.stdout.readable():
|
||||
while self.process and self.process.stdout:
|
||||
if self.process.poll() is not None: # process has terminated
|
||||
break
|
||||
line = self.process.stdout.readline()
|
||||
if not line:
|
||||
continue
|
||||
@@ -341,22 +345,38 @@ class SolidLanguageServerHandler:
|
||||
body = self._read_bytes_from_process(self.process, self.process.stdout, num_bytes)
|
||||
|
||||
self._handle_body(body)
|
||||
except (BrokenPipeError, ConnectionResetError):
|
||||
pass
|
||||
return self._received_shutdown
|
||||
except LanguageServerTerminatedException as e:
|
||||
exception = e
|
||||
except (BrokenPipeError, ConnectionResetError) as e:
|
||||
exception = LanguageServerTerminatedException("Language server process terminated while reading stdout", cause=e)
|
||||
except Exception as e:
|
||||
exception = LanguageServerTerminatedException("Unexpected error while reading stdout from language server process", cause=e)
|
||||
log.info("Language server stdout reader thread has terminated")
|
||||
if not self._is_shutting_down:
|
||||
if exception is None:
|
||||
exception = LanguageServerTerminatedException("Language server stdout read process terminated unexpectedly")
|
||||
log.error(str(exception))
|
||||
self._cancel_pending_requests(exception)
|
||||
|
||||
def run_forever_stderr(self) -> None:
|
||||
def _read_ls_process_stderr(self) -> None:
|
||||
"""
|
||||
Continuously read from the language server process stderr and log the messages
|
||||
"""
|
||||
try:
|
||||
while self.process and self.process.stderr and self.process.stderr.readable():
|
||||
while self.process and self.process.stderr:
|
||||
if self.process.poll() is not None:
|
||||
# process has terminated
|
||||
break
|
||||
line = self.process.stderr.readline()
|
||||
if not line:
|
||||
continue
|
||||
self._log("LSP stderr: " + line.decode(ENCODING, errors="replace"))
|
||||
except (BrokenPipeError, ConnectionResetError):
|
||||
pass
|
||||
log.error(line.decode(ENCODING, errors="replace"))
|
||||
except Exception as e:
|
||||
log.error("Error while reading stderr from language server process: %s", e, exc_info=e)
|
||||
if not self._is_shutting_down:
|
||||
log.error("Language server stderr reader thread terminated unexpectedly")
|
||||
else:
|
||||
log.info("Language server stderr reader thread has terminated")
|
||||
|
||||
def _handle_body(self, body: bytes) -> None:
|
||||
"""
|
||||
@@ -402,37 +422,47 @@ class SolidLanguageServerHandler:
|
||||
"""
|
||||
self._send_payload(make_response(request_id, params))
|
||||
|
||||
def send_error_response(self, request_id: Any, err: Error) -> None:
|
||||
def send_error_response(self, request_id: Any, err: LSPError) -> None:
|
||||
"""
|
||||
Send error response to the given request id to the server with the given error
|
||||
"""
|
||||
# Use lock to prevent race conditions on tasks and task_counter
|
||||
self._send_payload(make_error_response(request_id, err))
|
||||
|
||||
def _cancel_pending_requests(self, exception: Exception) -> None:
|
||||
"""
|
||||
Cancel all pending requests by setting their results to an error
|
||||
"""
|
||||
with self._response_handlers_lock:
|
||||
log.info("Cancelling %d pending language server requests", len(self._pending_requests))
|
||||
for request in self._pending_requests.values():
|
||||
log.info("Cancelling %s", request)
|
||||
request.on_error(exception)
|
||||
self._pending_requests.clear()
|
||||
|
||||
def send_request(self, method: str, params: dict | None = None) -> PayloadLike:
|
||||
"""
|
||||
Send request to the server, register the request id, and wait for the response
|
||||
"""
|
||||
request = Request()
|
||||
|
||||
# Use lock to prevent race conditions on request_id and _response_handlers
|
||||
with self._request_id_lock:
|
||||
request_id = self.request_id
|
||||
self.request_id += 1
|
||||
|
||||
request = Request(request_id=request_id, method=method)
|
||||
log.debug("Starting: %s", request)
|
||||
|
||||
with self._response_handlers_lock:
|
||||
self._response_handlers[request_id] = request
|
||||
self._pending_requests[request_id] = request
|
||||
|
||||
self._send_payload(make_request(method, request_id, params))
|
||||
|
||||
self._log(f"Waiting for response to request {method} with params:\n{params}")
|
||||
result = request.get_result(timeout=self._request_timeout)
|
||||
log.debug("Completed: %s", request)
|
||||
|
||||
self._log("Processing result")
|
||||
if result.is_error():
|
||||
raise LanguageServerException(
|
||||
f"Could not process request {method} with params:\n{params}.\n Language server error: {result.error}"
|
||||
) from result.error
|
||||
raise SolidLSPException(f"Error processing request {method} with params:\n{params}", cause=result.error) from result.error
|
||||
|
||||
self._log(f"Returning non-error result, which is:\n{result.payload}")
|
||||
return result.payload
|
||||
@@ -474,14 +504,14 @@ class SolidLanguageServerHandler:
|
||||
Handle the response received from the server for a request, using the id to determine the request
|
||||
"""
|
||||
with self._response_handlers_lock:
|
||||
request = self._response_handlers.pop(response["id"])
|
||||
request = self._pending_requests.pop(response["id"])
|
||||
|
||||
if "result" in response and "error" not in response:
|
||||
request.on_result(response["result"])
|
||||
elif "result" not in response and "error" in response:
|
||||
request.on_error(Error.from_lsp(response["error"]))
|
||||
request.on_error(LSPError.from_lsp(response["error"]))
|
||||
else:
|
||||
request.on_error(Error(ErrorCodes.InvalidRequest, ""))
|
||||
request.on_error(LSPError(ErrorCodes.InvalidRequest, ""))
|
||||
|
||||
def _request_handler(self, response: StringDict) -> None:
|
||||
"""
|
||||
@@ -494,7 +524,7 @@ class SolidLanguageServerHandler:
|
||||
if not handler:
|
||||
self.send_error_response(
|
||||
request_id,
|
||||
Error(
|
||||
LSPError(
|
||||
ErrorCodes.MethodNotFound,
|
||||
f"method '{method}' not handled on client.",
|
||||
),
|
||||
@@ -502,10 +532,10 @@ class SolidLanguageServerHandler:
|
||||
return
|
||||
try:
|
||||
self.send_response(request_id, handler(params))
|
||||
except Error as ex:
|
||||
except LSPError as ex:
|
||||
self.send_error_response(request_id, ex)
|
||||
except Exception as ex:
|
||||
self.send_error_response(request_id, Error(ErrorCodes.InternalError, str(ex)))
|
||||
self.send_error_response(request_id, LSPError(ErrorCodes.InternalError, str(ex)))
|
||||
|
||||
def _notification_handler(self, response: StringDict) -> None:
|
||||
"""
|
||||
@@ -522,7 +552,7 @@ class SolidLanguageServerHandler:
|
||||
except asyncio.CancelledError:
|
||||
return
|
||||
except Exception as ex:
|
||||
if (not self._received_shutdown) and self.logger:
|
||||
if (not self._is_shutting_down) and self.logger:
|
||||
self.logger(
|
||||
"client",
|
||||
"logger",
|
||||
|
||||
+60
-54
@@ -1,11 +1,17 @@
|
||||
from typing import Union
|
||||
from typing import TYPE_CHECKING, Any, Union
|
||||
|
||||
from solidlsp.lsp_protocol_handler import lsp_types
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .ls_handler import SolidLanguageServerHandler
|
||||
|
||||
|
||||
class LanguageServerRequest:
|
||||
def __init__(self, send_request):
|
||||
self.send_request = send_request
|
||||
def __init__(self, handler: "SolidLanguageServerHandler"):
|
||||
self.handler = handler
|
||||
|
||||
def _send_request(self, method: str, params: Any | None = None) -> Any:
|
||||
return self.handler.send_request(method, params)
|
||||
|
||||
def implementation(self, params: lsp_types.ImplementationParams) -> Union["lsp_types.Definition", list["lsp_types.LocationLink"], None]:
|
||||
"""A request to resolve the implementation locations of a symbol at a given text
|
||||
@@ -13,7 +19,7 @@ class LanguageServerRequest:
|
||||
(#TextDocumentPositionParams) the response is of type {@link Definition} or a
|
||||
Thenable that resolves to such.
|
||||
"""
|
||||
return self.send_request("textDocument/implementation", params)
|
||||
return self._send_request("textDocument/implementation", params)
|
||||
|
||||
def type_definition(
|
||||
self, params: lsp_types.TypeDefinitionParams
|
||||
@@ -23,7 +29,7 @@ class LanguageServerRequest:
|
||||
(#TextDocumentPositionParams) the response is of type {@link Definition} or a
|
||||
Thenable that resolves to such.
|
||||
"""
|
||||
return self.send_request("textDocument/typeDefinition", params)
|
||||
return self._send_request("textDocument/typeDefinition", params)
|
||||
|
||||
def document_color(self, params: lsp_types.DocumentColorParams) -> list["lsp_types.ColorInformation"]:
|
||||
"""A request to list all color symbols found in a given text document. The request's
|
||||
@@ -31,7 +37,7 @@ class LanguageServerRequest:
|
||||
response is of type {@link ColorInformation ColorInformation[]} or a Thenable
|
||||
that resolves to such.
|
||||
"""
|
||||
return self.send_request("textDocument/documentColor", params)
|
||||
return self._send_request("textDocument/documentColor", params)
|
||||
|
||||
def color_presentation(self, params: lsp_types.ColorPresentationParams) -> list["lsp_types.ColorPresentation"]:
|
||||
"""A request to list all presentation for a color. The request's
|
||||
@@ -39,7 +45,7 @@ class LanguageServerRequest:
|
||||
response is of type {@link ColorInformation ColorInformation[]} or a Thenable
|
||||
that resolves to such.
|
||||
"""
|
||||
return self.send_request("textDocument/colorPresentation", params)
|
||||
return self._send_request("textDocument/colorPresentation", params)
|
||||
|
||||
def folding_range(self, params: lsp_types.FoldingRangeParams) -> list["lsp_types.FoldingRange"] | None:
|
||||
"""A request to provide folding ranges in a document. The request's
|
||||
@@ -47,7 +53,7 @@ class LanguageServerRequest:
|
||||
response is of type {@link FoldingRangeList} or a Thenable
|
||||
that resolves to such.
|
||||
"""
|
||||
return self.send_request("textDocument/foldingRange", params)
|
||||
return self._send_request("textDocument/foldingRange", params)
|
||||
|
||||
def declaration(self, params: lsp_types.DeclarationParams) -> Union["lsp_types.Declaration", list["lsp_types.LocationLink"], None]:
|
||||
"""A request to resolve the type definition locations of a symbol at a given text
|
||||
@@ -56,7 +62,7 @@ class LanguageServerRequest:
|
||||
or a typed array of {@link DeclarationLink} or a Thenable that resolves
|
||||
to such.
|
||||
"""
|
||||
return self.send_request("textDocument/declaration", params)
|
||||
return self._send_request("textDocument/declaration", params)
|
||||
|
||||
def selection_range(self, params: lsp_types.SelectionRangeParams) -> list["lsp_types.SelectionRange"] | None:
|
||||
"""A request to provide selection ranges in a document. The request's
|
||||
@@ -64,7 +70,7 @@ class LanguageServerRequest:
|
||||
response is of type {@link SelectionRange SelectionRange[]} or a Thenable
|
||||
that resolves to such.
|
||||
"""
|
||||
return self.send_request("textDocument/selectionRange", params)
|
||||
return self._send_request("textDocument/selectionRange", params)
|
||||
|
||||
def prepare_call_hierarchy(self, params: lsp_types.CallHierarchyPrepareParams) -> list["lsp_types.CallHierarchyItem"] | None:
|
||||
"""A request to result a `CallHierarchyItem` in a document at a given position.
|
||||
@@ -72,42 +78,42 @@ class LanguageServerRequest:
|
||||
|
||||
@since 3.16.0
|
||||
"""
|
||||
return self.send_request("textDocument/prepareCallHierarchy", params)
|
||||
return self._send_request("textDocument/prepareCallHierarchy", params)
|
||||
|
||||
def incoming_calls(self, params: lsp_types.CallHierarchyIncomingCallsParams) -> list["lsp_types.CallHierarchyIncomingCall"] | None:
|
||||
"""A request to resolve the incoming calls for a given `CallHierarchyItem`.
|
||||
|
||||
@since 3.16.0
|
||||
"""
|
||||
return self.send_request("callHierarchy/incomingCalls", params)
|
||||
return self._send_request("callHierarchy/incomingCalls", params)
|
||||
|
||||
def outgoing_calls(self, params: lsp_types.CallHierarchyOutgoingCallsParams) -> list["lsp_types.CallHierarchyOutgoingCall"] | None:
|
||||
"""A request to resolve the outgoing calls for a given `CallHierarchyItem`.
|
||||
|
||||
@since 3.16.0
|
||||
"""
|
||||
return self.send_request("callHierarchy/outgoingCalls", params)
|
||||
return self._send_request("callHierarchy/outgoingCalls", params)
|
||||
|
||||
def semantic_tokens_full(self, params: lsp_types.SemanticTokensParams) -> Union["lsp_types.SemanticTokens", None]:
|
||||
"""@since 3.16.0"""
|
||||
return self.send_request("textDocument/semanticTokens/full", params)
|
||||
return self._send_request("textDocument/semanticTokens/full", params)
|
||||
|
||||
def semantic_tokens_delta(
|
||||
self, params: lsp_types.SemanticTokensDeltaParams
|
||||
) -> Union["lsp_types.SemanticTokens", "lsp_types.SemanticTokensDelta", None]:
|
||||
"""@since 3.16.0"""
|
||||
return self.send_request("textDocument/semanticTokens/full/delta", params)
|
||||
return self._send_request("textDocument/semanticTokens/full/delta", params)
|
||||
|
||||
def semantic_tokens_range(self, params: lsp_types.SemanticTokensRangeParams) -> Union["lsp_types.SemanticTokens", None]:
|
||||
"""@since 3.16.0"""
|
||||
return self.send_request("textDocument/semanticTokens/range", params)
|
||||
return self._send_request("textDocument/semanticTokens/range", params)
|
||||
|
||||
def linked_editing_range(self, params: lsp_types.LinkedEditingRangeParams) -> Union["lsp_types.LinkedEditingRanges", None]:
|
||||
"""A request to provide ranges that can be edited together.
|
||||
|
||||
@since 3.16.0
|
||||
"""
|
||||
return self.send_request("textDocument/linkedEditingRange", params)
|
||||
return self._send_request("textDocument/linkedEditingRange", params)
|
||||
|
||||
def will_create_files(self, params: lsp_types.CreateFilesParams) -> Union["lsp_types.WorkspaceEdit", None]:
|
||||
"""The will create files request is sent from the client to the server before files are actually
|
||||
@@ -115,7 +121,7 @@ class LanguageServerRequest:
|
||||
|
||||
@since 3.16.0
|
||||
"""
|
||||
return self.send_request("workspace/willCreateFiles", params)
|
||||
return self._send_request("workspace/willCreateFiles", params)
|
||||
|
||||
def will_rename_files(self, params: lsp_types.RenameFilesParams) -> Union["lsp_types.WorkspaceEdit", None]:
|
||||
"""The will rename files request is sent from the client to the server before files are actually
|
||||
@@ -123,7 +129,7 @@ class LanguageServerRequest:
|
||||
|
||||
@since 3.16.0
|
||||
"""
|
||||
return self.send_request("workspace/willRenameFiles", params)
|
||||
return self._send_request("workspace/willRenameFiles", params)
|
||||
|
||||
def will_delete_files(self, params: lsp_types.DeleteFilesParams) -> Union["lsp_types.WorkspaceEdit", None]:
|
||||
"""The did delete files notification is sent from the client to the server when
|
||||
@@ -131,14 +137,14 @@ class LanguageServerRequest:
|
||||
|
||||
@since 3.16.0
|
||||
"""
|
||||
return self.send_request("workspace/willDeleteFiles", params)
|
||||
return self._send_request("workspace/willDeleteFiles", params)
|
||||
|
||||
def moniker(self, params: lsp_types.MonikerParams) -> list["lsp_types.Moniker"] | None:
|
||||
"""A request to get the moniker of a symbol at a given text document position.
|
||||
The request parameter is of type {@link TextDocumentPositionParams}.
|
||||
The response is of type {@link Moniker Moniker[]} or `null`.
|
||||
"""
|
||||
return self.send_request("textDocument/moniker", params)
|
||||
return self._send_request("textDocument/moniker", params)
|
||||
|
||||
def prepare_type_hierarchy(self, params: lsp_types.TypeHierarchyPrepareParams) -> list["lsp_types.TypeHierarchyItem"] | None:
|
||||
"""A request to result a `TypeHierarchyItem` in a document at a given position.
|
||||
@@ -146,21 +152,21 @@ class LanguageServerRequest:
|
||||
|
||||
@since 3.17.0
|
||||
"""
|
||||
return self.send_request("textDocument/prepareTypeHierarchy", params)
|
||||
return self._send_request("textDocument/prepareTypeHierarchy", params)
|
||||
|
||||
def type_hierarchy_supertypes(self, params: lsp_types.TypeHierarchySupertypesParams) -> list["lsp_types.TypeHierarchyItem"] | None:
|
||||
"""A request to resolve the supertypes for a given `TypeHierarchyItem`.
|
||||
|
||||
@since 3.17.0
|
||||
"""
|
||||
return self.send_request("typeHierarchy/supertypes", params)
|
||||
return self._send_request("typeHierarchy/supertypes", params)
|
||||
|
||||
def type_hierarchy_subtypes(self, params: lsp_types.TypeHierarchySubtypesParams) -> list["lsp_types.TypeHierarchyItem"] | None:
|
||||
"""A request to resolve the subtypes for a given `TypeHierarchyItem`.
|
||||
|
||||
@since 3.17.0
|
||||
"""
|
||||
return self.send_request("typeHierarchy/subtypes", params)
|
||||
return self._send_request("typeHierarchy/subtypes", params)
|
||||
|
||||
def inline_value(self, params: lsp_types.InlineValueParams) -> list["lsp_types.InlineValue"] | None:
|
||||
"""A request to provide inline values in a document. The request's parameter is of
|
||||
@@ -169,7 +175,7 @@ class LanguageServerRequest:
|
||||
|
||||
@since 3.17.0
|
||||
"""
|
||||
return self.send_request("textDocument/inlineValue", params)
|
||||
return self._send_request("textDocument/inlineValue", params)
|
||||
|
||||
def inlay_hint(self, params: lsp_types.InlayHintParams) -> list["lsp_types.InlayHint"] | None:
|
||||
"""A request to provide inlay hints in a document. The request's parameter is of
|
||||
@@ -178,7 +184,7 @@ class LanguageServerRequest:
|
||||
|
||||
@since 3.17.0
|
||||
"""
|
||||
return self.send_request("textDocument/inlayHint", params)
|
||||
return self._send_request("textDocument/inlayHint", params)
|
||||
|
||||
def resolve_inlay_hint(self, params: lsp_types.InlayHint) -> "lsp_types.InlayHint":
|
||||
"""A request to resolve additional properties for an inlay hint.
|
||||
@@ -187,21 +193,21 @@ class LanguageServerRequest:
|
||||
|
||||
@since 3.17.0
|
||||
"""
|
||||
return self.send_request("inlayHint/resolve", params)
|
||||
return self._send_request("inlayHint/resolve", params)
|
||||
|
||||
def text_document_diagnostic(self, params: lsp_types.DocumentDiagnosticParams) -> "lsp_types.DocumentDiagnosticReport":
|
||||
"""The document diagnostic request definition.
|
||||
|
||||
@since 3.17.0
|
||||
"""
|
||||
return self.send_request("textDocument/diagnostic", params)
|
||||
return self._send_request("textDocument/diagnostic", params)
|
||||
|
||||
def workspace_diagnostic(self, params: lsp_types.WorkspaceDiagnosticParams) -> "lsp_types.WorkspaceDiagnosticReport":
|
||||
"""The workspace diagnostic request definition.
|
||||
|
||||
@since 3.17.0
|
||||
"""
|
||||
return self.send_request("workspace/diagnostic", params)
|
||||
return self._send_request("workspace/diagnostic", params)
|
||||
|
||||
def initialize(self, params: lsp_types.InitializeParams) -> "lsp_types.InitializeResult":
|
||||
"""The initialize request is sent from the client to the server.
|
||||
@@ -210,7 +216,7 @@ class LanguageServerRequest:
|
||||
the response if of type {@link InitializeResult} of a Thenable that
|
||||
resolves to such.
|
||||
"""
|
||||
return self.send_request("initialize", params)
|
||||
return self._send_request("initialize", params)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""A shutdown request is sent from the client to the server.
|
||||
@@ -218,7 +224,7 @@ class LanguageServerRequest:
|
||||
server. The only notification that is sent after a shutdown request
|
||||
is the exit event.
|
||||
"""
|
||||
return self.send_request("shutdown")
|
||||
return self._send_request("shutdown")
|
||||
|
||||
def will_save_wait_until(self, params: lsp_types.WillSaveTextDocumentParams) -> list["lsp_types.TextEdit"] | None:
|
||||
"""A document will save request is sent from the client to the server before
|
||||
@@ -228,7 +234,7 @@ class LanguageServerRequest:
|
||||
server constantly fails on this request. This is done to keep the save fast and
|
||||
reliable.
|
||||
"""
|
||||
return self.send_request("textDocument/willSaveWaitUntil", params)
|
||||
return self._send_request("textDocument/willSaveWaitUntil", params)
|
||||
|
||||
def completion(self, params: lsp_types.CompletionParams) -> Union[list["lsp_types.CompletionItem"], "lsp_types.CompletionList", None]:
|
||||
"""Request to request completion at a given text document position. The request's
|
||||
@@ -241,24 +247,24 @@ class LanguageServerRequest:
|
||||
request. However, properties that are needed for the initial sorting and filtering, like `sortText`,
|
||||
`filterText`, `insertText`, and `textEdit`, must not be changed during resolve.
|
||||
"""
|
||||
return self.send_request("textDocument/completion", params)
|
||||
return self._send_request("textDocument/completion", params)
|
||||
|
||||
def resolve_completion_item(self, params: lsp_types.CompletionItem) -> "lsp_types.CompletionItem":
|
||||
"""Request to resolve additional information for a given completion item.The request's
|
||||
parameter is of type {@link CompletionItem} the response
|
||||
is of type {@link CompletionItem} or a Thenable that resolves to such.
|
||||
"""
|
||||
return self.send_request("completionItem/resolve", params)
|
||||
return self._send_request("completionItem/resolve", params)
|
||||
|
||||
def hover(self, params: lsp_types.HoverParams) -> Union["lsp_types.Hover", None]:
|
||||
"""Request to request hover information at a given text document position. The request's
|
||||
parameter is of type {@link TextDocumentPosition} the response is of
|
||||
type {@link Hover} or a Thenable that resolves to such.
|
||||
"""
|
||||
return self.send_request("textDocument/hover", params)
|
||||
return self._send_request("textDocument/hover", params)
|
||||
|
||||
def signature_help(self, params: lsp_types.SignatureHelpParams) -> Union["lsp_types.SignatureHelp", None]:
|
||||
return self.send_request("textDocument/signatureHelp", params)
|
||||
return self._send_request("textDocument/signatureHelp", params)
|
||||
|
||||
def definition(self, params: lsp_types.DefinitionParams) -> Union["lsp_types.Definition", list["lsp_types.LocationLink"], None]:
|
||||
"""A request to resolve the definition location of a symbol at a given text
|
||||
@@ -267,7 +273,7 @@ class LanguageServerRequest:
|
||||
or a typed array of {@link DefinitionLink} or a Thenable that resolves
|
||||
to such.
|
||||
"""
|
||||
return self.send_request("textDocument/definition", params)
|
||||
return self._send_request("textDocument/definition", params)
|
||||
|
||||
def references(self, params: lsp_types.ReferenceParams) -> list["lsp_types.Location"] | None:
|
||||
"""A request to resolve project-wide references for the symbol denoted
|
||||
@@ -275,7 +281,7 @@ class LanguageServerRequest:
|
||||
type {@link ReferenceParams} the response is of type
|
||||
{@link Location Location[]} or a Thenable that resolves to such.
|
||||
"""
|
||||
return self.send_request("textDocument/references", params)
|
||||
return self._send_request("textDocument/references", params)
|
||||
|
||||
def document_highlight(self, params: lsp_types.DocumentHighlightParams) -> list["lsp_types.DocumentHighlight"] | None:
|
||||
"""Request to resolve a {@link DocumentHighlight} for a given
|
||||
@@ -283,7 +289,7 @@ class LanguageServerRequest:
|
||||
(#TextDocumentPosition) the request response is of type [DocumentHighlight[]]
|
||||
(#DocumentHighlight) or a Thenable that resolves to such.
|
||||
"""
|
||||
return self.send_request("textDocument/documentHighlight", params)
|
||||
return self._send_request("textDocument/documentHighlight", params)
|
||||
|
||||
def document_symbol(
|
||||
self, params: lsp_types.DocumentSymbolParams
|
||||
@@ -293,18 +299,18 @@ class LanguageServerRequest:
|
||||
response is of type {@link SymbolInformation SymbolInformation[]} or a Thenable
|
||||
that resolves to such.
|
||||
"""
|
||||
return self.send_request("textDocument/documentSymbol", params)
|
||||
return self._send_request("textDocument/documentSymbol", params)
|
||||
|
||||
def code_action(self, params: lsp_types.CodeActionParams) -> list[Union["lsp_types.Command", "lsp_types.CodeAction"]] | None:
|
||||
"""A request to provide commands for the given text document and range."""
|
||||
return self.send_request("textDocument/codeAction", params)
|
||||
return self._send_request("textDocument/codeAction", params)
|
||||
|
||||
def resolve_code_action(self, params: lsp_types.CodeAction) -> "lsp_types.CodeAction":
|
||||
"""Request to resolve additional information for a given code action.The request's
|
||||
parameter is of type {@link CodeAction} the response
|
||||
is of type {@link CodeAction} or a Thenable that resolves to such.
|
||||
"""
|
||||
return self.send_request("codeAction/resolve", params)
|
||||
return self._send_request("codeAction/resolve", params)
|
||||
|
||||
def workspace_symbol(
|
||||
self, params: lsp_types.WorkspaceSymbolParams
|
||||
@@ -318,7 +324,7 @@ class LanguageServerRequest:
|
||||
need to advertise support for WorkspaceSymbols via the client capability
|
||||
`workspace.symbol.resolveSupport`.
|
||||
"""
|
||||
return self.send_request("workspace/symbol", params)
|
||||
return self._send_request("workspace/symbol", params)
|
||||
|
||||
def resolve_workspace_symbol(self, params: lsp_types.WorkspaceSymbol) -> "lsp_types.WorkspaceSymbol":
|
||||
"""A request to resolve the range inside the workspace
|
||||
@@ -326,52 +332,52 @@ class LanguageServerRequest:
|
||||
|
||||
@since 3.17.0
|
||||
"""
|
||||
return self.send_request("workspaceSymbol/resolve", params)
|
||||
return self._send_request("workspaceSymbol/resolve", params)
|
||||
|
||||
def code_lens(self, params: lsp_types.CodeLensParams) -> list["lsp_types.CodeLens"] | None:
|
||||
"""A request to provide code lens for the given text document."""
|
||||
return self.send_request("textDocument/codeLens", params)
|
||||
return self._send_request("textDocument/codeLens", params)
|
||||
|
||||
def resolve_code_lens(self, params: lsp_types.CodeLens) -> "lsp_types.CodeLens":
|
||||
"""A request to resolve a command for a given code lens."""
|
||||
return self.send_request("codeLens/resolve", params)
|
||||
return self._send_request("codeLens/resolve", params)
|
||||
|
||||
def document_link(self, params: lsp_types.DocumentLinkParams) -> list["lsp_types.DocumentLink"] | None:
|
||||
"""A request to provide document links"""
|
||||
return self.send_request("textDocument/documentLink", params)
|
||||
return self._send_request("textDocument/documentLink", params)
|
||||
|
||||
def resolve_document_link(self, params: lsp_types.DocumentLink) -> "lsp_types.DocumentLink":
|
||||
"""Request to resolve additional information for a given document link. The request's
|
||||
parameter is of type {@link DocumentLink} the response
|
||||
is of type {@link DocumentLink} or a Thenable that resolves to such.
|
||||
"""
|
||||
return self.send_request("documentLink/resolve", params)
|
||||
return self._send_request("documentLink/resolve", params)
|
||||
|
||||
def formatting(self, params: lsp_types.DocumentFormattingParams) -> list["lsp_types.TextEdit"] | None:
|
||||
"""A request to to format a whole document."""
|
||||
return self.send_request("textDocument/formatting", params)
|
||||
return self._send_request("textDocument/formatting", params)
|
||||
|
||||
def range_formatting(self, params: lsp_types.DocumentRangeFormattingParams) -> list["lsp_types.TextEdit"] | None:
|
||||
"""A request to to format a range in a document."""
|
||||
return self.send_request("textDocument/rangeFormatting", params)
|
||||
return self._send_request("textDocument/rangeFormatting", params)
|
||||
|
||||
def on_type_formatting(self, params: lsp_types.DocumentOnTypeFormattingParams) -> list["lsp_types.TextEdit"] | None:
|
||||
"""A request to format a document on type."""
|
||||
return self.send_request("textDocument/onTypeFormatting", params)
|
||||
return self._send_request("textDocument/onTypeFormatting", params)
|
||||
|
||||
def rename(self, params: lsp_types.RenameParams) -> Union["lsp_types.WorkspaceEdit", None]:
|
||||
"""A request to rename a symbol."""
|
||||
return self.send_request("textDocument/rename", params)
|
||||
return self._send_request("textDocument/rename", params)
|
||||
|
||||
def prepare_rename(self, params: lsp_types.PrepareRenameParams) -> Union["lsp_types.PrepareRenameResult", None]:
|
||||
"""A request to test and perform the setup necessary for a rename.
|
||||
|
||||
@since 3.16 - support for default behavior
|
||||
"""
|
||||
return self.send_request("textDocument/prepareRename", params)
|
||||
return self._send_request("textDocument/prepareRename", params)
|
||||
|
||||
def execute_command(self, params: lsp_types.ExecuteCommandParams) -> Union["lsp_types.LSPAny", None]:
|
||||
"""A request send from the client to the server to execute a command. The request might return
|
||||
a workspace edit which the client will apply to the workspace.
|
||||
"""
|
||||
return self.send_request("workspace/executeCommand", params)
|
||||
return self._send_request("workspace/executeCommand", params)
|
||||
+12
-11
@@ -14,7 +14,7 @@ from pathlib import Path, PurePath
|
||||
|
||||
import requests
|
||||
|
||||
from solidlsp.ls_exceptions import LanguageServerException
|
||||
from solidlsp.ls_exceptions import SolidLSPException
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.ls_types import UnifiedSymbolInformation
|
||||
|
||||
@@ -170,29 +170,30 @@ class FileUtils:
|
||||
"""
|
||||
if not os.path.exists(file_path):
|
||||
logger.log(f"File read '{file_path}' failed: File does not exist.", logging.ERROR)
|
||||
raise LanguageServerException(f"File read '{file_path}' failed: File does not exist.")
|
||||
raise SolidLSPException(f"File read '{file_path}' failed: File does not exist.")
|
||||
try:
|
||||
with open(file_path, encoding="utf-8") as inp_file:
|
||||
return inp_file.read()
|
||||
except Exception as exc:
|
||||
logger.log(f"File read '{file_path}' failed to read with encoding 'utf-8': {exc}", logging.ERROR)
|
||||
raise LanguageServerException("File read failed.") from None
|
||||
raise SolidLSPException("File read failed.") from None
|
||||
|
||||
@staticmethod
|
||||
def download_file(logger: LanguageServerLogger, url: str, target_path: str) -> None:
|
||||
"""
|
||||
Downloads the file from the given URL to the given {target_path}
|
||||
"""
|
||||
os.makedirs(os.path.dirname(target_path), exist_ok=True)
|
||||
try:
|
||||
response = requests.get(url, stream=True, timeout=60)
|
||||
if response.status_code != 200:
|
||||
logger.log(f"Error downloading file '{url}': {response.status_code} {response.text}", logging.ERROR)
|
||||
raise LanguageServerException("Error downloading file.")
|
||||
raise SolidLSPException("Error downloading file.")
|
||||
with open(target_path, "wb") as f:
|
||||
shutil.copyfileobj(response.raw, f)
|
||||
except Exception as exc:
|
||||
logger.log(f"Error downloading file '{url}': {exc}", logging.ERROR)
|
||||
raise LanguageServerException("Error downloading file.") from None
|
||||
raise SolidLSPException("Error downloading file.") from None
|
||||
|
||||
@staticmethod
|
||||
def download_and_extract_archive(logger: LanguageServerLogger, url: str, target_path: str, archive_type: str) -> None:
|
||||
@@ -220,10 +221,10 @@ class FileUtils:
|
||||
shutil.copyfileobj(f_in, f_out)
|
||||
else:
|
||||
logger.log(f"Unknown archive type '{archive_type}' for extraction", logging.ERROR)
|
||||
raise LanguageServerException(f"Unknown archive type '{archive_type}'")
|
||||
raise SolidLSPException(f"Unknown archive type '{archive_type}'")
|
||||
except Exception as exc:
|
||||
logger.log(f"Error extracting archive '{tmp_file_name}' obtained from '{url}': {exc}", logging.ERROR)
|
||||
raise LanguageServerException("Error extracting archive.") from exc
|
||||
raise SolidLSPException("Error extracting archive.") from exc
|
||||
finally:
|
||||
for tmp_file_name in tmp_files:
|
||||
if os.path.exists(tmp_file_name):
|
||||
@@ -289,7 +290,7 @@ class PlatformUtils:
|
||||
platform_id += "-" + libc
|
||||
return PlatformId(platform_id)
|
||||
else:
|
||||
raise LanguageServerException(f"Unknown platform: {system=}, {machine=}, {bitness=}")
|
||||
raise SolidLSPException(f"Unknown platform: {system=}, {machine=}, {bitness=}")
|
||||
|
||||
@staticmethod
|
||||
def _determine_windows_machine_type():
|
||||
@@ -345,7 +346,7 @@ class PlatformUtils:
|
||||
available_version_cmd_output.append(version_cmd_output)
|
||||
|
||||
if not available_version_cmd_output:
|
||||
raise LanguageServerException("dotnet not found on the system")
|
||||
raise SolidLSPException("dotnet not found on the system")
|
||||
|
||||
# Check for supported versions in order of preference (latest first)
|
||||
for version_cmd_output in available_version_cmd_output:
|
||||
@@ -361,7 +362,7 @@ class PlatformUtils:
|
||||
return DotnetVersion.V4
|
||||
|
||||
# If no supported version found, raise exception with all available versions
|
||||
raise LanguageServerException(
|
||||
raise SolidLSPException(
|
||||
f"No supported dotnet version found. Available versions: {', '.join(available_version_cmd_output)}. Supported versions: 4, 6, 7, 8"
|
||||
)
|
||||
except (FileNotFoundError, subprocess.CalledProcessError):
|
||||
@@ -369,7 +370,7 @@ class PlatformUtils:
|
||||
result = subprocess.run(["mono", "--version"], capture_output=True, check=True)
|
||||
return DotnetVersion.VMONO
|
||||
except (FileNotFoundError, subprocess.CalledProcessError):
|
||||
raise LanguageServerException("dotnet or mono not found on the system")
|
||||
raise SolidLSPException("dotnet or mono not found on the system")
|
||||
|
||||
|
||||
class SymbolUtils:
|
||||
|
||||
@@ -59,7 +59,7 @@ class ProcessLaunchInfo:
|
||||
cwd: str = os.getcwd()
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
class LSPError(Exception):
|
||||
def __init__(self, code: ErrorCodes, message: str) -> None:
|
||||
super().__init__(message)
|
||||
self.code = code
|
||||
@@ -68,8 +68,8 @@ class Error(Exception):
|
||||
return {"code": self.code, "message": super().__str__()}
|
||||
|
||||
@classmethod
|
||||
def from_lsp(cls, d: StringDict) -> "Error":
|
||||
return Error(d["code"], d["message"])
|
||||
def from_lsp(cls, d: StringDict) -> "LSPError":
|
||||
return LSPError(d["code"], d["message"])
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{super().__str__()} ({self.code})"
|
||||
@@ -79,7 +79,7 @@ def make_response(request_id: Any, params: PayloadLike) -> StringDict:
|
||||
return {"jsonrpc": "2.0", "id": request_id, "result": params}
|
||||
|
||||
|
||||
def make_error_response(request_id: Any, err: Error) -> StringDict:
|
||||
def make_error_response(request_id: Any, err: LSPError) -> StringDict:
|
||||
return {"jsonrpc": "2.0", "id": request_id, "error": err.to_lsp()}
|
||||
|
||||
|
||||
|
||||
+10
-14
@@ -4,21 +4,17 @@ Defines settings for Solid-LSP
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class SolidLSPSettings:
|
||||
@staticmethod
|
||||
def get_language_server_directory() -> str:
|
||||
"""Returns the directory for language servers"""
|
||||
user_home = pathlib.Path.home()
|
||||
multilspy_dir = str(pathlib.PurePath(user_home, ".multilspy"))
|
||||
lsp_dir = str(pathlib.PurePath(multilspy_dir, "lsp"))
|
||||
os.makedirs(lsp_dir, exist_ok=True)
|
||||
return lsp_dir
|
||||
solidlsp_dir: str = str(pathlib.Path.home() / ".solidlsp")
|
||||
|
||||
@staticmethod
|
||||
def get_global_cache_directory() -> str:
|
||||
"""Returns the cache directory"""
|
||||
global_cache_dir = os.path.join(str(pathlib.Path.home()), ".multilspy", "global_cache")
|
||||
os.makedirs(global_cache_dir, exist_ok=True)
|
||||
return global_cache_dir
|
||||
def __post_init__(self):
|
||||
os.makedirs(str(self.solidlsp_dir), exist_ok=True)
|
||||
os.makedirs(str(self.ls_resources_dir), exist_ok=True)
|
||||
|
||||
@property
|
||||
def ls_resources_dir(self):
|
||||
return os.path.join(str(self.solidlsp_dir), "language_servers", "static")
|
||||
+5
-1
@@ -4,11 +4,13 @@ from pathlib import Path
|
||||
import pytest
|
||||
from sensai.util.logging import configure
|
||||
|
||||
from serena.constants import SERENA_MANAGED_DIR_IN_HOME
|
||||
from serena.project import Project
|
||||
from serena.util.file_system import GitignoreParser
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_logger import LanguageServerLogger
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
configure(level=logging.ERROR)
|
||||
|
||||
@@ -43,7 +45,9 @@ def create_ls(
|
||||
ignored_paths.extend(spec.patterns)
|
||||
config = LanguageServerConfig(code_language=language, ignored_paths=ignored_paths, trace_lsp_communication=trace_lsp_communication)
|
||||
logger = LanguageServerLogger(log_level=log_level)
|
||||
return SolidLanguageServer.create(config, logger, repo_path)
|
||||
return SolidLanguageServer.create(
|
||||
config, logger, repo_path, solidlsp_settings=SolidLSPSettings(solidlsp_dir=SERENA_MANAGED_DIR_IN_HOME)
|
||||
)
|
||||
|
||||
|
||||
def create_default_ls(language: Language) -> SolidLanguageServer:
|
||||
|
||||
+14
-9
@@ -4,16 +4,28 @@ import pytest
|
||||
from mcp.server.fastmcp.tools.base import Tool as MCPTool
|
||||
|
||||
from serena.agent import Tool, ToolRegistry
|
||||
from serena.config.context_mode import SerenaAgentContext
|
||||
from serena.mcp import SerenaMCPFactory
|
||||
|
||||
make_tool = SerenaMCPFactory.make_mcp_tool
|
||||
|
||||
|
||||
# Create a mock agent for tool initialization
|
||||
class MockAgent:
|
||||
def __init__(self):
|
||||
self.project_config = None
|
||||
self.serena_config = None
|
||||
|
||||
@staticmethod
|
||||
def get_context() -> SerenaAgentContext:
|
||||
return SerenaAgentContext.load_default()
|
||||
|
||||
|
||||
class BaseMockTool(Tool):
|
||||
"""A mock Tool class for testing."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
def __init__(self):
|
||||
super().__init__(MockAgent())
|
||||
|
||||
|
||||
class BasicTool(BaseMockTool):
|
||||
@@ -274,13 +286,6 @@ def is_test_mock_class(tool_class: type) -> bool:
|
||||
@pytest.mark.parametrize("tool_class", ToolRegistry().get_all_tool_classes())
|
||||
def test_make_tool_all_tools(tool_class) -> None:
|
||||
"""Test that make_tool works for all tools in the codebase."""
|
||||
|
||||
# Create a mock agent for tool initialization
|
||||
class MockAgent:
|
||||
def __init__(self):
|
||||
self.project_config = None
|
||||
self.serena_config = None
|
||||
|
||||
# Create an instance of the tool
|
||||
tool_instance = tool_class(MockAgent())
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ from solidlsp.language_servers.csharp_language_server import (
|
||||
)
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
|
||||
@pytest.mark.csharp
|
||||
@@ -214,7 +215,9 @@ class TestCSharpSolutionProjectOpening:
|
||||
mock_config.ignored_paths = []
|
||||
|
||||
# Create CSharpLanguageServer instance
|
||||
CSharpLanguageServer(mock_config, mock_logger, str(temp_path))
|
||||
mock_settings = Mock(spec=SolidLSPSettings)
|
||||
mock_settings.ls_resources_dir = "/tmp/test_ls_resources"
|
||||
CSharpLanguageServer(mock_config, mock_logger, str(temp_path), mock_settings)
|
||||
|
||||
# Verify that logger was called with solution file discovery
|
||||
mock_logger.log.assert_any_call(f"Found solution/project file: {solution_file}", 20) # logging.INFO
|
||||
@@ -236,7 +239,9 @@ class TestCSharpSolutionProjectOpening:
|
||||
mock_config.ignored_paths = []
|
||||
|
||||
# Create CSharpLanguageServer instance
|
||||
CSharpLanguageServer(mock_config, mock_logger, str(temp_path))
|
||||
mock_settings = Mock(spec=SolidLSPSettings)
|
||||
mock_settings.ls_resources_dir = "/tmp/test_ls_resources"
|
||||
CSharpLanguageServer(mock_config, mock_logger, str(temp_path), mock_settings)
|
||||
|
||||
# Verify that logger was called with warning about no solution/project files
|
||||
mock_logger.log.assert_any_call(
|
||||
|
||||
@@ -4,7 +4,7 @@ requires-python = "==3.11.*"
|
||||
|
||||
[[package]]
|
||||
name = "agno"
|
||||
version = "1.7.2"
|
||||
version = "1.7.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "docstring-parser" },
|
||||
@@ -20,9 +20,9 @@ dependencies = [
|
||||
{ name = "typer" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/50/64/6a82d2375145fd733009db0ada531b595fc2f3c1712ac91c91b2e5fcf02f/agno-1.7.2.tar.gz", hash = "sha256:f4f97e5ea1f9c0781d9bb8396862c82671381386c1569cdb456d70f6c8d3a108", size = 661788, upload-time = "2025-07-10T15:50:06.217Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b5/84/d2390249d1b0150971a749df79cf653a631227b748fe10be1ef4f426ed2d/agno-1.7.6.tar.gz", hash = "sha256:8df575f86eac6511996045fb90d281ca61fd19774dccbb82f9dc34bda46effdf", size = 724233, upload-time = "2025-07-24T14:36:27.356Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/24/884cbd1133e3b9985d53733250251098caead365bb8706af8da105813abe/agno-1.7.2-py3-none-any.whl", hash = "sha256:37a1985e67762f0d8016318ba5d8b8697c855827d7c86b21a41cdff088294cc2", size = 888229, upload-time = "2025-07-10T15:50:04.262Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/7e/5f85c92a34f41f8ffb1467768bae50506f734320c478ed24c4237dc342ea/agno-1.7.6-py3-none-any.whl", hash = "sha256:c6350e44747b37048d4ac080ffa5fb0e0ce2b3d5bc55d37120711ae8d31f27c6", size = 962755, upload-time = "2025-07-24T14:36:25.648Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -36,7 +36,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "anthropic"
|
||||
version = "0.57.1"
|
||||
version = "0.59.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
@@ -47,9 +47,9 @@ dependencies = [
|
||||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d7/75/6261a1a8d92aed47e27d2fcfb3a411af73b1435e6ae1186da02b760565d0/anthropic-0.57.1.tar.gz", hash = "sha256:7815dd92245a70d21f65f356f33fc80c5072eada87fb49437767ea2918b2c4b0", size = 423775, upload-time = "2025-07-03T16:57:35.932Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/52daff015f5a1f24eec891b3041f5f816712fea8b5113dc76638bcbc23d8/anthropic-0.59.0.tar.gz", hash = "sha256:d710d1ef0547ebbb64b03f219e44ba078e83fc83752b96a9b22e9726b523fd8f", size = 425679, upload-time = "2025-07-23T16:23:16.901Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/cf/ca0ba77805aec6171629a8b665c7dc224dab374539c3d27005b5d8c100a0/anthropic-0.57.1-py3-none-any.whl", hash = "sha256:33afc1f395af207d07ff1bffc0a3d1caac53c371793792569c5d2f09283ea306", size = 292779, upload-time = "2025-07-03T16:57:34.636Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/b1/03f680393eac04afd8f2be44ee0e39e033c40faf43dbc1c11764b07a2687/anthropic-0.59.0-py3-none-any.whl", hash = "sha256:cbc8b3dccef66ad6435c4fa1d317e5ebb092399a4b88b33a09dc4bf3944c3183", size = 293057, upload-time = "2025-07-23T16:23:14.934Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -130,11 +130,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.7.9"
|
||||
version = "2025.7.14"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/de/8a/c729b6b60c66a38f590c4e774decc4b2ec7b0576be8f1aa984a53ffa812a/certifi-2025.7.9.tar.gz", hash = "sha256:c1d2ec05395148ee10cf672ffc28cd37ea0ab0d99f9cc74c43e588cbd111b079", size = 160386, upload-time = "2025-07-09T02:13:58.874Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/66/f3/80a3f974c8b535d394ff960a11ac20368e06b736da395b551a49ce950cce/certifi-2025.7.9-py3-none-any.whl", hash = "sha256:d842783a14f8fdd646895ac26f719a061408834473cfc10203f6a575beb15d39", size = 159230, upload-time = "2025-07-09T02:13:57.007Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -200,11 +200,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "docstring-parser"
|
||||
version = "0.16"
|
||||
version = "0.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/08/12/9c22a58c0b1e29271051222d8906257616da84135af9ed167c9e28f85cb3/docstring_parser-0.16.tar.gz", hash = "sha256:538beabd0af1e2db0146b6bd3caa526c35a34d61af9fd2887f3a8a27a739aa6e", size = 26565, upload-time = "2024-03-15T10:39:44.419Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/7c/e9fcff7623954d86bdc17782036cbf715ecab1bec4847c008557affe1ca8/docstring_parser-0.16-py3-none-any.whl", hash = "sha256:bf0a1387354d3691d102edef7ec124f219ef639982d096e26e3b60aeffa90637", size = 36533, upload-time = "2024-03-15T10:39:41.527Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -267,14 +267,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "gitpython"
|
||||
version = "3.1.44"
|
||||
version = "3.1.45"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "gitdb" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c0/89/37df0b71473153574a5cdef8f242de422a0f5d26d7a9e231e6f169b4ad14/gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269", size = 214196, upload-time = "2025-01-02T07:32:43.59Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599, upload-time = "2025-01-02T07:32:40.731Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -293,7 +293,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "google-genai"
|
||||
version = "1.25.0"
|
||||
version = "1.27.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
@@ -305,9 +305,9 @@ dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7f/59/c9b9148c8702b60253f5a251f16ae436534c5d4362da193c9db05ac9858c/google_genai-1.25.0.tar.gz", hash = "sha256:a08a79c819a5d949d9948cd372e36e512bf85cd28158994daaa36d0ec4cb2b02", size = 228141, upload-time = "2025-07-09T20:53:47.885Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9a/37/6c0ececc3a7a629029b5beed2ceb9f28f73292236eb96272355636769b0d/google_genai-1.27.0.tar.gz", hash = "sha256:15a13ffe7b3938da50b9ab77204664d82122617256f55b5ce403d593848ef635", size = 220099, upload-time = "2025-07-23T22:00:46.145Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/ec/149f3d49b56cf848142071772aabb1c290b535bd9b5327a5dfccf1d00332/google_genai-1.25.0-py3-none-any.whl", hash = "sha256:fb5cee79b9a0a1b2afd5cfdf279099ecebd186551eefcaa6ec0c6016244e6138", size = 226847, upload-time = "2025-07-09T20:53:46.532Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/12/279afe7357af73f9737a3412b6f0bc1482075b896340eb46a2f9cb0fd791/google_genai-1.27.0-py3-none-any.whl", hash = "sha256:afd6b4efaf8ec1d20a6e6657d768b68d998d60007c6e220e9024e23c913c1833", size = 218489, upload-time = "2025-07-23T22:00:44.879Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -489,7 +489,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema"
|
||||
version = "4.24.0"
|
||||
version = "4.25.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
@@ -497,9 +497,9 @@ dependencies = [
|
||||
{ name = "referencing" },
|
||||
{ name = "rpds-py" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bf/d3/1cf5326b923a53515d8f3a2cd442e6d7e94fcc444716e879ea70a0ce3177/jsonschema-4.24.0.tar.gz", hash = "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196", size = 353480, upload-time = "2025-05-26T18:48:10.459Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d5/00/a297a868e9d0784450faa7365c2172a7d6110c763e30ba861867c32ae6a9/jsonschema-4.25.0.tar.gz", hash = "sha256:e63acf5c11762c0e6672ffb61482bdf57f0876684d8d249c0fe2d730d48bc55f", size = 356830, upload-time = "2025-07-18T15:39:45.11Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/3d/023389198f69c722d039351050738d6755376c8fd343e91dc493ea485905/jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d", size = 88709, upload-time = "2025-05-26T18:48:08.417Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/54/c86cd8e011fe98803d7e382fd67c0df5ceab8d2b7ad8c5a81524f791551c/jsonschema-4.25.0-py3-none-any.whl", hash = "sha256:24c2e8da302de79c8b9382fee3e76b355e44d2a4364bb207159ce10b517bd716", size = 89184, upload-time = "2025-07-18T15:39:42.956Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -558,7 +558,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "mcp"
|
||||
version = "1.11.0"
|
||||
version = "1.12.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
@@ -573,9 +573,9 @@ dependencies = [
|
||||
{ name = "starlette" },
|
||||
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3a/f5/9506eb5578d5bbe9819ee8ba3198d0ad0e2fbe3bab8b257e4131ceb7dfb6/mcp-1.11.0.tar.gz", hash = "sha256:49a213df56bb9472ff83b3132a4825f5c8f5b120a90246f08b0dac6bedac44c8", size = 406907, upload-time = "2025-07-10T16:41:09.388Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4d/19/9955e2df5384ff5dd25d38f8e88aaf89d2d3d9d39f27e7383eaf0b293836/mcp-1.12.3.tar.gz", hash = "sha256:ab2e05f5e5c13e1dc90a4a9ef23ac500a6121362a564447855ef0ab643a99fed", size = 427203, upload-time = "2025-07-31T18:36:36.795Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/92/9c/c9ca79f9c512e4113a5d07043013110bb3369fc7770040c61378c7fbcf70/mcp-1.11.0-py3-none-any.whl", hash = "sha256:58deac37f7483e4b338524b98bc949b7c2b7c33d978f5fafab5bde041c5e2595", size = 155880, upload-time = "2025-07-10T16:41:07.935Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/8b/0be74e3308a486f1d127f3f6767de5f9f76454c9b4183210c61cc50999b6/mcp-1.12.3-py3-none-any.whl", hash = "sha256:5483345bf39033b858920a5b6348a303acacf45b23936972160ff152107b850e", size = 158810, upload-time = "2025-07-31T18:36:34.915Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -589,22 +589,22 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.16.1"
|
||||
version = "1.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mypy-extensions" },
|
||||
{ name = "pathspec" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/81/69/92c7fa98112e4d9eb075a239caa4ef4649ad7d441545ccffbd5e34607cbb/mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab", size = 3324747, upload-time = "2025-06-16T16:51:35.145Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1e/e3/034322d5a779685218ed69286c32faa505247f1f096251ef66c8fd203b08/mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03", size = 3352114, upload-time = "2025-07-14T20:34:30.181Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/61/ec1245aa1c325cb7a6c0f8570a2eee3bfc40fa90d19b1267f8e50b5c8645/mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc", size = 10890557, upload-time = "2025-06-16T16:37:21.421Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/bb/6eccc0ba0aa0c7a87df24e73f0ad34170514abd8162eb0c75fd7128171fb/mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782", size = 10012921, upload-time = "2025-06-16T16:51:28.659Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/80/b337a12e2006715f99f529e732c5f6a8c143bb58c92bb142d5ab380963a5/mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507", size = 11802887, upload-time = "2025-06-16T16:50:53.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/59/f7af072d09793d581a745a25737c7c0a945760036b16aeb620f658a017af/mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca", size = 12531658, upload-time = "2025-06-16T16:33:55.002Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/c4/607672f2d6c0254b94a646cfc45ad589dd71b04aa1f3d642b840f7cce06c/mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4", size = 12732486, upload-time = "2025-06-16T16:37:03.301Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/5e/136555ec1d80df877a707cebf9081bd3a9f397dedc1ab9750518d87489ec/mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6", size = 9479482, upload-time = "2025-06-16T16:47:37.48Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/d3/53e684e78e07c1a2bf7105715e5edd09ce951fc3f47cf9ed095ec1b7a037/mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37", size = 2265923, upload-time = "2025-06-16T16:48:02.366Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/24/82efb502b0b0f661c49aa21cfe3e1999ddf64bf5500fc03b5a1536a39d39/mypy-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d4fe5c72fd262d9c2c91c1117d16aac555e05f5beb2bae6a755274c6eec42be", size = 10914150, upload-time = "2025-07-14T20:31:51.985Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/96/8ef9a6ff8cedadff4400e2254689ca1dc4b420b92c55255b44573de10c54/mypy-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96b196e5c16f41b4f7736840e8455958e832871990c7ba26bf58175e357ed61", size = 10039845, upload-time = "2025-07-14T20:32:30.527Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/32/7ce359a56be779d38021d07941cfbb099b41411d72d827230a36203dbb81/mypy-1.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73a0ff2dd10337ceb521c080d4147755ee302dcde6e1a913babd59473904615f", size = 11837246, upload-time = "2025-07-14T20:32:01.28Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/16/b775047054de4d8dbd668df9137707e54b07fe18c7923839cd1e524bf756/mypy-1.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24cfcc1179c4447854e9e406d3af0f77736d631ec87d31c6281ecd5025df625d", size = 12571106, upload-time = "2025-07-14T20:34:26.942Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/cf/fa33eaf29a606102c8d9ffa45a386a04c2203d9ad18bf4eef3e20c43ebc8/mypy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56f180ff6430e6373db7a1d569317675b0a451caf5fef6ce4ab365f5f2f6c3", size = 12759960, upload-time = "2025-07-14T20:33:42.882Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/75/3f5a29209f27e739ca57e6350bc6b783a38c7621bdf9cac3ab8a08665801/mypy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:eafaf8b9252734400f9b77df98b4eee3d2eecab16104680d51341c75702cad70", size = 9503888, upload-time = "2025-07-14T20:32:34.392Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/fc/ee058cc4316f219078464555873e99d170bde1d9569abd833300dbeb484a/mypy-1.17.0-py3-none-any.whl", hash = "sha256:15d9d0018237ab058e5de3d8fce61b6fa72cc59cc78fd91f1b474bce12abf496", size = 2283195, upload-time = "2025-07-14T20:31:54.753Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -913,12 +913,12 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pywin32"
|
||||
version = "310"
|
||||
version = "311"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/b1/68aa2986129fb1011dabbe95f0136f44509afaf072b12b8f815905a39f33/pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd", size = 8784284, upload-time = "2025-03-17T00:55:53.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/bd/d1592635992dd8db5bb8ace0551bc3a769de1ac8850200cfa517e72739fb/pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c", size = 9520748, upload-time = "2025-03-17T00:55:55.203Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/b1/ac8b1ffce6603849eb45a91cf126c0fa5431f186c2e768bf56889c46f51c/pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582", size = 8455941, upload-time = "2025-03-17T00:55:57.048Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -992,15 +992,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "14.0.0"
|
||||
version = "14.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markdown-it-py" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1079,39 +1079,39 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.12.3"
|
||||
version = "0.12.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c3/2a/43955b530c49684d3c38fcda18c43caf91e99204c2a065552528e0552d4f/ruff-0.12.3.tar.gz", hash = "sha256:f1b5a4b6668fd7b7ea3697d8d98857390b40c1320a63a178eee6be0899ea2d77", size = 4459341, upload-time = "2025-07-11T13:21:16.086Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/30/cd/01015eb5034605fd98d829c5839ec2c6b4582b479707f7c1c2af861e8258/ruff-0.12.5.tar.gz", hash = "sha256:b209db6102b66f13625940b7f8c7d0f18e20039bb7f6101fbdac935c9612057e", size = 5170722, upload-time = "2025-07-24T13:26:37.456Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/fd/b44c5115539de0d598d75232a1cc7201430b6891808df111b8b0506aae43/ruff-0.12.3-py3-none-linux_armv6l.whl", hash = "sha256:47552138f7206454eaf0c4fe827e546e9ddac62c2a3d2585ca54d29a890137a2", size = 10430499, upload-time = "2025-07-11T13:20:26.321Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/c5/9eba4f337970d7f639a37077be067e4ec80a2ad359e4cc6c5b56805cbc66/ruff-0.12.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0a9153b000c6fe169bb307f5bd1b691221c4286c133407b8827c406a55282041", size = 11213413, upload-time = "2025-07-11T13:20:30.017Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/2c/fac3016236cf1fe0bdc8e5de4f24c76ce53c6dd9b5f350d902549b7719b2/ruff-0.12.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fa6b24600cf3b750e48ddb6057e901dd5b9aa426e316addb2a1af185a7509882", size = 10586941, upload-time = "2025-07-11T13:20:33.046Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/0f/41fec224e9dfa49a139f0b402ad6f5d53696ba1800e0f77b279d55210ca9/ruff-0.12.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2506961bf6ead54887ba3562604d69cb430f59b42133d36976421bc8bd45901", size = 10783001, upload-time = "2025-07-11T13:20:35.534Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/ca/dd64a9ce56d9ed6cad109606ac014860b1c217c883e93bf61536400ba107/ruff-0.12.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c4faaff1f90cea9d3033cbbcdf1acf5d7fb11d8180758feb31337391691f3df0", size = 10269641, upload-time = "2025-07-11T13:20:38.459Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/5c/2be545034c6bd5ce5bb740ced3e7014d7916f4c445974be11d2a406d5088/ruff-0.12.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40dced4a79d7c264389de1c59467d5d5cefd79e7e06d1dfa2c75497b5269a5a6", size = 11875059, upload-time = "2025-07-11T13:20:41.517Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/d4/a74ef1e801ceb5855e9527dae105eaff136afcb9cc4d2056d44feb0e4792/ruff-0.12.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:0262d50ba2767ed0fe212aa7e62112a1dcbfd46b858c5bf7bbd11f326998bafc", size = 12658890, upload-time = "2025-07-11T13:20:44.442Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/c8/1057916416de02e6d7c9bcd550868a49b72df94e3cca0aeb77457dcd9644/ruff-0.12.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12371aec33e1a3758597c5c631bae9a5286f3c963bdfb4d17acdd2d395406687", size = 12232008, upload-time = "2025-07-11T13:20:47.374Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/59/4f7c130cc25220392051fadfe15f63ed70001487eca21d1796db46cbcc04/ruff-0.12.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:560f13b6baa49785665276c963edc363f8ad4b4fc910a883e2625bdb14a83a9e", size = 11499096, upload-time = "2025-07-11T13:20:50.348Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/01/a0ad24a5d2ed6be03a312e30d32d4e3904bfdbc1cdbe63c47be9d0e82c79/ruff-0.12.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023040a3499f6f974ae9091bcdd0385dd9e9eb4942f231c23c57708147b06311", size = 11688307, upload-time = "2025-07-11T13:20:52.945Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/72/08f9e826085b1f57c9a0226e48acb27643ff19b61516a34c6cab9d6ff3fa/ruff-0.12.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:883d844967bffff5ab28bba1a4d246c1a1b2933f48cb9840f3fdc5111c603b07", size = 10661020, upload-time = "2025-07-11T13:20:55.799Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/a0/68da1250d12893466c78e54b4a0ff381370a33d848804bb51279367fc688/ruff-0.12.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2120d3aa855ff385e0e562fdee14d564c9675edbe41625c87eeab744a7830d12", size = 10246300, upload-time = "2025-07-11T13:20:58.222Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/22/5f0093d556403e04b6fd0984fc0fb32fbb6f6ce116828fd54306a946f444/ruff-0.12.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6b16647cbb470eaf4750d27dddc6ebf7758b918887b56d39e9c22cce2049082b", size = 11263119, upload-time = "2025-07-11T13:21:01.503Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/c9/f4c0b69bdaffb9968ba40dd5fa7df354ae0c73d01f988601d8fac0c639b1/ruff-0.12.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e1417051edb436230023575b149e8ff843a324557fe0a265863b7602df86722f", size = 11746990, upload-time = "2025-07-11T13:21:04.524Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/84/7cc7bd73924ee6be4724be0db5414a4a2ed82d06b30827342315a1be9e9c/ruff-0.12.3-py3-none-win32.whl", hash = "sha256:dfd45e6e926deb6409d0616078a666ebce93e55e07f0fb0228d4b2608b2c248d", size = 10589263, upload-time = "2025-07-11T13:21:07.148Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/87/c070f5f027bd81f3efee7d14cb4d84067ecf67a3a8efb43aadfc72aa79a6/ruff-0.12.3-py3-none-win_amd64.whl", hash = "sha256:a946cf1e7ba3209bdef039eb97647f1c77f6f540e5845ec9c114d3af8df873e7", size = 11695072, upload-time = "2025-07-11T13:21:11.004Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/30/f3eaf6563c637b6e66238ed6535f6775480db973c836336e4122161986fc/ruff-0.12.3-py3-none-win_arm64.whl", hash = "sha256:5f9c7c9c8f84c2d7f27e93674d27136fbf489720251544c4da7fb3d742e011b1", size = 10805855, upload-time = "2025-07-11T13:21:13.547Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/de/ad2f68f0798ff15dd8c0bcc2889558970d9a685b3249565a937cd820ad34/ruff-0.12.5-py3-none-linux_armv6l.whl", hash = "sha256:1de2c887e9dec6cb31fcb9948299de5b2db38144e66403b9660c9548a67abd92", size = 11819133, upload-time = "2025-07-24T13:25:56.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/fc/c6b65cd0e7fbe60f17e7ad619dca796aa49fbca34bb9bea5f8faf1ec2643/ruff-0.12.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d1ab65e7d8152f519e7dea4de892317c9da7a108da1c56b6a3c1d5e7cf4c5e9a", size = 12501114, upload-time = "2025-07-24T13:25:59.471Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/de/c6bec1dce5ead9f9e6a946ea15e8d698c35f19edc508289d70a577921b30/ruff-0.12.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:962775ed5b27c7aa3fdc0d8f4d4433deae7659ef99ea20f783d666e77338b8cf", size = 11716873, upload-time = "2025-07-24T13:26:01.496Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/16/cf372d2ebe91e4eb5b82a2275c3acfa879e0566a7ac94d331ea37b765ac8/ruff-0.12.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b4cae449597e7195a49eb1cdca89fd9fbb16140c7579899e87f4c85bf82f73", size = 11958829, upload-time = "2025-07-24T13:26:03.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/bf/cd07e8f6a3a6ec746c62556b4c4b79eeb9b0328b362bb8431b7b8afd3856/ruff-0.12.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b13489c3dc50de5e2d40110c0cce371e00186b880842e245186ca862bf9a1ac", size = 11626619, upload-time = "2025-07-24T13:26:06.118Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/c9/c2ccb3b8cbb5661ffda6925f81a13edbb786e623876141b04919d1128370/ruff-0.12.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1504fea81461cf4841778b3ef0a078757602a3b3ea4b008feb1308cb3f23e08", size = 13221894, upload-time = "2025-07-24T13:26:08.292Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/58/68a5be2c8e5590ecdad922b2bcd5583af19ba648f7648f95c51c3c1eca81/ruff-0.12.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c7da4129016ae26c32dfcbd5b671fe652b5ab7fc40095d80dcff78175e7eddd4", size = 14163909, upload-time = "2025-07-24T13:26:10.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/d1/ef6b19622009ba8386fdb792c0743f709cf917b0b2f1400589cbe4739a33/ruff-0.12.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca972c80f7ebcfd8af75a0f18b17c42d9f1ef203d163669150453f50ca98ab7b", size = 13583652, upload-time = "2025-07-24T13:26:13.381Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/e3/1c98c566fe6809a0c83751d825a03727f242cdbe0d142c9e292725585521/ruff-0.12.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dbbf9f25dfb501f4237ae7501d6364b76a01341c6f1b2cd6764fe449124bb2a", size = 12700451, upload-time = "2025-07-24T13:26:15.488Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/ff/96058f6506aac0fbc0d0fc0d60b0d0bd746240a0594657a2d94ad28033ba/ruff-0.12.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c47dea6ae39421851685141ba9734767f960113d51e83fd7bb9958d5be8763a", size = 12937465, upload-time = "2025-07-24T13:26:17.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/d3/68bc5e7ab96c94b3589d1789f2dd6dd4b27b263310019529ac9be1e8f31b/ruff-0.12.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5076aa0e61e30f848846f0265c873c249d4b558105b221be1828f9f79903dc5", size = 11771136, upload-time = "2025-07-24T13:26:20.422Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/75/7356af30a14584981cabfefcf6106dea98cec9a7af4acb5daaf4b114845f/ruff-0.12.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a5a4c7830dadd3d8c39b1cc85386e2c1e62344f20766be6f173c22fb5f72f293", size = 11601644, upload-time = "2025-07-24T13:26:22.928Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/67/91c71d27205871737cae11025ee2b098f512104e26ffd8656fd93d0ada0a/ruff-0.12.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:46699f73c2b5b137b9dc0fc1a190b43e35b008b398c6066ea1350cce6326adcb", size = 12478068, upload-time = "2025-07-24T13:26:26.134Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/04/b6b00383cf2f48e8e78e14eb258942fdf2a9bf0287fbf5cdd398b749193a/ruff-0.12.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a655a0a0d396f0f072faafc18ebd59adde8ca85fb848dc1b0d9f024b9c4d3bb", size = 12991537, upload-time = "2025-07-24T13:26:28.533Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/b9/053d6445dc7544fb6594785056d8ece61daae7214859ada4a152ad56b6e0/ruff-0.12.5-py3-none-win32.whl", hash = "sha256:dfeb2627c459b0b78ca2bbdc38dd11cc9a0a88bf91db982058b26ce41714ffa9", size = 11751575, upload-time = "2025-07-24T13:26:30.835Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/0f/ab16e8259493137598b9149734fec2e06fdeda9837e6f634f5c4e35916da/ruff-0.12.5-py3-none-win_amd64.whl", hash = "sha256:ae0d90cf5f49466c954991b9d8b953bd093c32c27608e409ae3564c63c5306a5", size = 12882273, upload-time = "2025-07-24T13:26:32.929Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/db/c376b0661c24cf770cb8815268190668ec1330eba8374a126ceef8c72d55/ruff-0.12.5-py3-none-win_arm64.whl", hash = "sha256:48cdbfc633de2c5c37d9f090ba3b352d1576b0015bfc3bc98eaf230275b7e805", size = 11951564, upload-time = "2025-07-24T13:26:34.994Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sensai-utils"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e9/b1/9dba08e2d8ea739b58d1ac393c066169f1ca6506c86f0322d6e56e600ae2/sensai_utils-1.4.0.tar.gz", hash = "sha256:2d32bdcc91fd1428c5cae0181e98623142d2d5f7e115e23d585a842dd9dc59ba", size = 56317, upload-time = "2025-01-27T22:47:51.121Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8c/dd/faa2e2de71a03af3def212c70777e794dd54ad5ab87927bb5c29f85f24fc/sensai_utils-1.5.0.tar.gz", hash = "sha256:2ca709a0d5807caf1632d665a455c173987b25276ce61693021672e875f0f17b", size = 61677, upload-time = "2025-07-28T22:46:10.148Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/df/49cc942b653427df0bbd3b9a2fb1e85652df3d4c1d9f6dcbfce1b6155716/sensai_utils-1.4.0-py3-none-any.whl", hash = "sha256:ed6fc57552620e43b33cf364ea0bc0fd7df39391069dd7b621b113ef55547507", size = 63214, upload-time = "2025-01-27T22:47:49.606Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/d5/62a0e693230bace8e9a767d6d187a4d9421a7c6ee4b48551f8ff7bd1629a/sensai_utils-1.5.0-py3-none-any.whl", hash = "sha256:c23c51d23d353e7a9b77c70aa8ba7f2d0a8fe4e67ee5bc1ef41c69dba5e4befb", size = 68836, upload-time = "2025-07-28T22:46:08.929Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1174,7 +1174,7 @@ requires-dist = [
|
||||
{ name = "jinja2", specifier = ">=3.1.6" },
|
||||
{ name = "jinja2", marker = "extra == 'dev'" },
|
||||
{ name = "joblib", specifier = ">=1.5.1" },
|
||||
{ name = "mcp", specifier = ">=1.5.0" },
|
||||
{ name = "mcp", specifier = "==1.12.3" },
|
||||
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.16.1" },
|
||||
{ name = "overrides", specifier = ">=7.7.0,<8" },
|
||||
{ name = "pathspec", specifier = ">=0.12.1" },
|
||||
@@ -1189,7 +1189,7 @@ requires-dist = [
|
||||
{ name = "requests", specifier = ">=2.32.3,<3" },
|
||||
{ name = "ruamel-yaml", specifier = ">=0.18.0" },
|
||||
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.0.285" },
|
||||
{ name = "sensai-utils", specifier = ">=1.4.0" },
|
||||
{ name = "sensai-utils", specifier = ">=1.5.0" },
|
||||
{ name = "sqlalchemy", marker = "extra == 'agno'", specifier = ">=2.0.40" },
|
||||
{ name = "syrupy", marker = "extra == 'dev'", specifier = ">=4.9.1" },
|
||||
{ name = "tiktoken", specifier = ">=0.9.0" },
|
||||
@@ -1250,14 +1250,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "sse-starlette"
|
||||
version = "2.4.1"
|
||||
version = "3.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/07/3e/eae74d8d33e3262bae0a7e023bb43d8bdd27980aa3557333f4632611151f/sse_starlette-2.4.1.tar.gz", hash = "sha256:7c8a800a1ca343e9165fc06bbda45c78e4c6166320707ae30b416c42da070926", size = 18635, upload-time = "2025-07-06T09:41:33.631Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/42/6f/22ed6e33f8a9e76ca0a412405f31abb844b779d52c5f96660766edcd737c/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a", size = 20985, upload-time = "2025-07-27T09:07:44.565Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/f1/6c7eaa8187ba789a6dd6d74430307478d2a91c23a5452ab339b6fbe15a08/sse_starlette-2.4.1-py3-none-any.whl", hash = "sha256:08b77ea898ab1a13a428b2b6f73cfe6d0e607a7b4e15b9bb23e4a37b087fd39a", size = 10824, upload-time = "2025-07-06T09:41:32.321Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/10/c78f463b4ef22eef8491f218f692be838282cd65480f6e423d7730dfd1fb/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a", size = 11297, upload-time = "2025-07-27T09:07:43.268Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1276,15 +1276,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.47.1"
|
||||
version = "0.47.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/69/662169fdb92fb96ec3eaee218cf540a629d629c86d7993d9651226a6789b/starlette-0.47.1.tar.gz", hash = "sha256:aef012dd2b6be325ffa16698f9dc533614fb1cebd593a906b90dc1025529a79b", size = 2583072, upload-time = "2025-06-21T04:03:17.337Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948, upload-time = "2025-07-20T17:31:58.522Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/82/95/38ef0cd7fa11eaba6a99b3c4f5ac948d8bc6ff199aabd327a29cc000840c/starlette-0.47.1-py3-none-any.whl", hash = "sha256:5e11c9f5c7c3f24959edbf2dffdc01bba860228acf657129467d8a7468591527", size = 72747, upload-time = "2025-06-21T04:03:15.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload-time = "2025-07-20T17:31:56.738Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in new issue
Block a user